4

テンプレートのメタプログラミング コードを書くのは初めてです (それを読むだけではありません)。だから私はいくつかの初心者の問題に反抗しています。そのうちの 1 つは、 「私の SFINAE に何が起こったのですか?」というSO 以外の投稿でかなりよく要約されています。、これを次のように C++11 化します。

(注: この「思考実験」の例では、エラーの診断に役立つようにメソッドに異なる名前を付けました。非オーバーロードに対して実際にこのアプローチを実際に選択しない理由については、@R.MartinhoFernandes のメモを参照してください。)

#include <type_traits>

using namespace std;

template <typename T>
struct Foo {
    typename enable_if<is_pointer<T>::value, void>::type
    valid_if_pointer(T) const { }

    typename disable_if<is_pointer<T>::value, void>::type
    valid_if_not_pointer(T) const { }
};

int main(int argc, char * argv[])
{
    int someInt = 1020;
    Foo<int*>().valid_if_pointer(&someInt);    
    Foo<int>().valid_if_not_pointer(304);

    return 0;
}

@Alfは、SFINAEに何が起こったのか「そもそもそこになかった」と述べ、コンパイルすることを提案しますが、クラスの代わりに関数をテンプレート化します。これは状況によっては適切かもしれませんが、すべてではありません。 (たとえば、コピー構築可能かどうかに関係なく型を保持できるコンテナーを具体的に記述しようとしており、それに基づいてメソッドのオンとオフを切り替える必要があります。)

回避策として、これを試してみました...これは正しく機能しているようです。

#include <type_traits>

using namespace std;

template <typename T>
struct FooPointerBase {
    void valid_if_pointer(T) const { }
};

template <typename T>
struct FooNonPointerBase {
    void valid_if_not_pointer(T) const { }
};

template <typename T>
struct Foo : public conditional<
    is_pointer<T>::value, 
    FooPointerBase<T>,
    FooNonPointerBase<T> >::type {
};

int main(int argc, char * argv[])
{
    int someInt = 1020;
#if DEMONSTRATE_ERROR_CASES
    Foo<int*>().valid_if_not_pointer(&someInt);
    Foo<int>().valid_if_pointer(304);
#else
    Foo<int*>().valid_if_pointer(&someInt);
    Foo<int>().valid_if_not_pointer(304);
#endif
    return 0;
}

しかし、これが壊れていない場合(そうですか?)、特性の型をスニッフィングすることに基づいて、テンプレート化されたクラスでメソッドをオンおよびオフにする方法について、適切な一般的な方法論に従っていないことは確かです。より良い解決策はありますか?

4

2 に答える 2

12

まず、C++11はブーストのdisable_if. したがって、ブースト コードを移行する場合enable_ifは、否定された条件を使用する(または独自のdisable_if構成を再定義する)必要があります。

次に、SFINAE がメソッド レベルに到達して適用するには、それらのメソッド自体がテンプレートである必要があります。それでも、これらのテンプレートのパラメーターに対してテストを実行する必要があります...そのため、コードのようなものは機能しenable_if<is_pointer<T>ません。いくつかのテンプレート引数 (X としましょう) のデフォルトを T に等しくすることでこれを巧みに処理し、呼び出し元がそれを他の何かに明示的に特化していないという静的アサーションをスローします。

これは、次のように書く代わりに、次のことを意味します。

template <typename T>
struct Foo {
    typename enable_if<is_pointer<T>::value, void>::type
    valid_if_pointer(T) const { /* ... */ }

    typename disable_if<is_pointer<T>::value, void>::type
    valid_if_not_pointer(T) const { /* ... */ }
};

...あなたはこう書くでしょう:

template <typename T>
struct Foo {
    template <typename X=T>
    typename enable_if<is_pointer<X>::value, void>::type
    valid_if_pointer(T) const {
        static_assert(is_same<X,T>::value, "can't explicitly specialize");
        /* ... */
    }

    template <typename X=T>    
    typename enable_if<not is_pointer<X>::value, void>::type
    valid_if_not_pointer(T) const {
        static_assert(is_same<X,T>::value, "can't explicitly specialize");
        /* ... */
    }
};

どちらもテンプレートになり、enable_ifクラス全体の T ではなく、テンプレート パラメータ X を使用します。具体的には、オーバーロード解決の候補セットを作成するときに発生する置換に関するものです。最初のバージョンでは、オーバーロード解決中にテンプレートの置換は発生しません。

静的アサートは、元の問題の意図を保持し、誰かが次のようなものをコンパイルできないようにするためにあることに注意してください。

Foo<int>().valid_if_pointer<int*>(someInt);
于 2012-07-17T23:02:05.190 に答える
6

私の見方では、ここに SFINAE は必要ありません。SFINAE は、テンプレート化されたさまざまなオーバーロードを選択するのに役立ちます。基本的に、これを使用して、コンパイラが と の間template <typename Pointer> void f(Pointer);で選択できるようにしますtemplate <typename NotPointer> void f(NotPointer);

それはあなたがここで望むものではありません。ここでは、同じものの 2 つのオーバーロードではなく、異なる名前の 2 つの関数があります。コンパイラはすでに と の間template <typename Pointer> void f(Pointer);で選択できtemplate <typename NotPointer> void g(NotPointer);ます。

ここで、SFINAE が不要であるだけでなく、望ましくないと考える理由を説明するために、例を挙げます。

Foo<int> not_pointer;
Foo<int*> pointer;

not_pointer.valid_if_pointer(); // #1
not_pointer.valid_if_not_pointer(); // #2
pointer.valid_if_pointer(); // #3
pointer.valid_if_not_pointer(); // #4

さて、これを SFINAE で動作させることができたとしましょう。このコードをコンパイルしようとすると、1 行目と 4 行目でエラーが発生します。これらのエラーは、「メンバーが見つかりません」などの行に沿ったものになります。オーバーロードの解決で破棄される候補として関数をリストすることさえあります。

さて、これを SFINAE ではなく、代わりに行ったとしましょうstatic_assert。このような:

template <typename T>
struct Foo {
    void valid_if_pointer(T) const {
        static_assert(std::is_pointer<T>::value, "valid_if_pointer only works for pointers");
        // blah blah implementation
    }

    void valid_if_not_pointer(T) const {
        static_assert(!std::is_pointer<T>::value, "valid_if_not_pointer only works for non-pointers");
        // blah blah implementation
    }
};

これにより、同じ行でエラーが発生します。しかし、非常に短く有用なエラーが発生します。何年もの間、人々がコンパイラーの作成者に求めてきたものです。そして、それは今あなたの玄関先にあります:)

どちらの場合も同じエラーが発生しますが、SFINAE を使用しない方がはるかに優れたエラーが発生します。

また、まったく使用せずstatic_assert、関数の実装が、それぞれポインターまたは非ポインターが指定された場合にのみ有効であった場合でも、おそらく厄介なものを除いて、適切な行でエラーが発生することに注意してください。

TL;DR :同じ名前の実際のテンプレート関数が 2 つある場合を除きstatic_assert、SFINAE の代わりに使用することをお勧めします。

于 2012-07-17T23:34:51.353 に答える