2013-01-31 8 views
7

私のライブラリのconstおよびnon-constテンプレート引数の関数を提供しようとすると、私は奇妙な問題に遭遇しました。以下のソースコード最小例現象である:未定義テンプレート 'class'の暗黙的なインスタンス化

#include <iostream> 


template<typename some_type> 
struct some_meta_class; 

template<> 
struct some_meta_class<int> 
{ 
    typedef void type; 
}; 



template<typename some_type> 
struct return_type 
{ 
    typedef typename some_meta_class<some_type>::type test; 

    typedef void type; 
}; 



template<typename type> 
typename return_type<type>::type foo(type & in) 
{ 
    std::cout << "non-const" << std::endl; 
} 

template<typename type> 
void foo(type const & in) 
{ 
    std::cout << "const" << std::endl; 
} 


int main() 
{ 
    int i; 

    int const & ciref = i; 
    foo(ciref); 
} 

Iは非constバージョンとfooのCONSTバージョンを実装しようとしたが、残念ながら、このコードはCLANG 3.0とgcc 4.6.3でコンパイルされません。

main.cpp:18:22: error: implicit instantiation of undefined template 'some_meta_class'

なんらかの理由で、コンパイラは何らかの理由でconst-referenceにfooの非constバージョンを使用したいとします。 some_meta_classの実装がないため、これは明らかに上記のエラーにつながります。奇妙なことは、あなたが以下の変更のいずれかを実行した場合、コードがうまくコンパイルして動作すること、である:非constバージョン

  • uncomemnt/return_typeのtypedefのを削除::テスト
  • を削除/

    • コメント解除

    この例はもちろん最小限で純粋な学問です。私のライブラリでは、constとnon-constバージョンが異なる型を返すので、この問題に遭遇しました。私はこの問題を部分的に特化したヘルパークラスを使って管理しました。

    しかし、なぜ上記の例ではこのような奇妙な動作が起こりますか?なぜコンパイラーは、constバージョンが有効で、より良く一致する非constバージョンを使用したいのですか?

    +0

    現在、有効な有効なオーバーロードのリストを生成する段階にあり、return_type を正しくインスタンス化できないため、そこに救済が行われます(return_typeのtypedefをコメントアウトしてください) – PlasmaHH

    答えて

    15

    なぜなら、関数呼び出しの解決がテンプレート引数の減算と置換と一緒に実行されるためです。

    1. まず、名前検索が行われます。これは、一致する名前がfoo()の2つの関数を提供します。

    2. 第二に、型推論が行われる:一致する名前を持つテンプレート関数の各ため、コンパイラは、実行可能な一致を生じる関数テンプレート引数を推測しようとします。この段階では、エラーが発生します。

    3. 第3に、オーバーロードの解像度がゲームに入ります。これは、型減算が実行され、呼び出しを解決する実行可能な関数のシグネチャが決定された後にのみになります。コンパイラは、すべての関数の正確なシグネチャが見つかった後でのみ、候補者。

    コンパイラは(すなわち、ステップ3になります)の呼び出しを解決するための最も実行可能な候補として、それを選択したので、しかし、コンパイラが生成するので、あなたが非const過負荷に関連するエラーが出るということではありませんステップ2.

    時に、その署名を決定するために、その戻り値の型をインスタンス化しながら、1がSFINAEは(置換の失敗はアンではありません適用されることを期待するかもしれないので、これは、しかしエラーになり、なぜエラーこれは、完全に明らかにされていませんエラー)。これを明確にするために、我々は単純な例を検討できます。この例では

    template<typename T> struct X { }; 
    
    template<typename T> typename X<T>::type f(T&) { } // 1 
    template<typename T> void f(T const&) { }   // 2 
    
    int main() 
    { 
        int const i = 0; 
        f(i); // Selects overload 2 
    } 
    

    を、SFINAEが適用されます。ステップ2の間に、コンパイラは、上記の2つのオーバーロードのそれぞれのTを推定し、その署名を決定しようとします。オーバーロード1の場合、置換エラーが発生します。X<const int>は、typetypedefX)を定義していません。しかし、SFINAEのために、コンパイラは単にを捨ててとなり、オーバーロード2は実行可能な一致であることがわかります。従って、それはそれを選ぶ。

    のは、今あなた例を反映するように、少し例を変更してみましょう:何が変わったのか

    template<typename T> struct X { }; 
    
    template<typename Y> 
    struct R { typedef typename X<Y>::type type; }; 
    
    // Notice the small change from X<T> into R<T>! 
    template<typename T> typename R<T>::type f(T&) { } // 1 
    template<typename T> void f(T const&) { }   // 2 
    
    int main() 
    { 
        int const i = 0; 
        f(i); // ERROR! Cannot instantiate R<int const> 
    } 
    

    は、過負荷1は、もはやX<T>::typeを返すのではなく、R<T>::typeということではありません。これは、Rtypedef宣言があるため、と同じとなります。したがって、同じ結果が得られると予想される場合があります。ただし、この場合はコンパイルエラーが発生します。どうして?

    標準回答(段落14.8.3/8)を有する:

    If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed if written using the substituted arguments. [...] Only invalid types and expressions in the immediate context of the function type and its template parameter types can result in a deduction failure.

    明らかに、第二の例(ならびにあなたが)ネストされたコンテキストにエラーが発生し、そうSFINAEはありません適用する。私はこれがあなたの質問に答えると信じている。あなたが興味があれば

    [...] If a substitution in a template parameter or in the function type of the function template results in an invalid type, type deduction fails. [...]

    :ところで

    、(パラグラフ14.8.2/2)より一般的に述べられており、このはC++ 03以降に変更されたこと、気づくことは興味深いです理由についてなぜものが変更された、this paperあなたにアイデアを与えるかもしれません。

    +0

    対の探偵仕事と実践的な言語弁護士。 –

    +0

    @honk:ありがとうございました:-) –

    +0

    これは、明らかに、ありがとう! –

    関連する問題