依賴名稱
在模板(包括類模板和函式模板)的定義中,某些構造的含義可能因例項化而異。特別是,型別和表示式可能依賴於型別模板引數的型別和非型別模板引數的值。
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 | 模板化函式中的區域性類的名稱不屬於當前例項化 函式不屬於當前例項化 |
它們是 |