C ++ SFINAEの例?


122

テンプレートのメタプログラミングについてもっと知りたいです。SFINAEが「置換の失敗はエラーではない」の略であることを知っています。しかし、誰かがSFINAEの良い使い方を私に示すことができますか?


2
これは良い質問です。私はSFINAEをかなりよく理解していますが、これを使用する必要があったとは思いません(ライブラリが知らないうちにそれを行わない限り)。
Zifre 2009年

5
STLはここのFAQで「置換の失敗は象ではない」と少し異なって表現しています
バルカンレイヴン

回答:


72

ヒレス一例(ここから):

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


8
@rlbond、私はここのこの質問へのコメントであなたの質問に答えました:stackoverflow.com/questions/822059/…。つまり、両方のテスト関数が候補であり、実行可能である場合、「...」は変換コストが最悪なので、他の関数が優先されて使用されることはありません。"..."は省略記号、var-argです:int printf(char const *、...);
ヨハネスシャウブ-litb 2009年


20
ここでの奇妙なことはIMOはではなくであり...int C::*私が見たことがないため、調べなければなりませんでした。以下のための答えを見つけたものつまり、何それはここに使用されることがあります。stackoverflow.com/questions/670734/...
HostileForkがdont信頼SEと言う

1
誰かがC :: *が何であるかを説明できますか?すべてのコメントとリンクを読みましたが、まだ疑問に思っています。intC :: *は、それがint型のメンバーポインターであることを意味します。クラスにint型のメンバーがない場合はどうなりますか?何が欠けていますか?そして、どのようにtest <T>(0)がこれに関与しますか?私は何かが欠けている必要があります
user2584960

92

を使用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の配列へのポインターCCfalse(ここでは0)の場合、無効なタイプを取得しますchar(*)[0]、サイズ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) { /* ... */ }
}

実際には、状態をチェックする機能は便利な機能だと思います。


1
@Johannes奇妙なことに、GCC(4.8)とClang(3.2)はサイズ0の配列の宣言を受け入れます(そのため、型は実際には「無効」ではありません)が、コードでは正しく動作します。SFINAEとタイプの「通常の」使用の場合のこのケースには、おそらく特別なサポートがあります。
akim

@akim:もしそれが本当なら(奇妙な?!いつから?)そして、おそらくM <= N ? 1 : -1代わりに動作するでしょう。
v.oddou 2014年

1
@ v.oddou試してみてくださいint foo[0]。非常に便利な「長さが0の配列で終わる構造体」のトリック(gcc.gnu.org/onlinedocs/gcc/Zero-Length.html)を使用できるので、サポートされていることには驚かない。
akim

@akim:ええ、私が思ったこと-> C99。:これは、あなたが最新のコンパイラで得るものですここでは、C ++で許可されていませんerror C2466: cannot allocate an array of constant size 0
v.oddou

1
@ v.oddouいいえ、私は本当にC ++を意味し、実際にはC ++ 11を意味します。clang++とg ++の両方がそれを受け入れ、これがなぜ有用であるかを説明するページを示しました。
akim

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の時点で、std :: void_tがあることに注意してください。これは、ここではTypeSinkと基本的に同じです。


コードの最初のブロックは、同じテンプレートを再定義します。
2014

is_integralとis_floating_pointの両方がtrueであるタイプがないため、どちらかであるか、SFINAEが少なくとも1つを削除するためです。
odinthenerd 2014年

同じテンプレートを異なるデフォルトのテンプレート引数で再定義しています。コンパイルしてみましたか?
TC

2
私はテンプレートメタプログラミングが初めてなので、この例を理解したいと思いました。TypeSinkT<decltype(std::declval<T&>().*(&T::bar))>ある場所で使用し、次にTypeSinkT<decltype(&T::bar)>別の場所で使用する理由はありますか?また&必要std::declval<T&>ですか?
Kevin Doyon

1
あなたについてTypeSink、C ++ 17はstd::void_t:)
YSC 2018

10

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


4

C ++ 17は、機能を照会するための一般的な手段を提供するでしょう。詳細については、N4502を参照してください。ただし、自己完結型の例として、以下を検討してください。

この部分は定数部分なので、ヘッダーに入れます。

// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;

// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};

// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};

N4502から抜粋した次の例は、使用方法を示しています。

// Archetypal expression for assignment operation.
template <typename T>
using assign_t = decltype(std::declval<T&>() = std::declval<T const &>())

// Trait corresponding to that archetype.
template <typename T>
using is_assignable = detect<T, assign_t>;

他の実装と比較すると、これはかなり単純です。ツール(void_tおよびdetect)の数を減らすだけで十分です。さらに、以前のアプローチよりもかなり効率的(コンパイル時およびコンパイラのメモリ消費)であることが報告されています(N4502を参照)。

ここで実際の例 GCCのための移植性の微調整が5.1を事前に含まれ、。


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;
}

int C::*あなたの答えのこの構文は何を意味しますか?C::*パラメータ名はどのようにできますか?
Kirill Kobelev 2016年

1
メンバーへのポインターです。一部の参照:isocpp.org/wiki/faq/pointers-to-members
'whoan

@KirillKobelev int C::*は、intメンバー変数へのポインターの型ですC
YSC 2018

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簡単に作成できないタイプのオブジェクトへの「偽の参照」を提供するユーティリティです。declvalSFINAEの構築に非常に便利です。

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';
}

0

ここでは、(直接SFINAEではなく)テンプレート関数のオーバーロードを使用して、ポインターが関数であるかメンバークラスポインターであるかを判断しています:(iostream cout / cerrメンバー関数ポインターが1またはtrueとして印刷されるのを修正できますか?

https://godbolt.org/z/c2NmzR

#include<iostream>

template<typename Return, typename... Args>
constexpr bool is_function_pointer(Return(*pointer)(Args...)) {
    return true;
}

template<typename Return, typename ClassType, typename... Args>
constexpr bool is_function_pointer(Return(ClassType::*pointer)(Args...)) {
    return true;
}

template<typename... Args>
constexpr bool is_function_pointer(Args...) {
    return false;
}

struct test_debugger { void var() {} };
void fun_void_void(){};
void fun_void_double(double d){};
double fun_double_double(double d){return d;}

int main(void) {
    int* var;

    std::cout << std::boolalpha;
    std::cout << "0. " << is_function_pointer(var) << std::endl;
    std::cout << "1. " << is_function_pointer(fun_void_void) << std::endl;
    std::cout << "2. " << is_function_pointer(fun_void_double) << std::endl;
    std::cout << "3. " << is_function_pointer(fun_double_double) << std::endl;
    std::cout << "4. " << is_function_pointer(&test_debugger::var) << std::endl;
    return 0;
}

プリント

0. false
1. true
2. true
3. true
4. true

コードがそうであるように、それ(コンパイラーに応じて「善意」)、trueまたはfalseを返す関数へのランタイム呼び出しを生成できます。is_function_pointer(var)をコンパイル時に強制的に評価する場合(実行時に関数呼び出しは実行されません)、constexpr変数トリックを使用できます。

constexpr bool ispointer = is_function_pointer(var);
std::cout << "ispointer " << ispointer << std::endl;

C ++標準では、すべてのconstexpr変数がコンパイル時に評価されることが保証されています(コンパイル時のC文字列の計算長。これは本当にconstexprですか?)。


0

次のコードはSFINAEを使用して、型に特定のメソッドがあるかどうかに基づいてコンパイラーにオーバーロードを選択させます。

    #include <iostream>
    
    template<typename T>
    void do_something(const T& value, decltype(value.get_int()) = 0) {
        std::cout << "Int: " <<  value.get_int() << std::endl;
    }
    
    template<typename T>
    void do_something(const T& value, decltype(value.get_float()) = 0) {
        std::cout << "Float: " << value.get_float() << std::endl;
    }
    
    
    struct FloatItem {
        float get_float() const {
            return 1.0f;
        }
    };
    
    struct IntItem {
        int get_int() const {
            return -1;
        }
    };
    
    struct UniversalItem : public IntItem, public FloatItem {};
    
    int main() {
        do_something(FloatItem{});
        do_something(IntItem{});
        // the following fails because template substitution
        // leads to ambiguity 
        // do_something(UniversalItem{});
        return 0;
    }

出力:

フロート:1
Int:-1
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.