7

次のC++コード例を検討してください。

namespace n
{
    struct A {};
}

struct B {};

void foo(int) {}

template<typename T>
void quux()
{
    foo(T());
}

void foo(n::A) {}
void foo(B) {}


int main()
{
    quux<n::A>(); // Error (but works if you comment out the foo(int) declaration)
    quux<B>();    // Works

    return 0;
}

コメントに示されているように、テンプレートのインスタンス化quux<n::A>()により、コンパイラエラーが発生します(GCC 4.6.3の場合)。

foo.cpp: In function ‘void quux() [with T = n::A]’:
foo.cpp:22:16:   instantiated from here
foo.cpp:13:5: error: cannot convert ‘n::A’ to ‘int’ for argument ‘1’ to ‘void foo(int)’

誰かが私に何が起こっているのか説明できますか?と同じように機能することを期待していましたquux<B>()foo依存していると見なされる場合、それは何かと関係があるに違いありません。残念ながら、私のC++fooは十分ではありません。宣言が存在しない場合、例は正常にコンパイルfoo(int)されます。これも私にとっては驚くべきことです。

ヒント、説明、回避策は大歓迎です。

アップデート1:

foo(n::A)の定義の前にの宣言を移動したくない(読み取ることができない)quux(エラーを回避する)。

アップデート2:

テンプレートの前に宣言された間違った署名を持つ関数によって混乱している関連する質問テンプレート関数呼び出しを指摘してくれたDavidに感謝します。Johannes Schaubによって受け入れられた回答-litbはラッパークラスソリューションを提案します。これは、私の場合は回避策としても機能します。しかし、私はそれに100%満足しているわけではありません。

アップデート3:

foo(n::A)の定義を名前空間に入れることで問題を解決しましたn。規格の関連セクションを指摘するだけでなく、代替ソリューションも提供するJesseGoodとbames53の有益な回答に感謝します。DavidRodríguezに感謝します-私が提案された解決策と他のすべての貢献者を正しく理解していなかったときの彼の説明のためのdribeas。

4

2 に答える 2

2

ルールは14.6.4.2p1だと思います。

テンプレートパラメータに依存する関数呼び出しの場合、候補関数は、次の点を除いて、通常のルックアップルール(3.4.1、3.4.2、3.4.3)を使用して検出されます。

—非修飾名ルックアップ(3.4.1)または修飾名ルックアップ(3.4.3)を使用したルックアップの一部では、テンプレート定義コンテキストからの関数宣言のみが検出されます。

—関連付けられた名前空間(3.4.2)を使用したルックアップの一部では、テンプレート定義コンテキストまたはテンプレートインスタンス化コンテキストのいずれかで見つかった関数宣言のみが見つかります。

void foo(n::A) {}テンプレート定義コンテキストの後にあり、と同じ名前空間にないため、は表示されません。したがって、テンプレート定義のに表示するか、以下のように同じ名前空間に含める必要があります。foon::A

namespace n
{
    void foo(n::A) {}
}
于 2012-11-09T22:17:27.870 に答える
1

私のコンパイラが与えるエラーは次のとおりです。

main.cpp:11:5: error: call to function 'foo' that is neither visible in the template definition nor found by argument-dependent lookup
    foo(T());
    ^
main.cpp:18:5: note: in instantiation of function template specialization 'quux<n::A>' requested here
    quux<n::A>(); // Error (but works if you comment out the foo(int) declaration)
    ^
main.cpp:14:6: note: 'foo' should be declared prior to the call site or in namespace 'n'
void foo(n::A) {}
     ^

これにより、問題が何であるかが明確になります。

ただし、コメントアウトしてvoid foo(int) も機能しません。これは、コンパイラのバグ/拡張機能にすぎません。

void foo(n::A)以前は定義できないquux()とおっしゃっていますが、コメントで名前空間内で定義できないと言った場合n、その理由は当てはまらないようです。これにより、他の問題が発生することなく問題が解決するはずです。

template<typename T>
void quux()
{
    foo(T());
}

namespace n {
    void foo(n::A) {}
}
using n::foo; // in case there's any other code that depends on getting foo(n::A) from the global namespace

void foo(B) {} // this stays in the global namespace

void foo(n::A)の定義を適切な2フェーズルックアップで機能する場所に移動できない場合(ここでも、名前空間の前quux() または内部でn)、機能する可能性のある一種のハッキーなソリューションがあります。insideの適切なオーバーロードを前方宣言しfoo() ます quux()

template<typename T>
void quux()
{
    void foo(T);
    foo(T());
}

関数は、最終的にはと同じ名前空間内で定義する必要がありquux()、「generic」前方宣言と一致する必要があります。


または、別の方法があります。ほとんどのC++コンパイラが正しい2フェーズの名前ルックアップを提供し始めたのはごく最近のことです。そのため、正しくないが、どのコンパイラがサポートしたいコードがたくさんあります。コードを変更できない場合は、互換性コンパイラオプションを有効にするのに適している可能性があります。私のコンパイラはフラグ-fdelayed-template-parsingを取り、2フェーズの名前ルックアップを無効にし、代わりにインスタンス化コンテキストで常に名前をルックアップします。

于 2012-11-12T15:35:33.847 に答える