19

最近、このサンプルコードを作成して、C++11可変個引数テンプレート関数の使用法を説明しました。

template <typename Head, typename... Tail> void foo (Head, Tail...);
template <typename... Tail> void foo (int, Tail...);
void foo () {}

template <typename... Tail>
void foo (int x, Tail... tail)
{
    std :: cout << "int:" << x;
    foo (tail...);
}

template <typename Head, typename... Tail>
void foo (Head x, Tail... tail)
{
    std :: cout << " ?:" << x;
    foo (tail...);
}

foo (int (123), float (123)); // Prints "int:123 ?:123.0"

前方宣言する最初の2行をfoo省略すると、int:123int:123代わりにこれが出力されます。これは、特定の経験豊富で知識豊富なC++プログラマーを驚かせました。

彼は、2フェーズルックアップの第2フェーズまでボディがインスタンス化されないため、前方宣言は必要ないと確信していました。彼は、コンパイラ(gcc 4.6)にバグがあると考えています。

foo2つは異なるベーステンプレート関数であり、ベーステンプレートの選択は最初のフェーズでロックインする必要があるため、コンパイラは正しいと思います。そうしないと、fooすべてのバージョンが定義される前にインスタンス化することで、単一定義規則に違反する可能性があります。そしてその後も(リンカが冗長なテンプレート関数定義が同一で、交換可能で、破棄可能であるとどのように想定するかを検討してください)。

それで、誰が正しいのですか?


上記のリンクされたGOTWは、関数テンプレートが部分的に特殊化されない方法と理由をうまく説明していますが、可変個引数テンプレート関数の存在は混乱を助長しているようです-foo<int,Tail...>部分的に特殊化されるべきfoo<Head,Tail...>直感は、非少なくとも私にとっては、可変個引数関数。

4

2 に答える 2

11

GCC(およびClang)は正しいです。MSVCは、ルックアップを正しく実装していないため、間違ってしまいます。

同僚からの誤解があるようです。ルックアップのルールは次のとおりです。

  • ベーステンプレート関数は、定義から呼び出される前に宣言する必要があります
  • 特殊なテンプレート関数は、インスタンス化する前に宣言する必要があります

注:これらのルールはfree-functionsに適用され、クラス内では前方宣言は必要ありません

定義は宣言としても機能するため、この例では、intバージョンを前方宣言する必要がないことに注意してください。

正しい例:

template <typename T> void foo(T);             // declare foo<T>

template <typename T> void bar(T t) { foo(t); }// call foo<T> (dependent context)

template <> void foo<int>(int);                // declare specialiaztion foo<int>

void bar(int i) { foo(i); }                    // instantiate foo<T> with int
                                               // which is the specialization

使用可能なベーステンプレートがある場合、これはエラーです。インスタンス化の前に特殊化が宣言されていない場合、それは使用されません。これは、その後、ODRルールの違反を意味する可能性があります(別のインスタンス化が特殊化を使用する場合)。

標準(C ++ 0x FDIS)から:

14.6.4.2

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

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

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

関数名がunqualified-idであり、呼び出しの形式が正しくないか、関連付けられた名前空間内のルックアップで、すべての変換ユニットのそれらの名前空間に導入された外部リンケージを持つすべての関数宣言が考慮された場合、テンプレート定義およびテンプレートインスタンス化コンテキストで見つかった宣言の場合、プログラムの動作は未定義です。

参照されている段落は通常の機能に関するものであることに注意してください。

于 2011-08-31T11:32:20.323 に答える
7

2フェーズルックアップは以下を見つけます:

  • 定義の時点で表示される関数、および
  • インスタンス化の時点でADLによって検出できる関数。

template <typename Head, typename... Tail> void foo (Head x, Tail... tail)ADLで検出できないため、定義の時点で表示されていない場合は、まったく検出されません。

言い換えれば、GCCは正しいです。

于 2011-08-31T11:27:23.387 に答える