1

次の C++ コードは、MS Visual Studio 2010 ではコンパイルされません。

class Foo
{
public:
    /// Provides the signature of the methods that can be given to addValueSetListener
    template <typename TT>
    struct ChangeHandler
    {
        typedef void ( TT::* OnSetValueMethod )();
    };

    template <typename TT>
    void bar_ok(TT*, void ( TT::* )(), bool = false) {}
    template <typename TT>
    void bar_ok(const char*, TT*, void ( TT::* )()) {}

    template <typename TT>
    void bar_fails(TT*, typename ChangeHandler<TT>::OnSetValueMethod, bool = false) {}
    template <typename TT>
    void bar_fails(const char*, TT*, typename ChangeHandler<TT>::OnSetValueMethod) {}

    void testBar() {}
};

int main()
{
  Foo foo; 
  foo.bar_ok   ("allo",& foo, & Foo::testBar);  // compiles
  foo.bar_fails("allo",& foo, & Foo::testBar);  // compile ERROR
}

'TT': must be a class or namespace when followed by '::'ERROR 行のコンパイラ エラーはです。

失敗する行と失敗しない行の唯一の違いは、bar_fails がvoid (TT::*)()「テンプレート化された typedef」を介して「メソッド ポインター型」引数を宣言しているのに対し、bar_ok はそれを直接宣言していることです。

const char*のオーバーロードがなければ、テンプレート化された typedef は正常に機能することに注意してください。const char* オーバーロードが使用可能な場合、コンパイラは誤ってTT=[const char]bar_fails のオーバーロードを選択しますが、bar_ok の TT=Foo オーバーロードを正しく選択します。typedef が TT* や float* のような「単純な」データ用である場合、この問題は発生しません。

4

2 に答える 2

2

その理由は、このbar_ok場合、誤った構成がテンプレート パラメーター置換の直接のコンテキストで表示されるため、 SFINAEが適用される可能性があるためです。const char::*このbar_fail場合、これは 1 つのステップが削除された (「テンプレート typedef」に隠されている) ため、SFINAE は適用されなくなり、コンパイラは の構文上のナンセンスを処理する必要があるconst char::*ため、停止してエラーを報告します。

つまり、コンパイラが で間違ったオーバーロードを選択するわけではありませんbar_fail。どちらの場合も両方のオーバーロードを検査する必要がありますが、最初のオーバーロードでは、SFINAE によって誤ったオーバーロードを無視することが許可されていますが、2 番目のオーバーロードでは「遅すぎます」。

于 2013-08-20T05:19:23.140 に答える
1

Angew は、OP のコードが機能しない理由について簡単な説明を提供してくれました。回避策はありますか?とても簡単ですが、説明がコメントに収まらないので、ここにあります。

Angew が指摘したように、SFINAE は直接置換レベルでのみ適用されるため、次のようにコンパイルされます。

template <typename TT> void testFunc(TT) {}
template <typename TT> void testFunc(typename TT::Foo) {}
int main()
{
    testFunc<int>(3);
}

実際、コンパイラは の 2 番目のオーバーロードを削除することを認識しています。testFunc整数にはネストされたFoo型はありません。SFINAE が C++ でまったく使用できない場合、コンパイルが停止します。

上記の実装を特性のようなクラスを使用するように少し変更すると、次の完全に同等のコードはコンパイルされなくなります。

template <typename TT>
struct Helper
{
    typedef typename TT::Foo MyFoo;
};
template <typename TT> void testFunc(TT) {}
template <typename TT> void testFunc(typename Helper<TT>::MyFoo) {}

int main()
{
    testFunc<int>(3);
}

Helper<int>MyFoo typedef を解決するとき、コンパイラはクラスの「内部」にあるためです。直接SFINAEint::Fooが適用されないので、コンパイルを断念します。

私の OP では、テンプレート パラメーターを明示的に指定しなかったことに気付くかもしれませんが、上記では指定しています。これは、コンパイラーが param が int であることを認識しているため、パラメーターとしてtestFunc取るすべてのものと一致し、すべてをint試しているわけではないためです。 testFunc<int>. 私の OP では、エラーを取得するためにテンプレート パラメータを明示的に指定する必要はありませんでした。これは、最初の関数引数がこれを行ったためです。問題を曇らせるため、メソッドポインターを廃止しましょう。私の元の問題は、テンプレートパラメーターを明示的に指定しない次のより単純なコードによって示されます。

struct Foo
{
    template <typename TT> struct Helper
    {
        typedef typename TT::Foo MyFoo;
    };

    template <typename TT> void bar(TT*, typename Helper<TT>::MyFoo) {}
    template <typename TT> void bar(const char*, TT*) {}
};


int main()
{
    Foo foo; 
    foo.bar("allo", & foo);  // ok
}

コンパイラは への最初の関数呼び出しパラメータを とfoo.bar見なすため、 と の両方をconst char*考慮する必要があることがわかります。次に、内部で解決を試み、コンパイルを停止します (ここでも、直接の SFINAE は適用されません)。bar(const char*, Foo*)bar(const char*, Helper<const char>::MyFoo)Helper<const char>const char::Foo

1 つの解決策は、 を削除しHelperて直接ネストされた型に固執することです。

struct Foo
{
    template <typename TT> void bar(TT*, typename TT::Foo) {}
    template <typename TT> void bar(const char*, TT*) {}
};

bar()ただし、実際のコードではメソッドポインターが必要であり、宣言でこれを明確にしようとしているため、これは私にとって理想的ではありません(ただし、テンプレート化された typedef が役立つか妨害するかは疑問ですが、それは別の問題です) .

2 番目の解決策は、SFINAE を使用してコンパイラをbarレベルでボークさせることです。これは簡単に実行できることがわかりました: Helperforの特殊化を定義して、const char欠落している部分があります:

struct Foo
{
    template <typename TT> struct Helper
    {
        typedef typename TT::Foo MyFoo;
    };
    template <> struct Helper<const char>
    {
    };

    template <typename TT> void bar(TT*, typename Helper<TT>::MyFoo) {}
    template <typename TT> void bar(const char*, TT*) {}
};

コンパイラが に一致TTする場合、const char考慮すべき 2 つのオーバーロードがあります。

void bar(const char*, Helper<const char>::MyFoo)
void bar(const char*, Foo*)

ただしHelper<const char>::MyFoo、特殊化には存在しないため、SFINAE を使用できます。コンパイラは「内部」に入る必要はありませんHelper<T>

特殊化のためのこれらの 3 行は、問題を解決するのに十分でした。

于 2013-08-20T17:14:19.647 に答える