为何C++20允许将模板用作非类型模板参数?
C++20中类模板实例作为非类型模板参数的合法性解析
我发现自C++20起,将模板实例用作非类型模板参数的“类型”是被允许的,示例代码如下:
template< typename T > struct LiteralType { T a, b, c; }; template< LiteralType t > struct S { static constexpr auto value = t; }; auto f() { return S< LiteralType< int >{} >::value; }
我的疑问是:
- 为什么这种写法是被允许的?
- 直观上
LiteralType的每个实例化都是字面类型(或结构类型),但标准中哪里有正式规定? - 粗略查阅标准草案
[temp.param]p6部分后,发现里面说仅允许“类型”作为非类型模板参数,而非模板,这似乎和示例矛盾?
核心解释
你这里的写法并不是把模板作为非类型模板参数的类型,而是利用了C++20引入的类模板实参推导(CTAD),用类模板名作为占位符,让编译器推导出具体的实例化类型(也就是LiteralType<int>这种具体类类型),最终作为非类型模板参数的类型。
一、合法性的来源
在template< LiteralType t >的声明中,LiteralType并非直接作为模板使用,而是触发了CTAD:编译器会根据传入的模板实参LiteralType<int>{},推导出类模板LiteralType的模板实参为int,从而确定非类型模板参数的实际类型是LiteralType<int>。
这是C++20对非类型模板参数的扩展规则——允许在非类型模板参数的类型位置使用类模板名,只要能通过后续的模板实参推导出具体的实例化类型。
二、标准中的正式依据
非类型模板参数的类型要求
C++20标准在[temp.param]p4中明确,非类型模板参数的类型必须是字面类型(literal type)。而LiteralType<int>完全符合字面类型的定义:- 它是聚合类型(aggregate type),且所有成员(
int类型的a/b/c)都是字面类型; - 根据
[basic.types.general]p10,聚合类型属于字面类型的范畴。
- 它是聚合类型(aggregate type),且所有成员(
类模板名作为非类型模板参数类型的规则
针对你提到的[temp.param]p6,该条款的完整逻辑是:非类型模板参数的类型必须是一个合法的“类型”,但允许用类模板名作为类型占位符,前提是编译器能通过模板实参推导出具体的实例化类型。这里的“类型”包括推导得到的具体实例化类型,并非禁止使用类模板名来声明参数。
三、关键总结
- 代码中的非类型模板参数实际类型是
LiteralType<int>,属于合法的字面类型,满足C++20对非类型模板参数的类型要求; template< LiteralType t >中的LiteralType是CTAD的占位符,最终会被推导为具体类型,而非直接将模板作为类型;- 字面类型的定义确保了
LiteralType<T>的实例化类型可以在编译期构造、复制,符合非类型模板参数的编译期常量要求。
内容的提问来源于stack exchange,提问作者mkluwe
相关产品推荐
相关产品推荐

