C++11新機能--std::enable_ifとSFINAE

7250 ワード

https://www.jianshu.com/p/a961c35910d2
名詞解釈SFINAE
英語のSubstitution failure is not an errorの略で、一致に失敗したのは間違いではないという意味です.この言葉はどういう意味ですか.
SFINAEとは、ある特化がコンパイル時にエラー(すなわちコンパイル失敗)を招き、他に選択肢があれば、この特化エラーを無視して別の選択肢を選択することを意味する.
テンプレート関数を呼び出すと、コンパイラは、入力パラメータに基づいて最適なテンプレート関数を導出します.この導出の過程で、あるテンプレート関数またはいくつかのテンプレート関数がコンパイルに合格できない場合、1つが正しく導出できる限り、コンパイルエラーが発生する可能性のあるテンプレート関数はコンパイルエラーを引き起こすことはありません.この話は回りくどいので、次にコードで説明してみましょう.見ればわかります.
struct Test {
    typedef int foo;
};

template  
void f(typename T::foo) {} // Definition #1

template  
void f(T) {}               // Definition #2

int main() {
    f(10); // Call #1.
    f(10);  // Call #2. Without error (even though there is no int::foo) thanks to SFINAE.
}

これはwiki上のSFINAEの古典的な例であり、注釈はテンプレート関数を導く過程で正しいバージョンが見つかるため、即時int::fooは文法エラーであるが、コンパイラもエラーを報告しないことがよく理解されている.これがSFINAEの要義です.C++11において、規格は、C++98がその動作を明確に定義していないのではなく、このようなコンパイルの動作を確立する.std::enable_を介してifとSFINAEの共同使用は、多くの奇妙な実装を生み出し、STLライブラリにはこのような組合せが大量に適用されています.次に、それらの組合せがどのように動作しているかを見てみましょう.
std::enable_if
このテンプレートクラスの実装はかなり簡単で、1つのバージョンの実装を見てみましょう.
template
struct enable_if {};
 
template
struct enable_if { typedef T type; };

   一般バージョンのテンプレートクラス定義、偏特化バージョンのテンプレートクラス定義.最初のテンプレートパラメータがfalseの場合typeは定義されず、最初のテンプレートパラメータがtrueの場合にのみtypeが定義されます.次のテンプレートインスタンス化コードを見てみましょう
typename std::enable_if::type t; //  
typename std::enable_if::type; //      ,      ,           ,       true,                    void
typename std::enable_if::type; //      ,type      
typename std::enable_if::type t2; //  

typename std::enable_if::typeがbool値を入力すると、このtypeが定義されていないかどうかを導くことができます.では、この使い方にはどんな用途がありますか.上のSFINAEと合わせてコードを見てみましょう
template 
typename std::enable_if<:is_trivial>::value>::type SFINAE_test(T value)
{
    std::cout<
typename std::enable_if::value>::type SFINAE_test(T value)
{
    std::cout<

    この2つの関数が通常の関数であれば、リロードされたルールに基づいてコンパイルされません.テンプレート関数でも、両方の関数が正しい結果を導くことができれば、リロードの二義的な問題が発生しますが、std::enable_ifの運用は,この2つの関数の戻り値を同じ関数呼び出しの導出過程で1つの合法のみとし,SFINAEの原則に従えばコンパイルを円滑に行うことができる.
        SFINAE_test(std::string("123"));
        SFINAE_test(123);

   最初の関数呼び出しがテンプレート関数の導出を行う場合、最初のバージョンのテンプレート関数std::is_trivial::valueはfalse、std::enable_if<:is_trivial>::value>::typeというタイプは定義されておらず、正しく導くことができず、コンパイラ領域は次の可能な実装を探しているので、次に2番目のテンプレート関数を見つけます.std::is_trivial::valueの値はtrueで、std::enable_if<:is_trivial>::value>::typeはvoidタイプで、導出に成功しました.このときSFINAE_test(std::string("123"));呼び出しは、唯一決定された導出、すなわち2番目のテンプレート関数であるため、プログラムはT is none trivalを印刷する.これと同様の手順で、2番目の関数呼び出しはT is trivalを印刷します.このように書くメリットは何ですか.この例では,SFINAE特性を用いて異なる戻り値,同じ関数パラメータによる関数再ロードを実現し,コードがより統一されているように見えると考えられる.他にもstd::enable_ifの方式、例えばテンプレートパラメータリスト、関数パラメータリストでは、SFINAE特性を利用してある関数の選択導出を実現する.cppreferenceの例コードを見てみましょう
#include 
#include 
#include 
 
namespace detail { struct inplace_t{}; }
void* operator new(std::size_t, void* p, detail::inplace_t) {
    return p;
}
 
// enabled via the return type
template
typename std::enable_if<:is_trivially_constructible>::value>::type 
    construct(T* t,Args&&... args) 
{
    std::cout << "constructing trivially constructible T
"; } // enabled via a parameter template void destroy(T* t, typename std::enable_if<:is_trivially_destructible>::value>::type* = 0) { std::cout << "destroying trivially destructible T
"; } // enabled via a template parameter template{} && (std::is_class{} || std::is_union{}), int>::type = 0> void destroy(T* t) { std::cout << "destroying non-trivially destructible T
"; t->~T(); } int main() { std::aligned_union_t<0,int,std::string> u; construct(reinterpret_cast(&u)); destroy(reinterpret_cast(&u)); construct(reinterpret_cast<:string>(&u),"Hello"); destroy(reinterpret_cast<:string>(&u)); }

    コードから分かるように、上記の例は基本的なstd::enable_をカバーしています.ifが適用できるシーンは、SFINAEの原則を適用して、正しいテンプレート関数バージョンを導くために関数を呼び出すことを実現することです.
作者:于天佐リンク:https://www.jianshu.com/p/a961c35910d2出典:簡書簡書の著作権は著者の所有であり、いかなる形式の転載も著者に連絡して授権を得て出典を明記してください.
 
C++:enableの使用方法の例ifとテンプレートの関数ポインタパラメータ
次の例では、主に次の機能を実現します.
1パラメータがデータ型の場合std::to_を呼び出すstring()メソッドは、数値を文字列に変換して出力します.2パラメータがstd::stringタイプの場合、std::stringの値出力を直接使用します.3パラメータがオブジェクトの場合、オブジェクトにstd::string str()メソッドが含まれている場合、オブジェクトのstd::string str()メソッドが呼び出されます.4オブジェクトにstd::string str()メソッドが含まれていない場合、空の文字列が返されます.
class Box {
public:
    string str() {
        return "yes";
    }
};
 
class Bin {
public:
    string str1() {
        return "no";
    }
};
 
template
std::string str(T& t) {
    cout << "1.---------------------" << endl;
    return "null";
};
 
template<>
std::string str(string& t) {
    cout << "2.---------------------" << endl;
    return t;
};
 
template
string str(typename std::enable_if<:is_class>::value && !std::is_same::value, T>::type& t) {
    cout << "3.---------------------" << endl;
    return t.str();
};
 
template
string str(typename std::enable_if<:is_arithmetic>::value, T>::type&  t) {
    cout << "4.---------------------" << endl;
    return std::to_string(t);
};
 
 
int main() {
    string s = "sddds";
    cout << str(s) << endl;
 
    bool j = true;
    cout << str(j) << endl;
 
    int i = 1000; 
    cout << str(i) << endl; 
 
    float f = 10.6f;
    cout << str(f) << endl;
 
    Box b1;
    cout << str(b1) << endl;
 
    Bin b2;
    cout << str(b2) << endl;
 
    return 1;
}

2.--------------------- sddds 4.--------------------- 1 4.--------------------- 1000 4.--------------------- 10.600000 3.--------------------- yes 1.--------------------- null上記のコードで使用されているstd::enable_を見てみましょう.if
typename std::enable_if<:is_arithmetic>::value,T>::typeは、Tが数値タイプの場合、Tを使用するタイプを表します.
typename std::enable_if<:is_class>::value && !std::is_same::value,T>::typeは、Tがクラスであり、Tがstringタイプでない場合、Tのタイプを使用することを示す
テンプレートの関数ポインタパラメータを見てみましょう
templateは、このテンプレート関数を使用する場合、Tにstd::string str()関数を含める必要があることを示します.