在C++17标准之前,必须使用class来声明模板形参,而typename是不允许使用的,例如:
template <typename T> struct A {};
template <template <typename> class T> struct B {};
int main()
{
B<A> ba;
}上面的代码可以顺利地编译通过,但是如果将B的定义修改为template <template <typename> typename T> struct B {};,则可能会发生编译错误。具体情况要根据编译器厂商和版本而定,比如在GCC新版本中这种写法都是允许的,而CLang的新版本也只会给出一个警告,只有在它们的老版本中才会给出错误提示。总之,在C++17之前typename的这种写法是不符合标准的。
其实,这种严苛的规则在过去看来是顺理成章的。因为在过去,能作为模板形参的只有类模板,并没有其他可能性,所以规定必须使用class来声明模板形参是合情合理的。但是自从C++11标准诞生,随着别名模板的引入,类模板不再是模板形参的唯一选择了,例如:
template <typename T> using A = int;
template <template <typename> class T> struct B {};
int main()
{
B<A> ba;
}可以看到,这里的A实际上就是int类型而不是一个类模板。很明显,现在已经没有必要强调必须使用class来声明模板形参了,删除这个规则可以让语言更加简单合理。所以在C++17标准中使用typename来声明模板形参已经不是问题了:
template <typename T> using A = int;
template <template <typename> typename T> struct B {};
int main()
{
B<A> ba;
}我们知道当使用未决类型的内嵌类型时,例如X<T>::Y,需要使用typename明确告知编译器X<T>::Y是一个类型,否则编译器会将其当作一个表达式的名称,比如一个静态数据成员或者静态成员函数:
template<class T> void f(T::R);template<class T> void f(typename T::R);在C++20标准之前,只有两种情况例外,它们分别是指定基类和成员初始化,例如:
struct Impl {};
struct Wrap {
using B = Impl;
};
template<class T>
struct D : T::B {
D() : T::B() {}
};
D<Wrap> var;在上面的代码中struct D : T::B和D() : T::B() {}都没有指定typename,但是编译器依然可以正确地识别程序意图。实际上,除了以上两种情况外,还有很多时候也可以从语义中明确地判断出X<T>::Y表示的是类型,比如使用using创建类型别名的时候,using R = typename T::B;中typename完全没有存在的必要。
在C++20标准中,增加了一些情况可以让我们省略typename关键字。
1.在上下文仅可能是类型标识的情况,可以忽略typename。
static_cast、const_cast、reinterpret_cast或dynamic_cast等类型转换:
static_cast<T::B>(p);定义类型别名:
using R = T::B;后置返回类型:
auto g() -> T::B;模板类型形参的默认参数:
template <class R = T::B> struct X;2.还有一些声明的情况也可以忽略typename。
全局或者命名空间中简单的声明或者函数的定义:
template<class T> T::R f();结构体的成员:
template<class T>
struct D : T::B {
D() : T::B() {}
T::B b; // 编译成功
}; 作为成员函数或者lambda表达式形参声明:
template<class T>
struct D : T::B {
D() : T::B() {}
T::B f(T::B) { return T::B(); } // 编译成功
};最后需要提出的是,到目前为止实现了这部分特性的编译器只有GCC而已,至于CLang和MSVC编译以上代码依然会报错,并且提示需要添加typename。
本章的内容虽然比较简单,但是对于爱好模板元编程的读者来说却有一定意义。要知道模板元编程可以说是对类型的编程,所以在模板元编程的代码中总是会出现成堆的typename关键字,这些冗余的描述增加了无谓的代码量,非常影响代码的整洁。C++20标准减少typename声明的必要性无疑减轻了这种负担。允许使用typename声明模板形参也让模板声明体系显得更加合理了。