関数テンプレート宣言の順序は可視性に影響する場合があります
-
03-07-2019 - |
質問
関数を作成しようとしています:
template <typename T>
void doIt( T*& p )
{
if ( !p ) { return; }
T& ref = *p;
getClassName( ref );
}
ここで、渡されるp
のタイプによって動作が異なります。特に、呼び出されるgetClassName
のバージョンは、getClassName( std::vector<T,A>& )
のタイプによって異なります。次の例では、正常に呼び出すことができます:
doIt<myClass1>( myClass1*& )
doIt<myClass1<int> >( myClass1*& )
doIt<myClass2>( myClass2*& )
doIt<myClass2<int> >( myClass2*& )
しかし、次のように呼び出すと失敗します:
doIt< std::vector<int, std::allocator<int> > >( std::vector<int, std::allocator<int>>*& )
エラーあり:
a.cxx: In function ‘void doIt(T*&) [with T = std::vector<int, std::allocator<int> >]’:
ba.cxx:87: instantiated from here
a.cxx:33: error: invalid initialization of reference of type ‘MyClass1&’ from expression of type ‘std::vector<int, std::allocator<int> >’
a.cxx:16: error: in passing argument 1 of ‘const char* getClassName(MyClass1&)’
(gcc 4.2.4)。
次の宣言を移動した場合:
template<typename T, typename A>
char const* getClassName( std::vector<T,A>& ) { printf("std::vector<T,A>\n"); return NULL; }
doItの前-コンパイルします。だから、
-
doIt
がgetClassName( MyClass2T<T>& )
の前に表示されるが、std::vector
の前には表示されないのはなぜですか
- <=>を<=>から独立させるにはどうすればよいですか? (<=>を独自のヘッダーに配置し、<=>、またはユーザー定義の特殊化について知る必要はありません。)
。
#include <stdio.h>
#include <assert.h>
#include <vector>
//template<typename T>
//char const* getClassName( T& );
//template<typename T, typename A>
////char const* getClassName( std::vector<T,A>& ) { printf("std::vector<T,A>\n"); return NULL; }
#if 1
// --------- MyClass2
struct MyClass1
{};
char const* getClassName( MyClass1& ) { printf("MyClass1\n"); return NULL; }
// --------- MyClass1T
template< typename T>
struct MyClass1T
{};
template<typename T>
char const* getClassName( MyClass1T<T>& ) { printf("MyClass1T<T>\n"); return NULL; }
#endif
template <typename T>
void doIt( T*& p )
{
if ( !p ) { return; }
T& ref = *p;
getClassName( ref );
}
// --------- MyClass2
struct MyClass2
{};
// declared after doIt, OK.
char const* getClassName( MyClass2& ) { printf("MyClass2\n"); return NULL; }
// --------- MyClass2T
template< typename T>
struct MyClass2T
{};
// declared after doIt, OK.
template<typename T>
char const* getClassName( MyClass2T<T>& ) { printf("MyClass2T<T>\n"); return NULL; }
template<typename T, typename A>
char const* getClassName( std::vector<T,A>& ) { printf("std::vector<T,A>\n"); return NULL; }
void test()
{
#if 1
MyClass1 mc1;
MyClass1* mc1p = &mc1;
doIt( mc1p );
MyClass2 mc2;
MyClass2* mc2p = &mc2;
doIt( mc2p );
MyClass1T<int> mc1t;
MyClass1T<int>* mc1tp = &mc1t;
doIt( mc1tp );
MyClass2T<int> mc2t;
MyClass2T<int>* mc2tp = &mc2t;
doIt( mc2tp );
// Nested templates are OK.
MyClass2T<MyClass1> mc2t2;
MyClass2T<MyClass1>* mc2tp2 = &mc2t2;
doIt( mc2tp2 );
#endif
#if 1
std::vector<int> v;
std::vector<int>* vp = &v;
doIt( vp ); // FAIL!
#endif
}
解決
getClassName(std :: vector <!> amp;)がdoItの前に表示され、getClassName(MyClass2T <!> amp;)が表示されないのはなぜですか
関数にはスコープ内の宣言が必要です。テンプレート関数をvector<int>
でインスタンス化すると、コンパイルが成功するために、署名getClassName(vector<int>&)
を持つ関数(少なくともプロトタイプ)が存在することが期待されます。
dodItをstd :: vectorから独立させるにはどうすればよいですか? (doItを独自のヘッダーに配置でき、std :: vector、またはユーザー定義の特殊化について知る必要はありません)
テンプレートに関するよくある質問をご覧ください。 doIt
の最初のインスタンス化の前に、<=>のすべての依存テンプレート関数のプロトタイプを配置してください。
他のヒント
失敗の理由は、インスタンス化時に、関数の非修飾名ルックアップが発生しないためです(ただし、ADL-引数依存ルックアップのみ)。インスタンス化コンテキストは次のとおりです(C ++標準の14.6.4.1/6
から取得):
テンプレート引数に依存する式のインスタンス化コンテキストは、同じ翻訳単位でのテンプレートの特殊化のインスタンス化のポイントの前に宣言された外部リンケージを持つ宣言のセットです。
この場合に呼び出したすべてのテンプレート特化のインスタンス化のポイントは、test
の定義の直後(14.6.4.1/1
を読む)です。したがって、宣言したすべての関数は、修飾されていないルックアップを使用してgetClassName
関数に表示されますが、それらのルックアップは実際には関数呼び出しで異なります。
テンプレート内のテンプレートパラメータに依存する関数呼び出しは、次のように検索されます。
- テンプレート定義コンテキストの名前は、通常のルックアップとADLの両方で考慮されます。
- インスタンス化コンテキストからの名前は、ADLでのみ考慮されます。
これは、テンプレートの定義コンテキストで宣言された適切なstd::vector<T>
関数がないため、ADLを使用してインスタンス化コンテキストで適切な関数を見つける必要があることを意味します。そうしないと、呼び出しが失敗し、宣言が見つかりません。
引数依存ルックアップ(ADL)
タイプstd
の引数の場合、ADLはネームスペースT
およびADL
のネームスペースの関数を検索します。 doIt
関数をMyClass2
名前空間に入れるとこれで機能します(ただし、未定義の動作が発生するため、標準では許可されていません-これは最後の手段としてのみ行う必要があります)。
int
の効果を確認するには、T = MyClass2
ではなくstd::vector<MyClass2>
のベクトルでMyClass1
を呼び出してみてください。それ以来14.6.4.2/1
であるため、ADLは<=>を受け入れる適切な関数を<=>のネームスペースで検索し、成功します-<=>のみを調べる<=>を使用する場合とは対照的です。
他の関数呼び出しについては、それぞれの宣言もすべて見つかります。これらはすべて、関数呼び出しの引数型も定義されているグローバル名前空間で宣言されているためです(<=>、<=>など)。
C ++ FAQは良いですが、テンプレートには深く入りません(ADLについての言及は見つかりませんでした)。より複雑な落とし穴のいくつかを処理する専用のテンプレートに関するよくある質問があります。
未定義の動作に注意してください
多くのコンパイラーは、<=>関数の前ではなく、後にを示した宣言を挿入した場合でもコードを受け入れることに注意してください。しかし、上記の標準の引用が示すように、宣言はインスタンス化コンテキストの一部ではなく、<=>で見つかったルールが監視されます:
呼び出しが不正な形式である場合、または関連する名前空間内のルックアップが、テンプレートで見つかった宣言を考慮するだけでなく、すべての翻訳単位でそれらの名前空間に導入された外部リンケージを持つすべての関数宣言を考慮した場合、より良い一致を見つける場合定義およびテンプレートのインスタンス化コンテキストの場合、プログラムの動作は未定義です。
したがって、動作するように見えるのは未定義の動作です。コンパイラがそれを受け入れることは有効ですが、それを拒否するか、クラッシュして終了することも同様に有効です。説明したように、必要な名前がインスタンス化コンテキストで実際に表示されることを確認してください。
これがお役に立てば幸いです。