3

テンプレートのコンテキストでの関数名の検索について混乱しています。テンプレートがインスタンス化されるまで、コンパイラはテンプレート化されたコードで引数に依存する識別子の検索を遅らせることを知っています。つまり、テンプレート化されたコード内で構文エラーが発生したり、存在しない関数を呼び出したりすることがありますが、テンプレートを実際にインスタンス化しない限り、コンパイラは文句を言いません。

ただし、さまざまなコンパイラ間に不一致があることがわかりました。標準自体が何を必要としているのかを知りたいと思っています。

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

#include <iostream>

class Foo
{
    public:

    template <class T>
    void bar(T v)
    {
        do_something(v);
    }
};

void do_something(std::string s)
{
    std::cout << "do_something(std::string)" << std::endl;
}

void do_something(int x)
{
    std::cout << "do_something(int)" << std::endl;
}

int main()
{
    Foo f;
    f.bar("abc");
    f.bar(123);
}

テンプレート メンバー関数は、まだ宣言されていないという引数に依存しないグローバル関数をFoo::bar呼び出すことに注意してください。do_something

それでも、GCC 4.6.3 は上記のプログラムを問題なくコンパイルします。実行すると、出力は次のようになります。

do_something(std::string)
do_something(int)

ここにアイデアリンクがあります。

そのため、コンパイラはテンプレートがインスタンス化されるまで識別子の検索を遅らせたように見え、その時点でdo_something.

対照的に、GCC 4.7.2 は上記のプログラムをコンパイルしません。次のエラーが発生します。

test.cc: In instantiation of ‘void Foo::bar(T) [with T = const char*]’:
test.cc:27:13:   required from here
test.cc:10:3: error: ‘do_something’ was not declared in this scope, and no declarations were found by argument-dependent lookup at the point of instantiation [-fpermissive]
test.cc:19:6: note: ‘void do_something(int)’ declared here, later in the translation unit

したがって、GCC 4.7.2 は が後で宣言されていることを認識していますが、引数に依存しない do_somethingため、プログラムのコンパイルを拒否します。do_something

したがって、ここではおそらく GCC 4.7.2 が正しく、GCC 4.6.3 は正しくないと思います。おそらく、が定義されるdo_something前に宣言する必要があります。これに関する問題は、自分のクラスのユーザーがの独自のオーバーロードを実装しての動作を拡張Foo::barできるようにしたいと考えていることです。次のように書く必要があります。FooFoo::bardo_something

#include <iostream>

template <class T>
void do_something(T v)
{
    std::cout << "do_something(T)" << std::endl;
}

class Foo
{
    public:

    template <class T>
    void bar(T v)
    {
        do_something(v);
    }
};

void do_something(int x)
{
    std::cout << "do_something(int)" << std::endl;
}

int main()
{
    Foo f;
    f.bar("abc");
    f.bar(123);
}

ここでの問題は、 のオーバーロードがdo_something内から見えないFoo::barため、呼び出されないことです。したがって、 を呼び出しても、 のオーバーロードではなくdo_something(int)呼び出します。したがって、GCC 4.6.3 と GCC 4.7.2 の両方で、上記のプログラムは次のように出力します。do_something(T)int

do_something(T)
do_something(T)

それで、ここでいくつかの解決策は何ですか?Foo::barユーザーが独自のオーバーロードを実装して拡張できるようにするにはどうすればよいdo_somethingですか?

4

1 に答える 1

3

オーバーロードに関する限りdo_something、元のテンプレートを特殊化する必要があります。

template<>
void do_something<int>(int x) {
    std::cout << "do_something(int)" << std::endl;
}

編集: @MatthieuMとして。関数をオーバーロードする必要がある場合、関数テンプレートの特殊化は奇妙な結果をもたらす可能性があることを指摘しました (関数テンプレートは部分的に特殊化できないため、ある時点でおそらく必要になるでしょう)。Herb Sutter の記事Why Not Specialize Function Templates?への Matthieu のリンクを参照してください。完全な説明のために。

代わりに、構造体にラップされた静的関数を使用することをお勧めします。これにより、部分的な特殊化が可能になり、オーバーロードされた関数テンプレートに伴う名前解決の問題が解決されます。

template<typename T>
struct DoSomething {
    static void do_something(T v) {
        std::cout << "do_something(T)" << std::endl;
    }
};

struct Foo
{
    template <class T>
    void bar(T v) {
        DoSomething<T>::do_something(v);
    }
};

// Now you can specialize safely
template<>
struct DoSomething<int> {
    static void do_something(int v) {
        std::cout << "do_something(int)" << std::endl;
    }
};
于 2013-04-18T14:01:31.643 に答える