c++11-17 模板核心知识(十五)—— 解析模板之依赖型类型名称与typename
发布于 2021-01-27 23:46
模板名称的问题及解决 typename 规则 C++20 typename
上篇文章c++11-17 模板核心知识(十四)—— 解析模板之依赖型模板名称 Dependent Names of Templates(.template/->template/::template) 介绍了依赖型模板名称,提到关于模板解析有六个大方面:
非模板中的上下文相关性 Context Sensitivity in Nontemplates 依赖型类型名称 Dependent Names of Types <----- 依赖型模板名称 Dependent Names of Templates using-declaration 中的依赖型名称 Dependent Names in Using Declarations ADL 和显式模板实参 ADL and Explicit Template Arguments 依赖性表达式 Dependent Expressions
这篇文章介绍下依赖型类型名称(Dependent Names of Types)。
模板名称的问题及解决
模板中的名称存在一个问题:它们有的时候不能被很好的分类,比如一个模板引用其他模板的名称,因为模板特化的存在,会让问题变得复杂一些。例如:
template <typename T> class Trap {
public:
enum { x }; // #1 x is not a type here
};
template <typename T> class Victim {
public:
int y;
void poof() {
Trap<T>::x *y; // #2 declaration or multiplication?
}
};
template <> class Trap<void> { // evil specialization!
public:
using x = int; // #3 x is a type here
};
void boom(Victim<void> &bomb) { bomb.poof(); }
如果你直接编译,会报错:
main.cc:30:14: error: unexpected type name 'x': expected expression
Trap<T>::x *y; // #2 declaration or multiplication?
^
main.cc:39:38: note: in instantiation of member function 'Victim<void>::poof' requested here
void boom(Victim<void> &bomb) { bomb.poof(); }
^
1 error generated.
这个问题和解决方案在c++11-17 模板核心知识(二)—— 类模板涉及过,这篇文章再展开说一下相关规则。
回到上面的例子,当编译器解析到#2 处时,它需要决定Trap<T>::x
是一个类型还是一个值,这决定了Trap<T>::x *y
是声明一个指针还是做乘法。
问题是,在 Trap 中,Trap<T>::x
是一个值,但是在全特化版本Trap<void>
中,Trap<T>::x
是一个类型。所以,这种情况实际是依赖模板参数 T 的,也就是依赖型类型名称(Dependent Names of Types)。
C++规定,只有当加上 typename 关键字后,依赖型类型名称才会被当做类型,否则会被当做一个值。这里 typename 的意义和声明一个模板时使用的 typename 是两个意思,所以不能用 class 来替换 typename.
typename 规则
当一个名称具备以下性质时,需要在名称前面加 typename:
是 qualified name。 不是Elaborated type specifier[1]的一部分(例如,以 class、struct、union、enum 为开头的类型) 名称不是用于指定基类继承的列表中,也不是位于引入构造函数的成员初始化列表中。 依赖于模板参数。
例如:
template <typename(1) T>
struct S : typename(2) X<T>::Base {
S() : typename(3) X<T>::Base(typename(4) X<T>::Base(0)) {}
typename(5) X<T> f() {
typename(6) X<T>::C *p; // declaration of pointer p
X<T>::D *q; // multiplication!
}
typename(7) X<int>::C *s;
using Type = T;
using OtherType = typename(8) S<T>::Type;
};
下面逐一说下上面各个 typename 的使用场景(有的使用方式是错误的):
第一个 typename 代表一个模板参数,不在此文章讨论范围内。 第二和第三个 typename 是错误的使用方式,不需要添加,违反了上面的第 3 条规则。第二个出现在了指定基类继承的列表中,第三个出现在了构造函数的成员初始化列表。如果加上 typename 编译,会报如下错误:
main.cc:30:12: error: 'typename' is redundant; base classes are implicitly types
struct S : typename X<T>::Base {
^~~~~~~~~
第四个 typename 是必须的,它满足上面第 3 条规则,且其他规则也满足。 第五个 typename 是错误的,因为 X 不是一个 qualified name,如果加上 typename 编译,会报:
main.cc:33:12: error: expected a qualified name after 'typename'
typename X<T> f() {
^
第六个 typename 是必须的,上面讲过,代表一个类型。 第七个 typename 是可有可无的,因为 X<int>::C
不依赖模板参数,即不是 Dependent Name.第八个 typename 也是可有可无的,因为它指向的是 current instantiation
,这个概念下篇文章会讲到。
C++20 typename
是了,这一大堆乱七八糟的规则,谁也不想去记。C++20 对 typename 的规则做了一些改善,有一些场景不再需要 typename。详情大家可以参考 :The typename disambiguator for dependent names[2]
Elaborated type specifier: https://en.cppreference.com/w/cpp/language/elaborated_type_specifier
[2]The typename disambiguator for dependent names: https://en.cppreference.com/w/cpp/language/dependent_name#The_typename_disambiguator_for_dependent_names
本文来自网络或网友投稿,如有侵犯您的权益,请发邮件至:aisoutu@outlook.com 我们将第一时间删除。
相关素材