依存名
テンプレート(クラステンプレートと関数テンプレートの両方)の定義内では、一部の構文の意味がインスタンス化ごとに異なる場合があります。特に、型と式は、型テンプレートパラメーターの型と非型テンプレートパラメーターの値に依存する場合があります。
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
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を型依存式に適用した結果は、一意の依存型となります。そのような2つの結果が同じ型を参照するのは、それらの式が同等である場合に限られます。 |
(C++11以降) |
型依存定数式に適用されるパックインデックス指定子は、一意の依存型です。そのような2つのパックインデックス指定子が同じ型を参照するのは、それらの定数式が同等である場合に限られます。それ以外の場合、そのような2つのパックインデックス指定子が同じ型を参照するのは、それらのインデックスが同じ値を持つ場合に限られます。 |
(C++26以降) |
注意:現在のインスタンス化の`typedef`メンバーは、それが参照する型が依存している場合にのみ依存的になります。
[編集] 型依存式
以下の式は型依存です。
- 名前ルックアップによって少なくとも1つの依存宣言が見つかる識別子を含む場合
- 依存するテンプレート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 issue 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++標準に遡って適用されました。
| DR | 適用対象 | 公開された動作 | 正しい動作 |
|---|---|---|---|
| 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 | 型依存識別子式は値依存ではないかもしれない | 常に値依存である 値依存である可能性がある |
| 常に値依存である | C++98 | CWG 2276 例外指定が値依存である関数型は依存型ではなかった |
そうである |
| 例外指定が値依存である関数型は依存型ではなかった | C++98 | CWG 2307 テンプレート引数として使用される括弧で囲まれた非型テンプレートパラメーターは、そのテンプレートパラメーターと同等であった |
もはや同等ではない |
| CWG 2457 | C++11 | 関数パラメーターパックを持つ関数型は依存型ではなかった 関数パラメーターパックを持つ関数型は依存型ではなかった |
そうである |
| CWG 2785 | C++20 | requires式は型依存である可能性がある | 決して型依存ではない 決して型依存ではない |
| CWG 2905 | C++11 | noexcept式は、そのオペランドが値依存である場合にのみ値依存であった そのオペランドが値依存である場合にのみ値依存であった |
そのオペランドが テンプレートパラメーターを含む場合、値依存である テンプレートパラメーターを含む場合、値依存である |
| CWG 2936 | C++98 | テンプレート化された関数のローカルクラスの名前は現在のインスタンス化の一部ではなかった テンプレート化された関数のローカルクラスの名前は現在のインスタンス化の一部ではなかった |
現在は |