依存名
テンプレート (クラステンプレートと関数テンプレートの両方) の定義内では、一部の要素の意味がある実体化と別の実体化で異なることがあります。 特に、型や式が型テンプレート引数の型や非型テンプレート引数の値に依存することがあります。
template<typename T>struct X : B<T>// 「B<T>」は T に依存しています。{typename T::A* pa;// 「T::A」は T に依存しています。// (この「typename」の使用方法については後述)void f(B<T>* pb){staticint i = B<T>::i;// 「B<T>::i」は T に依存しています。 pb->j++;// 「pb->j」は T に依存しています。}};
依存名の場合と非依存名の場合で名前探索および束縛が異なります。
目次 |
[編集]束縛ルール
非依存名はテンプレート定義の時点で名前探索され束縛されます。 たとえテンプレートの実体化の時点でより良いマッチがあったとしても、この束縛は維持されます。
定義の文脈とテンプレートの特殊化の実体化の時点で非依存名の意味が変わる場合、プログラムは ill-formed です (診断は要求されません)。 これは以下の状況で発生し得ます。
- 非依存名で使用された型が、定義の時点では不完全であるが、実体化の時点では完全である。
- (C++17) 実体化が、定義の時点では定義されていないデフォルト引数またはデフォルトテンプレート引数を使用する。
- (C++17) 実体化の時点での定数式が、整数型またはスコープなし列挙型の const オブジェクトの値、 constexpr オブジェクトの値、参照の値、 constexpr 関数の定義を使用し、そのオブジェクト、参照、関数が定義の時点で定義されていなかった。
- (C++17) テンプレートが実体化の時点で非依存クラステンプレートの特殊化または変数テンプレートの特殊化を使用し、それが使用するこのテンプレートが、定義の時点で定義されていなかった部分特殊化から実体化されるか、定義の時点で宣言されていなかった明示的特殊化を表すかの、いずれかである。
依存名の束縛は名前探索が行われるまで遅延されます。
[編集]名前探索のルール
名前探索で議論した通り、テンプレートで使用される依存名の名前探索はテンプレートの実引数が判明するまで遅延されます。 これは以下の時点になります。
- 非 ADL の名前探索がテンプレート定義の文脈から可視な外部リンケージを持つ関数宣言を調べるとき。
- ADL がテンプレート定義の文脈またはテンプレート実体化の文脈のいずれかから可視な外部リンケージを持つ関数宣言を調べるとき。
(別の言い方をすると、テンプレート定義後に新しい関数宣言を追加しても、 ADL を除いては、可視になりません。)
このルールの目的は、テンプレートを実体化する際の ODR の違反に対する保護を補助することです。
// 外部のライブラリnamespace E {template<typename T>void writeObject(const T& t){std::cout<<"Value = "<< t <<'\n';}} // 翻訳単位 1:// プログラマAは E::writeObject で vector<int> を使えるようにしたいです。namespace P1 {std::ostream& operator<<(std::ostream& os, conststd::vector<int>& v){for(int n: v) os << n <<' ';return os;}void doSomething(){std::vector<int> v; E::writeObject(v);// エラー。 P1::operator<< を発見できません。}} // 翻訳単位 2:// プログラマBは E::writeObject で vector<int> を使えるようにしたいです。namespace P2 {std::ostream& operator<<(std::ostream& os, conststd::vector<int>& v){for(int n: v) os << n <<':';return os <<"[]";}void doSomethingElse(){std::vector<int> v; E::writeObject(v);// エラー。 P2::operator<< を発見できません。}}
上記の例で、もし実体化の文脈から operator<< に対する非 ADL 名前探索が可能であったならば、 E::writeObject<vector<int>> の実体化は2つの異なる定義 (P1::operator<< を使用するものと P2::operator<< を使用するもの) を持つこととなったことでしょう。 そのような ODR 違反はリンカによって検出されないかもしれず、どちらの実体化が使用されるかわからない状態を招きます。
ADL にユーザ定義名前空間を調べさせるには、 std::vector
をユーザ定義クラスに置き換えるか、その要素型をユーザ定義クラスにするかの、いずれかです。
namespace P1 {// C が名前空間 P1 で定義されたクラスであれば、std::ostream& operator<<(std::ostream& os, conststd::vector<C>& v){for(C n: v) os << n;return os;}void doSomething(){std::vector<C> v; E::writeObject(v);// OK。 writeObject(std::vector<P1::C>) を実体化し、// ADL によって P1::operator<< を発見します。}}
ノート: このルールは標準ライブラリの型に対する演算子のオーバーロードを非実用的にします。
#include <iostream>#include <vector>#include <iterator>#include <utility> // 悪いアイディア: std 名前空間の型を引数に取るグローバル名前空間の演算子。std::ostream& operator<<(std::ostream& os, std::pair<int, double> p){return os << p.first<<','<< p.second;} int main(){typedefstd::pair<int, double> elem_t;std::vector<elem_t> v(10);std::cout<< v[0]<<'\n';// OK。 通常の名前探索により ::operator<< が発見されます。std::copy(v.begin(), v.end(), std::ostream_iterator<elem_t>(std::cout, " "));// エラー。// std::ostream_iterator の定義時点からの通常の名前探索と ADL はどちらも// std 名前空間のみを考慮し、多数の std::operator<< のオーバーロードを発見し、// それで名前探索は終わります。 オーバーロード解決は名前探索によって発見された集合内に// elem_t に対する operator<< を発見できません。}
ノート: 依存名の制限付きの名前探索 (束縛ではない) は、それらを非依存名と区別するために、およびそれらが現在の実体化のメンバであるか未知の特殊化のメンバであるかを判断するために、必要に応じてテンプレート定義時にも行われます。 この名前探索によって得られた情報は、エラーを検出するために使用されることがあります (後述)。
[編集]依存型
以下の型は依存型です。
- テンプレート仮引数
- 未知の特殊化のメンバ (後述)
- 未知の特殊化の依存メンバであるネストしたクラスまたは列挙 (後述)
- 依存型の cv 修飾されたバージョン
- 依存型から構築された複合型
- 要素が依存型であるか、境界 (もしあれば) が値依存である、配列型
| (C++17以上) |
- 以下のいずれかであるテンプレート識別子
- テンプレート名がテンプレート仮引数である。または、
- テンプレート実引数のいずれかが型依存、値依存、またはパック展開である (たとえそのテンプレート識別子が実引数リストなしで (注入されたクラス名として) 使用された場合でも)。
- 型依存の式に適用された decltype の結果
ノート: 現在の実体化の typedef メンバは、その参照先の型が依存である場合にのみ、依存です。
[編集]型依存な式
以下の式は型依存です。
- 型依存の式を部分式として含む式
- this (そのクラスが依存型である場合)
- 以下のような識別子式
| (C++14以上) |
| (C++17以上) |
- 依存型へのキャスト式
- 依存型のオブジェクトを作成する new 式
- 依存型の現在の実体化のメンバを参照するメンバアクセス式
- 未知の特殊化のメンバを参照するメンバアクセス式
(C++17以上) |
ノート: リテラル、擬似デストラクタ呼び出し、 sizeof、 alignof、 typeid、 delete 式、 throw 式、および noexcept 式は、それらの型が型依存となることはできないため、型依存となることはありません。
[編集]値依存な式
- 型依存である
- 非型テンプレート仮引数の名前である
| (C++14以上) |
- 値依存な式から初期化されたリテラル型の定数である
| (C++14以上) |
(C++17以上) |
[編集]依存名
This section is incomplete Reason: the lede from [temp.dep], which is missing (id-expression followed by parenthesized list... |
This section is incomplete Reason: reword to maybe make it more clear (or at least less intimidating), and while at ait, apply CWG issue 591 |
[編集]現在の実体化
クラステンプレートの定義内 (そのメンバ関数およびネストしたクラスを含みます) では、いくつかの名前は現在の実体化を参照すると推定されることがあります。 これにより一部のエラーを実体化時ではなく定義時に検出することが可能となり、依存名に対する typename
および template
曖昧性解消子の必要性を取り除きます (後述)。
現在の実体化を参照できるのは以下の名前のみです。
- クラステンプレートの定義内では、
- ネストしたクラス、クラステンプレートのメンバ、ネストしたクラスのメンバ、そのテンプレートの注入されたクラス名、ネストしたクラスの注入されたクラス名。
- プライマリクラステンプレートの定義内またはそのメンバの定義内では、
- 各実引数が対応する仮引数と同等である場合の、プライマリテンプレートに対するテンプレート実引数リストが後続する、クラステンプレートの名前 (または同等なエイリアステンプレートの特殊化)。 非型テンプレート引数として式が使用される (N が仮引数であるときの N+0 など) 場合は、たとえその値がマッチしていても、それは現在の実体化を表さないことに注意してください。
- ネストしたクラスまたはクラステンプレートの定義では、
- 現在の実体化のメンバとして使用されたネストしたクラスの名前
- 部分特殊化の定義または部分特殊化のメンバの定義では、
- 各実引数が対応する仮引数と同等である場合の、その部分特殊化に対するテンプレート実引数が後続するクラステンプレートの名前
template<class T>class A { A* p1;// A は現在の実体化です。 A<T>* p2;// A<T> は現在の実体化です。::A<T>* p4;// ::A<T> は現在の実体化です。 A<T*> p3;// A<T*> は現在の実体化ではありません。class B { B* p1;// B は現在の実体化です。 A<T>::B* p2;// A<T>::B は現在の実体化です。typename A<T*>::B* p3;// A<T*>::B は現在の実体化ではありません。};};template<class T>class A<T*>{ A<T*>* p1;// A<T*> は現在の実体化です。 A<T>* p2;// A<T> は現在の実体化ではありません。};template<int I>struct B {staticconstint my_I = I;staticconstint my_I2 = I+0;staticconstint my_I3 = my_I; B<my_I>* b3;// B<my_I> は現在の実体化です。 B<my_I2>* b4;// B<my_I2> は現在の実体化ではありません。 B<my_I3>* b5;// B<my_I3> は現在の実体化です。};
ネストしたクラスが、その囲っているクラステンプレートから派生している場合は、基底クラスが現在の実体化になり得ることに注意してください。 依存型だけれども現在の実体化ではない基底クラスは依存基底クラスです。
template<class T>struct A {typedefint M;struct B {typedefvoid M;struct C;};};template<class T>struct A<T>::B::C: A<T>{ M m;// OK、 A<T>::M です。};
以下の場合、その名前は現在の実体化のメンバとして分類されます。
- 現在の実体化またはその非依存な基底で非修飾名の名前探索によって発見される非修飾名。
- 修飾名で、その修飾 (
::
の左の名前) が現在の実体化を表し、名前探索によって現在の実体化またはその非依存な基底内の名前が発見される場合。 - クラスメンバアクセス式で使用される名前 (
x.y
やxp->y
のy
) で、そのオブジェクト式 (x
または*xp
) が現在の実体化であり、名前探索によって現在の実体化またはその非依存な基底内の名前が発見される場合。
template<class T>class A {staticconstint i =5;int n1[i];// i は現在の実体化のメンバを参照します。int n2[A::i];// A::i は現在の実体化のメンバを参照します。int n3[A<T>::i];// A<T>::i は現在の実体化のメンバを参照します。int f();};template<class T>int A<T>::f(){return i;// i は現在の実体化のメンバを参照します。}
現在の実体化のメンバは、依存であることも非依存であることもあります。
現在の実体化のメンバの名前探索が実体化時と定義時で異なる結果となる場合、その名前探索は曖昧です。 しかし、メンバ名が使用されたときに、それがクラスメンバアクセス式に自動的に変換されるわけではないことに注意してください。 現在の実体化のメンバを表すのは明示的なメンバアクセス式だけです。
struct A {int m;};struct B {int m;}; template<typename T>struct C : A, T {int f(){return this->m;}// テンプレート定義時の文脈では A::m を発見します。int g(){return m;}// テンプレート定義時の文脈では A::m を発見します。}; templateint C<B>::f();// エラー、 A::m と B::m の両方を発見します。 templateint C<B>::g();// OK、テンプレート定義時の文脈で// クラスメンバアクセス構文への変換は発生しません。
[編集]未知の特殊化
テンプレート定義内では、一部の名前は未知の特殊化に属すると推定されます。 特に、
- 修飾名で、
::
の左に現れる名前のいずれかが現在の実体化のメンバでない依存型である場合。 - 修飾名で、その修飾が現在の実体化であり、その名前が現在の実体化内にもその非依存基底クラス内にも見つからず、依存基底クラスがある場合。
- クラスメンバアクセス式のメンバの名前 (
x.y
やxp->y
のy
) で、そのオブジェクト式 (x
または*xp
) の型が現在の実体化でない依存型である場合。 - クラスメンバアクセス式のメンバの名前 (
x.y
やxp->y
のy
) で、そのオブジェクト式 (x
または*xp
) の型が現在の実体化であり、その名前が現在の実体化内にもそも非依存基底クラス内にも見つからず、依存基底クラスがある場合。
template<typename T>struct Base {}; template<typename T>struct Derived : Base<T>{void f(){// Derived<T> は現在の実体化を参照します。// 現在の実体化に unknown_type はありませんが、// 依存基底 (Base<T>) があるため、// unknown_type は未知の特殊化のメンバです。typename Derived<T>::unknown_type z;}}; template<>struct Base<int>{// この特殊化がそれを提供します。typedefint unknown_type;};
この分類により、以下のエラーをテンプレートの定義時 (実体化時ではなく) に検出することが可能となります。
- テンプレートの定義に修飾名があり、その修飾が現在の実体化を参照し、その名前が現在の実体化でも未知の特殊化のメンバでもない場合は、たとえそのテンプレートが実体化されなくても、プログラムは ill-formed です (診断は要求されません)。
template<class T>class A {typedefint type;void f(){ A<T>::type i;// OK。「type」は現在の実体化のメンバです。typename A<T>::other j;// エラー。// 「other」は現在の実体化のメンバではなく、// 未知の特殊化のメンバでもありません。// (A<T> (現在の実体化) には「other」が// 隠れているかもしれない依存基底がありません。)}};
- テンプレートの定義にメンバアクセス式があり、そのオブジェクト式が現在の実体化だけれども、その名前が現在の実体化のメンバでも未知の特殊化のメンバでもない場合は、たとえそのテンプレートが実体化されなくても、プログラムは ill-formed です。
未知の特殊化のメンバは常に依存であり、他の依存名と同様 (上を参照)、実体化時に名前探索され束縛されます。
[編集]依存名に対する typename 曖昧性解消子
テンプレートの宣言または定義において (エイリアステンプレートを含みます)、テンプレート仮引数に依存する現在の実体化のメンバでない名前は、キーワード typename を使用するか、すでに型名として確立されている (例えば typedef 宣言や、基底クラスを表すために使用されているなどによって) のでない限り、型であるとみなされません。
#include <iostream>#include <vector> int p =1;template<typename T>void foo(conststd::vector<T>&v){ // std::vector<T>::const_iterator は依存名です。typenamestd::vector<T>::const_iterator it = v.begin(); // 「typename」がなければ、以下の文は// 型依存のメンバ変数「const_iterator」と何らかの変数「p」の// 乗算として解析されます。 グローバル変数「p」がここから// 可視であるため、このテンプレート定義はコンパイルできます。std::vector<T>::const_iterator* p; typedeftypenamestd::vector<T>::const_iterator iter_t; iter_t * p2;// iter_t は依存名ですが、型名であると判明しています。} template<typename T>struct S {typedefint value_t;// 現在の実体化のメンバvoid f(){ S<T>::value_t n{};// S<T> は依存ですが、「typename」は不要です。std::cout<< n <<'\n';}}; int main(){std::vector<int> v; foo(v);// テンプレートの実体化に失敗します。 型 std::vector<int> に// 「const_iterator」という名前のメンバ変数はありません。 S<int>().f();}
キーワード typename は、この方法でのみ、修飾名 (T::x など) の前で使用できますが、その名前が依存である必要はありません。
typename
を前置した識別子に対しては通常の修飾名の名前探索が使用されます。 複雑型指定子の場合と異なり、修飾があっても名前探索のルールは変わりません。
struct A {// A にはネストした変数 X とネストした型 struct X があります。struct X {};int X;};struct B {struct X {};// B にはネストした型 struct X があります。};template<class T>void f(T t){typename T::X x;}void foo(){ A a; B b; f(b);// OK、 f<B> を実体化します。 T::X は B::X を参照します。 f(a);// エラー、 f<A> を実体化できません。// A::X に対する修飾名の名前探索はデータメンバを発見します。}
キーワード typename は、テンプレートの宣言と定義、および依存名が使用可能な文脈でのみ、使用できます。 これには明示的特殊化の宣言や明示的実体化の宣言は含まれません。 | (C++11未満) |
キーワード typename は、たとえテンプレートの外側でも、使用できます。 #include <vector> int main(){typedeftypenamestd::vector<int>::const_iterator iter_t;// C++11 では OK。typenamestd::vector<int> v;// これも C++11 では OK。} | (C++11以上) |
一部の文脈では、型名のみが有効に現れることができます。 それらの文脈では、依存修飾名は型を表すと仮定され、 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>();// エラー、 < は小なり演算子として解析されます。 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 接頭辞は、たとえ名前が依存でないまたは使用がテンプレートのスコープ内でない(C++11以上)場合でも、使用できます。
たとえ template<typename>struct s {};::template s<void> q;// 使用できますが、不要です。 | (C++17以上) |
メンバアクセス式におけるテンプレート名に対する非修飾名の名前探索の特別なルールのため、非依存テンプレート名がメンバアクセス式 (-> または . の後) に現れるときは、その式の文脈における通常の名前探索によって発見される同じ名前のクラスまたはエイリアス(C++11以上)テンプレートが存在するならば、曖昧性解消子は不要です。 しかし、式の文脈における名前探索によって発見されるテンプレートがクラスの文脈で発見されるものと異なる場合は、プログラムは ill-formed です。(C++11未満)
template<int>struct A {int value;}; template<class T>void f(T t){ t.A<0>::value;// A の通常の名前探索はクラステンプレートを発見します。 A<0>::value はクラス A<0> のメンバを表します。// t.A < 0; // エラー。 「<」はテンプレート引数リストの始まりとして扱われます。}
[編集]欠陥報告
以下の動作変更欠陥報告は以前に発行された C++ 標準に遡って適用されました。
DR | 適用先 | 発行時の動作 | 正しい動作 |
---|---|---|---|
CWG 2100 | C++14 | address of a static data member of class template wasn't listed as value-dependent | listed |