152

テンプレートのメタプログラミングについてもっと知りたいです。SFINAE は「代入の失敗はエラーではありません」の略であることは知っています。しかし、誰かが SFINAE の良い使い方を教えてくれませんか?

4

10 に答える 10

97

SFINAEブール条件をチェックするために使用するのが好きです。

template<int I> void div(char(*)[I % 2 == 0] = 0) {
    /* this is taken when I is even */
}

template<int I> void div(char(*)[I % 2 == 1] = 0) {
    /* this is taken when I is odd */
}

とても便利です。たとえば、演算子コンマを使用して収集された初期化子リストが固定サイズを超えていないかどうかを確認するために使用しました

template<int N>
struct Vector {
    template<int M> 
    Vector(MyInitList<M> const& i, char(*)[M <= N] = 0) { /* ... */ }
}

このリストは、M が N より小さい場合にのみ受け入れられます。これは、イニシャライザ リストの要素が多すぎないことを意味します。

構文のchar(*)[C]意味: 要素型が char で size の配列へのポインターCCが false (ここでは 0) の場合、無効な type char(*)[0]、サイズがゼロの配列へのポインターを取得します。SFINAE は、テンプレートが無視されるようにします。

で表すとboost::enable_if、こんな感じ

template<int N>
struct Vector {
    template<int M> 
    Vector(MyInitList<M> const& i, 
           typename enable_if_c<(M <= N)>::type* = 0) { /* ... */ }
}

実際には、条件をチェックする能力が役立つ能力であることがよくあります。

于 2009-06-12T23:40:28.010 に答える
79

一例を次に示します(ここから):

template<typename T>
class IsClassT {
  private:
    typedef char One;
    typedef struct { char a[2]; } Two;
    template<typename C> static One test(int C::*);
    // Will be chosen if T is anything except a class.
    template<typename C> static Two test(...);
  public:
    enum { Yes = sizeof(IsClassT<T>::test<T>(0)) == 1 };
    enum { No = !Yes };
};

IsClassT<int>::Yes評価されると、int はクラスではないため 0 に変換int int::*できず、メンバー ポインターを持つことはできません。SFINAE が存在しない場合、「0 は非クラス型 int のメンバー ポインターに変換できません」のようなコンパイラ エラーが発生します。代わりに、...Two を返すフォームを使用するだけなので、false と評価されます。int はクラス型ではありません。

于 2009-06-11T18:54:41.733 に答える
16

C++11 では、SFINAE テストがよりきれいになりました。以下に、一般的な使用例をいくつか示します。

特性に応じて関数のオーバーロードを選択する

template<typename T>
std::enable_if_t<std::is_integral<T>::value> f(T t){
    //integral version
}
template<typename T>
std::enable_if_t<std::is_floating_point<T>::value> f(T t){
    //floating point version
}

いわゆる型シンクイディオムを使用すると、型にメンバーがあるかどうか、そのメンバーが特定の型であるかどうかを確認するなど、型に対してかなり任意のテストを実行できます

//this goes in some header so you can use it everywhere
template<typename T>
struct TypeSink{
    using Type = void;
};
template<typename T>
using TypeSinkT = typename TypeSink<T>::Type;

//use case
template<typename T, typename=void>
struct HasBarOfTypeInt : std::false_type{};
template<typename T>
struct HasBarOfTypeInt<T, TypeSinkT<decltype(std::declval<T&>().*(&T::bar))>> :
    std::is_same<typename std::decay<decltype(std::declval<T&>().*(&T::bar))>::type,int>{};


struct S{
   int bar;
};
struct K{

};

template<typename T, typename = TypeSinkT<decltype(&T::bar)>>
void print(T){
    std::cout << "has bar" << std::endl;
}
void print(...){
    std::cout << "no bar" << std::endl;
}

int main(){
    print(S{});
    print(K{});
    std::cout << "bar is int: " << HasBarOfTypeInt<S>::value << std::endl;
}

ここに実際の例があります: http://ideone.com/dHhyHE また、最近ブログで SFINAE とタグ ディスパッチに関するセクション全体を書きました (恥知らずなプラグですが関連があります) http://metaporky.blogspot.de/2014/08/ part-7-static-dispatch-function.html

C++14 の時点で、ここの TypeSink と本質的に同じ std::void_t があることに注意してください。

于 2014-08-11T11:46:29.693 に答える
10

Boost のenable_ifライブラリは、SFINAE を使用するためのきれいなインターフェイスを提供します。私のお気に入りの使用例の 1 つは、Boost.Iteratorライブラリにあります。SFINAE は、反復子の型変換を有効にするために使用されます。

于 2009-06-11T19:39:28.520 に答える
3

ここに SFINAE の 1 つの良い記事があります: C++ の SFINAE 概念の紹介: クラス メンバーのコンパイル時のイントロスペクション

次のように要約します。

/*
 The compiler will try this overload since it's less generic than the variadic.
 T will be replace by int which gives us void f(const int& t, int::iterator* b = nullptr);
 int doesn't have an iterator sub-type, but the compiler doesn't throw a bunch of errors.
 It simply tries the next overload. 
*/
template <typename T> void f(const T& t, typename T::iterator* it = nullptr) { }

// The sink-hole.
void f(...) { }

f(1); // Calls void f(...) { }

template<bool B, class T = void> // Default template version.
struct enable_if {}; // This struct doesn't define "type" and the substitution will fail if you try to access it.

template<class T> // A specialisation used if the expression is true. 
struct enable_if<true, T> { typedef T type; }; // This struct do have a "type" and won't fail on access.

template <class T> typename enable_if<hasSerialize<T>::value, std::string>::type serialize(const T& obj)
{
    return obj.serialize();
}

template <class T> typename enable_if<!hasSerialize<T>::value, std::string>::type serialize(const T& obj)
{
    return to_string(obj);
}

declval簡単に構築できないタイプのオブジェクトへの「偽の参照」を提供するユーティリティです。declvalは、SFINAE 構造に非常に便利です。

struct Default {
    int foo() const {return 1;}
};

struct NonDefault {
    NonDefault(const NonDefault&) {}
    int foo() const {return 1;}
};

int main()
{
    decltype(Default().foo()) n1 = 1; // int n1
//  decltype(NonDefault().foo()) n2 = n1; // error: no default constructor
    decltype(std::declval<NonDefault>().foo()) n2 = n1; // int n2
    std::cout << "n2 = " << n2 << '\n';
}
于 2015-12-25T07:13:09.237 に答える
3

Greg Rogers回答に基づく別の (後期) SFINAEの例を次に示します。

template<typename T>
class IsClassT {
    template<typename C> static bool test(int C::*) {return true;}
    template<typename C> static bool test(...) {return false;}
public:
    static bool value;
};

template<typename T>
bool IsClassT<T>::value=IsClassT<T>::test<T>(0);

このようにして、 の値をチェックして、がクラスvalueかどうかを確認できます。T

int main(void) {
    std::cout << IsClassT<std::string>::value << std::endl; // true
    std::cout << IsClassT<int>::value << std::endl;         // false
    return 0;
}
于 2015-02-28T15:42:47.330 に答える
1

他の回答で提供された例は、必要以上に複雑に思えます。

cppreferenceからの例を少し理解しやすくします:

#include <iostream>
 
// this overload is always in the set of overloads
// ellipsis parameter has the lowest ranking for overload resolution
void test(...)
{
    std::cout << "Catch-all overload called\n";
}
 
// this overload is added to the set of overloads if
// C is a reference-to-class type and F is a pointer to member function of C
template <class C, class F>
auto test(C c, F f) -> decltype((void)(c.*f)(), void())
{
    std::cout << "Reference overload called\n";
}
 
// this overload is added to the set of overloads if
// C is a pointer-to-class type and F is a pointer to member function of C
template <class C, class F>
auto test(C c, F f) -> decltype((void)((c->*f)()), void())
{
    std::cout << "Pointer overload called\n";
}
 
struct X { void f() {} };
 
int main(){
  X x;
  test( x, &X::f);
  test(&x, &X::f);
  test(42, 1337);
}

出力:

Reference overload called
Pointer overload called
Catch-all overload called

ご覧のとおり、test の 3 回目の呼び出しでは、代入はエラーなしで失敗します。

于 2021-07-08T13:58:56.323 に答える