通过CTAD将模板名嵌入变量声明中
根据 P2996,在C++26中我们可以得到一个类模板的反射,因此对其进行拼接应该会产生一个模板名称。因此,我们可能尝试将模板名称拼接到一个声明中,该声明使用类模板参数推导来初始化一个变量:
template <class T> struct X;
template <> struct X<int> {
constexpr X(int a, int b) {}
};
X(int, int) -> X<int>;
static constexpr auto template_info = ^^X;
X x{ 5, 10 }; // a usual CTAD initialization
[:template_info:] y{ 13, 12 }; // splicing a reflection of the X template-name
这在Bloomberg Clang的分支上可以工作(godbolt链接)。它也接受拼接 typename[:template_info:],但不接受 template[:template_info:]。另一方面,GCC 16拒绝这三种情况(godbolt链接),似乎在解析带有 error: expected unqualified-id before '[:' token 的拼接时失败。
我的问题是:
- P2996及标准的其余部分是否允许将模板名称拼接到一个变量声明,而该声明通常可以通过CTAD解析?
- 这两个编译器中哪一个处理得更正确(如果有的话)?
谢谢!
解决方案
C++26草案明确支持当类模板或别名模板由一个拼接指定时的类模板参数推导(CTAD)。请参见 [dcl.type.simple]/3:
A 占位符类型说明符 是一个用于推导类型的占位符 ([dcl.spec.auto])。 A type-specifier is a placeholder for a deduced class type ([dcl.type.class.deduct]) if either
- 它的形式是
typenameopt 嵌套名限定符opt 模板名,或- 它的形式是
typenameopt 拼接说明符,且 拼接说明符 指定一个类模板或别名模板。The 嵌套名限定符 或 拼接说明符,若有,应该是非依赖的,且 模板名 或 拼接说明符 应指示一个可推导模板。一个 可推导模板 要么是类模板,要么是一个别名模板,其 defining-type-id 的形式为
typenameopt 嵌套名限定符opttemplateopt 简单模板标识符其中,若有的 嵌套名限定符 是非依赖的,且 简单模板标识符 的 模板名 指示一个可推导模板。 [...]
X 是一个类模板,因此它是一个可推导模板,[:template_info:] 是一个 拼接说明符,它指定 X,因此它可以用作CTAD的占位符。注意,这段落允许在 拼接说明符 之前可选地出现 typename,但不允许 template。Clang的实现是正确的。
之所以这里不允许 template 歧义符,是因为通常只有在模板名后跟着尖括号时才需要用于消解歧义;例如,你有时需要写 A<T>::template B<x> y;,以便编译器知道该解释不是表达式 (A<T>::B < x) > y;。在没有后续的 < 的情况下,写 A<T>::template B 是允许的,但已被弃用(据我所知,唯一会这样做的场景是在把该模板作为模板模板参数使用时),只是因为在C++23之前,它曾被允许,并且对是否需要它存在一些不确定性;当时有些编译器就会要求它。将不必要的 template 歧义符的许可并不会扩展到更新的特性,如反射。
在这一区域上下文中,typename 歧义符似乎也不是必需的,因为你只允许对非依赖的反射进行拼接。然而,为了与一般情况下的 拼接类型说明符(即指示一个类型的拼接)的通用情况保持一致,出于可用性考虑,你被允许在它之前放置 typename,即使它不是依赖的,也可以这样做。