依赖名称
在模板(包括类模板和函数模板)的定义中,某些构造的含义可能因实例化而异。特别是,类型和表达式可能依赖于类型模板参数的类型和非类型模板参数的值。
template<typename T> struct X : B<T> // “B<T>” is dependent on T { typename T::A* pa; // “T::A” is dependent on T // (see below for the meaning of this use of “typename”) void f(B<T>* pb) { static int i = B<T>::i; // “B<T>::i” is dependent on T pb->j++; // “pb->j” is dependent on T } };
依赖名称和非依赖名称的名称查找和绑定是不同的。
目录 |
[编辑] 绑定规则
非依赖名称在模板定义时查找并绑定。即使在模板实例化时有更好的匹配,此绑定仍然有效。
如果非依赖名称的含义在定义上下文和模板特化实例化点之间发生变化,则程序格式错误,不需要诊断。这可能发生在以下情况:
- 在定义点,非依赖名称中使用的类型是不完整的,但在实例化点是完整的
|
(C++17 起) |
- 实例化使用一个在定义点尚未定义的默认参数或默认模板参数
- 在实例化点,一个常量表达式使用一个整数或无作用域枚举类型的 const 对象的值、一个 constexpr 对象的值、一个引用的值或一个 constexpr 函数的定义(C++11 起),并且该对象/引用/函数(C++11 起)在定义点尚未定义
- 模板在实例化点使用非依赖类模板特化或变量模板特化(C++14 起),并且它使用的此模板要么是从在定义点未定义的偏特化实例化而来,要么是命名在定义点未声明的显式特化
依赖名称的绑定推迟到查找发生时。
[编辑] 查找规则
模板中使用的依赖名称的查找推迟到模板参数已知时进行,此时:
- 非 ADL 查找检查从模板定义上下文可见的具有外部链接的函数声明
- ADL 检查从模板定义上下文或模板实例化上下文可见的具有外部链接的函数声明
(换句话说,在模板定义之后添加新的函数声明不会使其可见,除非通过 ADL)。
此规则的目的是帮助防止模板实例化违反ODR
// an external library namespace E { template<typename T> void writeObject(const T& t) { std::cout << "Value = " << t << '\n'; } } // translation unit 1: // Programmer 1 wants to allow E::writeObject to work with vector<int> namespace P1 { std::ostream& operator<<(std::ostream& os, const std::vector<int>& v) { for (int n : v) os << n << ' '; return os; } void doSomething() { std::vector<int> v; E::writeObject(v); // Error: will not find P1::operator<< } } // translation unit 2: // Programmer 2 wants to allow E::writeObject to work with vector<int> namespace P2 { std::ostream& operator<<(std::ostream& os, const std::vector<int>& v) { for (int n : v) os << n << ':'; return os << "[]"; } void doSomethingElse() { std::vector<int> v; E::writeObject(v); // Error: will not find P2::operator<< } }
在上述示例中,如果允许从实例化上下文进行 `operator<<` 的非 ADL 查找,则 E::writeObject<vector<int>> 的实例化将有两个不同的定义:一个使用 P1::operator<<,另一个使用 P2::operator<<。这种 ODR 违规可能不会被链接器检测到,导致在两种情况下都使用其中之一。
要使 ADL 检查用户定义的命名空间,std::vector 应该被用户定义的类替换,或者其元素类型应该是用户定义的类
namespace P1 { // if C is a class defined in the P1 namespace std::ostream& operator<<(std::ostream& os, const std::vector<C>& v) { for (C n : v) os << n; return os; } void doSomething() { std::vector<C> v; E::writeObject(v); // OK: instantiates writeObject(std::vector<P1::C>) // which finds P1::operator<< via ADL } }
注意:此规则使得为标准库类型重载运算符不切实际
#include <iostream> #include <iterator> #include <utility> #include <vector> // Bad idea: operator in global namespace, but its arguments are in std:: std::ostream& operator<<(std::ostream& os, std::pair<int, double> p) { return os << p.first << ',' << p.second; } int main() { typedef std::pair<int, double> elem_t; std::vector<elem_t> v(10); std::cout << v[0] << '\n'; // OK, ordinary lookup finds ::operator<< std::copy(v.begin(), v.end(), std::ostream_iterator<elem_t>(std::cout, " ")); // Error: both ordinary lookup from the point of definition of // std::ostream_iterator and ADL will only consider the std namespace, // and will find many overloads of std::operator<<, so the lookup will be done. // Overload resolution will then fail to find operator<< for elem_t // in the set found by the lookup. }
注意:依赖名称的有限查找(但不是绑定)也在模板定义时进行,以便将它们与非依赖名称区分开来,并确定它们是当前实例化的成员还是未知特化的成员。通过此查找获得的信息可用于检测错误,参见下文。
[编辑] 依赖类型
以下类型是*依赖类型*:
- 模板参数
- 未知特化的成员(见下文)
- 作为未知特化依赖成员的嵌套类/枚举(见下文)
- 依赖类型的 cv 限定版本
- 由依赖类型构造的复合类型
- 元素类型是依赖类型或界限(如果有)是值依赖的数组类型
|
(C++11 起) |
- 其异常规范是值依赖的函数类型
- 一个模板-id,其中:
- 模板名称是模板参数,或者
- 任何模板参数是类型依赖的,或值依赖的,或是包扩展(C++11 起)(即使模板-id 在没有其参数列表的情况下使用,作为注入类名)
decltype 应用于类型依赖表达式的结果是一个唯一的依赖类型。只有当它们的表达式等价时,两个这样的结果才指代同一类型。 |
(C++11 起) |
应用于类型依赖常量表达式的包索引说明符是一个唯一的依赖类型。只有当它们的常量表达式等价时,两个这样的包索引说明符才指代同一类型。否则,只有当它们的索引具有相同的值时,两个这样的包索引说明符才指代同一类型。 |
(C++26 起) |
注意:当前实例化的 typedef 成员只有在它所引用的类型是依赖类型时才是依赖类型。
[编辑] 类型依赖表达式
以下表达式是*类型依赖的*:
- 包含一个通过名称查找至少找到一个依赖声明的标识符
- 包含一个依赖的模板-id
|
(C++11 起) |
|
(C++14 起) |
|
(C++17 起) |
|
(C++26 起) |
- 任何到依赖类型的转换表达式
- 创建一个依赖类型对象的new 表达式
- 引用当前实例化中类型依赖的成员的成员访问表达式
- 引用未知特化成员的成员访问表达式
(C++17 起) |
|
(C++26 起) |
以下表达式永远不是类型依赖的,因为这些表达式的类型不能是:
(C++11 起) |
(C++20 起) |
[编辑] 值依赖表达式
以下表达式是*值依赖的*:
|
(C++20 起) |
- 它是类型依赖的。
- 它是一个非类型模板参数的名称。
- 它命名一个静态数据成员,该成员是当前实例化的依赖成员且未初始化。
- 它命名一个静态成员函数,该函数是当前实例化的依赖成员。
- 它是一个带有整数或枚举(C++11 前)字面量(C++11 起)类型的常量,从值依赖表达式初始化。
- 以下表达式,其中操作数是类型依赖表达式
(C++11 起) |
- 以下表达式,其中操作数是依赖类型 ID
- 以下表达式,其中目标类型是依赖类型或操作数是类型依赖表达式
- 函数式转换表达式,其中目标类型是依赖类型或值依赖表达式被括号或花括号(C++11 起)包围
(C++11 起) | |
(C++17 起) |
- 取地址表达式,其中参数是命名当前实例化的依赖成员的限定标识符
- 取地址表达式,其中参数是任何表达式,该表达式作为核心常量表达式求值时,引用具有静态或线程存储(C++11 起)持续时间的对象或成员函数模板化实体。
[编辑] 依赖名称
本节不完整 原因:[temp.dep] 中的引导语,缺少(标识符表达式后跟括号列表…… |
本节不完整 原因:重新措辞,使其更清晰(或至少不那么吓人),并同时应用 CWG 问题 591 |
[编辑] 当前实例化
在类模板定义(包括其成员函数和嵌套类)中,某些名称可能被推断为指向*当前实例化*。这允许在定义点而非实例化点检测到某些错误,并消除了对依赖名称的 typename 和 template 消歧义符的要求,见下文。
只有以下名称可以指向当前实例化:
- 在类模板、类模板的嵌套类、类模板的成员或类模板的嵌套类的成员的定义中
- 类模板或嵌套类的注入类名
- 在主类模板或主类模板成员的定义中
- 类模板的名称后跟主模板的模板参数列表(或等效的别名模板特化),其中每个参数都与其对应的参数等效(定义如下)。
- 在类模板的嵌套类的定义中
- 嵌套类的名称用作当前实例化的成员
- 在类模板偏特化或类模板偏特化成员的定义中
- 类模板的名称后跟偏特化的模板参数列表,其中每个参数都与其对应的参数等效
- 在模板化函数的定义中
- 局部类的名称
如果一个模板参数等价于一个模板参数,则:
- 它与模板参数具有相同的类型(忽略 cv 限定符)并且
- 其初始化器由一个单独的标识符组成,该标识符命名模板参数或递归地命名此类变量。
template<class T> class A { A* p1; // A is the current instantiation A<T>* p2; // A<T> is the current instantiation ::A<T>* p4; // ::A<T> is the current instantiation A<T*> p3; // A<T*> is not the current instantiation class B { B* p1; // B is the current instantiation A<T>::B* p2; // A<T>::B is the current instantiation typename A<T*>::B* p3; // A<T*>::B is not the current instantiation }; }; template<class T> class A<T*> { A<T*>* p1; // A<T*> is the current instantiation A<T>* p2; // A<T> is not the current instantiation }; template<int I> struct B { static const int my_I = I; static const int my_I2 = I + 0; static const int my_I3 = my_I; static const long my_I4 = I; static const int my_I5 = (I); B<my_I>* b1; // B<my_I> is the current instantiation: // my_I has the same type as I, // and it is initialized with only I B<my_I2>* b2; // B<my_I2> is not the current instantiation: // I + 0 is not a single identifier B<my_I3>* b3; // B<my_I3> is the current instantiation: // my_I3 has the same type as I, // and it is initialized with only my_I (which is equivalent to I) B<my_I4>* b4; // B<my_I4> is not the current instantiation: // the type of my_I4 (long) is not the same as the type of I (int) B<my_I5>* b5; // B<my_I5> is not the current instantiation: // (I) is not a single identifier };
请注意,如果嵌套类派生自其封闭类模板,则基类可以是当前实例化。作为依赖类型但不是当前实例化的基类是*依赖基类*
template<class T> struct A { typedef int M; struct B { typedef void M; struct C; }; }; template<class T> struct A<T>::B::C : A<T> { M m; // OK, A<T>::M };
如果一个名称是以下情况之一,则将其归类为当前实例化的成员:
- 通过非限定查找在当前实例化或其非依赖基类中找到的非限定名称。
- 限定名称,如果限定符(`::` 左侧的名称)命名当前实例化,并且查找在当前实例化或其非依赖基类中找到该名称
- 在类成员访问表达式(x.y 中的 y 或 xp->y 中的 y)中使用的名称,其中对象表达式(x 或 *xp)是当前实例化,并且查找在当前实例化或其非依赖基类中找到该名称
template<class T> class A { static const int i = 5; int n1[i]; // i refers to a member of the current instantiation int n2[A::i]; // A::i refers to a member of the current instantiation int n3[A<T>::i]; // A<T>::i refers to a member of the current instantiation int f(); }; template<class T> int A<T>::f() { return i; // i refers to a member of the current instantiation }
当前实例化的成员可以是依赖的,也可以是非依赖的。
如果当前实例化的成员查找在实例化点和定义点之间给出不同的结果,则查找是模糊的。但是请注意,当使用成员名称时,它不会自动转换为类成员访问表达式,只有显式成员访问表达式才指示当前实例化的成员
struct A { int m; }; struct B { int m; }; template<typename T> struct C : A, T { int f() { return this->m; } // finds A::m in the template definition context int g() { return m; } // finds A::m in the template definition context }; template int C<B>::f(); // error: finds both A::m and B::m template int C<B>::g(); // OK: transformation to class member access syntax // does not occur in the template definition context
[编辑] 未知特化
在模板定义中,某些名称被推断为属于*未知特化*,特别是:
- 一个限定名称,如果 `::` 左侧出现的任何名称是一个不是当前实例化成员的依赖类型
- 一个限定名称,其限定符是当前实例化,并且该名称在当前实例化或其任何非依赖基类中都未找到,并且存在一个依赖基类
- 类成员访问表达式中的成员名称(x.y 中的 y 或 xp->y 中的 y),如果对象表达式(x 或 *xp)的类型是依赖类型且不是当前实例化
- 类成员访问表达式中的成员名称(x.y 中的 y 或 xp->y 中的 y),如果对象表达式(x 或 *xp)的类型是当前实例化,并且该名称在当前实例化或其任何非依赖基类中都未找到,并且存在一个依赖基类
template<typename T> struct Base {}; template<typename T> struct Derived : Base<T> { void f() { // Derived<T> refers to current instantiation // there is no “unknown_type” in the current instantiation // but there is a dependent base (Base<T>) // Therefore, “unknown_type” is a member of unknown specialization typename Derived<T>::unknown_type z; } }; template<> struct Base<int> // this specialization provides it { typedef int unknown_type; };
这种分类允许在模板定义点(而非实例化点)检测到以下错误:
- 如果任何模板定义中存在一个限定名称,其中限定符指向当前实例化,并且该名称既不是当前实例化的成员,也不是未知特化的成员,则程序格式错误(不需要诊断),即使该模板从未实例化。
template<class T> class A { typedef int type; void f() { A<T>::type i; // OK: “type” is a member of the current instantiation typename A<T>::other j; // Error: // “other” is not a member of the current instantiation // and it is not a member of an unknown specialization // because A<T> (which names the current instantiation), // has no dependent bases for “other” to hide in. } };
- 如果任何模板定义中存在成员访问表达式,其中对象表达式是当前实例化,但该名称既不是当前实例化的成员,也不是未知特化的成员,则程序格式错误,即使该模板从未实例化。
未知特化的成员始终是依赖的,并且像所有依赖名称一样在实例化点进行查找和绑定(见上文)
[编辑] 用于依赖名称的 typename 消歧义符
在模板(包括别名模板)的声明或定义中,不是当前实例化成员且依赖于模板参数的名称不被视为类型,除非使用关键词 typename,或者它已被确立为类型名称,例如通过 typedef 声明或用于命名基类。
#include <iostream> #include <vector> int p = 1; template<typename T> void foo(const std::vector<T> &v) { // std::vector<T>::const_iterator is a dependent name, typename std::vector<T>::const_iterator it = v.begin(); // without “typename”, the following is parsed as multiplication // of the type-dependent data member “const_iterator” // and some variable “p”. Since there is a global “p” visible // at this point, this template definition compiles. std::vector<T>::const_iterator* p; typedef typename std::vector<T>::const_iterator iter_t; iter_t * p2; // “iter_t” is a dependent name, but it is known to be a type name } template<typename T> struct S { typedef int value_t; // member of current instantiation void f() { S<T>::value_t n{}; // S<T> is dependent, but “typename” not needed std::cout << n << '\n'; } }; int main() { std::vector<int> v; foo(v); // template instantiation fails: there is no member variable // called “const_iterator” in the type std::vector<int> S<int>().f(); }
关键词 typename 只能以这种方式用于限定名称之前(例如 T::x),但名称不必是依赖的。
对于以 typename 为前缀的标识符,使用通常的限定名称查找。与阐明类型说明符的情况不同,查找规则不会因限定符而改变。
struct A // A has a nested variable X and a nested type struct X { struct X {}; int X; }; struct B { struct X {}; // B has a nested type struct X }; template<class T> void f(T t) { typename T::X x; } void foo() { A a; B b; f(b); // OK: instantiates f<B>, T::X refers to B::X f(a); // error: cannot instantiate f<A>: // because qualified name lookup for A::X finds the data member }
关键词 typename 甚至可以在模板外部使用。
#include <vector> int main() { // Both OK (after resolving CWG 382) typedef typename std::vector<int>::const_iterator iter_t; typename std::vector<int> v; }
在某些上下文中,只有类型名称可以有效出现。在这些上下文中,依赖的限定名称被假定为命名一个类型,并且不需要 typename:
|
(C++20 起) |
[编辑] 用于依赖名称的 template 消歧义符
类似地,在模板定义中,不是当前实例化成员的依赖名称不被视为模板名称,除非使用消歧义关键词 template,或者它已被确立为模板名称。
template<typename T> struct S { template<typename U> void foo() {} }; template<typename T> void bar() { S<T> s; s.foo<T>(); // error: < parsed as less than operator s.template foo<T>(); // OK }
关键词 template 只能在运算符 :: (作用域解析)、-> (通过指针的成员访问) 和 . (成员访问) 之后以这种方式使用,以下都是有效的示例:
- T::template foo<X>();
- s.template foo<X>();
- this->template foo<X>();
- typename T::template iterator<int>::value_type v;
与 typename 的情况一样,即使名称不是依赖的或者使用不在模板的作用域中,也允许使用 template 前缀。
即使 `::` 左侧的名称引用命名空间,也允许使用模板消歧义符。
template<typename> struct S {}; ::template S<void> q; // allowed, but unnecessary
由于成员访问表达式中模板名称的非限定名称查找的特殊规则,当非依赖模板名称出现在成员访问表达式中(在 -> 或 . 之后),如果通过表达式上下文中的普通查找找到具有相同名称的类或别名(C++11 起)模板,则不需要消歧义符。但是,如果在表达式上下文中找到的模板与在类上下文中找到的模板不同,则程序格式错误。(C++11 前) template<int> struct A { int value; }; template<class T> void f(T t) { t.A<0>::value; // Ordinary lookup of A finds a class template. // A<0>::value names member of class A<0> // t.A < 0; // Error: “<” is treated as the start of template argument list } |
(直至 C++23) |
[编辑] 关键词
[编辑] 缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
缺陷报告 | 应用于 | 发布时的行为 | 正确的行为 |
---|---|---|---|
CWG 206 | C++98 | 未指定在何时应用语义约束 当非依赖名称中使用的类型 在模板定义时是不完整的,但在 执行实例化时是完整的 |
程序格式错误 并且不需要诊断 在此情况下 |
CWG 224 | C++98 | 依赖类型的定义是基于 名称的形式而不是查找 |
定义已修改 |
CWG 382 | C++98 | typename 消歧义符只允许在模板作用域内使用 | 也允许在模板之外使用 的模板 |
CWG 468 | C++98 | template 消歧义符只允许在模板作用域内使用 | 也允许在模板之外使用 的模板 |
CWG 502 | C++98 | 未指定嵌套枚举是否为依赖类型 | 作为嵌套类依赖 |
CWG 1047 | C++98 | typeid 表达式从未是值依赖的 | 如果操作数是类型依赖的,则是值依赖的 操作数是类型依赖的 |
CWG 1160 | C++98 | 未指定名称是否引用当前实例化 当匹配主模板或部分特化的模板-id 出现在模板成员的定义中时 特化出现在模板成员的定义中 |
已指定 |
CWG 1413 | C++98 | 未初始化的静态数据成员、静态成员函数和地址 类模板的成员没有被列为值依赖的 |
已列出 |
CWG 1471 | C++98 | 当前实例化的非依赖基类的嵌套类型是依赖的 的当前实例化是依赖的 |
它不是依赖的 |
CWG 1850 | C++98 | 定义上下文和实例化点之间可能改变含义的情况列表不完整 定义上下文和实例化点之间可能改变含义的情况不完整 |
已完成 |
CWG 1929 | C++98 | 不清楚 template 消歧义符是否可以跟随一个 `::`,当其左侧的名称引用命名空间时 跟随一个 `::`,当其左侧的名称引用命名空间时 |
允许 |
CWG 2066 | C++98 | this 从未是值依赖的 | 它可能是值依赖的 值依赖的 |
CWG 2100 | C++98 | 类模板的静态数据成员的地址 类模板的静态数据成员未被列为值依赖的 |
已列出 |
CWG 2109 | C++98 | 类型依赖标识符表达式可能不是值依赖的 | 它们总是值依赖的 值依赖的 |
CWG 2276 | C++98 | 其异常规范是值依赖的函数类型 是值依赖的函数类型不是依赖类型 |
它是 |
CWG 2307 | C++98 | 用作模板参数的带括号的非类型模板参数等效于该模板参数 模板参数 |
不再等效 |
CWG 2457 | C++11 | 带有函数参数包的函数类型不是依赖类型 包不是依赖类型 |
它是 |
CWG 2785 | C++20 | requires 表达式可能是类型依赖的 | 它们从不是类型依赖的 类型依赖的 |
CWG 2905 | C++11 | noexcept 表达式仅在操作数为值依赖时是值依赖的 如果其操作数是值依赖的 |
它是值依赖的 如果其操作数涉及 模板参数 |
CWG 2936 | C++98 | 模板化函数中的局部类的名称不属于当前实例化 函数不属于当前实例化 |
它们是 |