TMPの条件分岐を記載する場所
- 返り値型がvoid型
template<typename T> typename enable_if< /*条件式*/>::type func(T t) { } template<typename T> typename enable_if<!/*条件式*/>::type func(T t) { }
- 返り値型が引数型に依存 その(1)
template<typename T> typename enable_if< /*条件式*/,T>::type func(T t) { } template<typename T> typename enable_if<!/*条件式*/,T>::type func(T t) { }
enable_ifの第2型引数を指定すると、enable_if<>::typeがその型になる
- 返り値型が引数型に依存 その(2)
template<typename T> auto func(T t) -> typename enable_if< /*条件式*/,T>::type { } template<typename T> auto func(T t) -> typename enable_if<!/*条件式*/,T>::type { }
後置返り値型版
- 返り値型が特定の型(例:int型) その(1) 関数の戻り値型を使う(1)
template<typename T> typename enable_if< /*条件式*/,int>::type func(T t) { } template<typename T> typename enable_if<!/*条件式*/,int>::type func(T t) { }
enable_ifの第2型引数を指定すると、enable_if<>::typeがその型になる
- 返り値型が特定の型(例:int型) その(1) 関数の戻り値型を使う(2)
template<typename T> auto func(T t) -> typename enable_if< /*条件式*/,int>::type { } template<typename T> auto func(T t) -> typename enable_if<!/*条件式*/,int>::type { }
後置返り値型版
- 返り値型が特定の型(例:int型) その(2)関数の引数型を使う (1)
template<typename T> int func(T t, typename enable_if< /*条件式*/>::type = 0) { } template<typename T> int func(T t, typename enable_if<!/*条件式*/>::type = 0) { }
返り値型が使用できない場合、関数の第2引数の型を使用
- 返り値型が特定の型(例:int型) その(2)関数の引数型を使う (2)
template<typename T> void func(T t, typename enable_if< /*条件式*/>::type *= 0) { } template<typename T> void func(T t, typename enable_if<!/*条件式*/>::type *= 0) { }
ポインタ型版 こっちの方が良く見る
- 返り値型が特定の型(例:int型) その(2)関数の引数型を使う (3)
template<typename T> void func(T t, typename enable_if< /*条件式*/>::type *= nullptr) { } template<typename T> void func(T t, typename enable_if<!/*条件式*/>::type *= nullptr) { }
デフォルト引数がnullptr
引数が有限個のときにしか使用できない、などの制限があるため、
実際は次のTemplate型引数でチェックする方法が使われる(らしい)
- 返り値型が特定の型(例:int型) その(3)テンプレートのデフォルト型引数を使う (駄目な例)
これはSFINAEが失敗します
template<typename T, typename SFINAE = typename std::enable_if< /*条件式*/>::type > void func(T t) { } template<typename T, typename SFINAE = typename std::enable_if<!/*条件式*/>::type > void func(T t) { }
初見、なんでダメなのかわからなかったのですが デフォルト型引数にSFINAEは作用しないようです
- 返り値型が特定の型(例:int型) その(4)テンプレートの型引数・・・ではなくてtemplate定数の型(int)を条件定義する (1)
テンプレートはtemplate<typename T>
という書き方で、"型"をテンプレート引数にする以外に、
template<int N>
と言う形で、"定数"をテンプレート引数にする事もできます
ですので、tempate<int N>func()
はfunc<42>
という形で関数呼び出しできるわけです
このtemplate<int>
のint
の部分をenable_if
の第2型引数を使って、間接的にint
と書きます
template<typename std::enable_if</*hogehoge*/,int>::type=0> // template<int=0> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~
これだとSHINAEが作用するそうです
template<typename T, typename std::enable_if< /*条件式*/,int>::type = 0> void func(T t) { } template<typename T, typename std::enable_if<!/*条件式*/,int>::type = 0> void func(T t) { }
enable_if<>::typeは第2型引数で与えた型、つまりintになります
しかし、enable_if<>::typeがintとなるのは、第一引数の条件が真のときだけのです
条件が偽ならば、template関数の形をなさなくなるので、オーバーロード解決時に
見送られます。よって条件が真のときだけオーバーロードされるのです
初めて見たとき、あまりに変態的な書き方で感動しました
- 返り値型が特定の型(例:int型) その(4)テンプレートの型引数・・・ではなくてtemplate定数の型(int)を条件定義する (2)
extern void* enabler; template<typename T, typename std::enable_if< /*条件式*/>::type *& = enabler> void func(T t) { } template<typename T, typename std::enable_if<!/*条件式*/>::type *& = enabler> void func(T t) { }
さらにまた一歩変態的な書き方になったバージョンです
私はこれに関しては、理解できていません
enablerはポインタ型のグローバル変数で、デフォルト型引数として・・・何を持っているのでしょう?
http://cpplover.blogspot.jp/2011/04/c0xenableif.html
- 返り値型が特定の型(例:int型) その(4)テンプレートの型引数・・・ではなくてtemplate定数の型(int)を条件定義する (3)
template<typename T, typename std::enable_if< /*条件式*/>::type * = nullptr> void func(T t) { } template<typename T, typename std::enable_if<!/*条件式*/>::type * = nullptr> void func(T t) { }
上記のデフォルト型引数をnullptrに指定した事で
enablerをなくしたようです
http://d.hatena.ne.jp/gintenlabo/20110413/1302675301
- 返り値型が特定の型(例:int型) その(5)decltypeを使用する (1)
template<typename T> auto func(T t) -> decltype( typename std::enable_if< /*条件式*/>::type(), int() ) { } template<typename T> auto func(T t) -> decltype( typename std::enable_if<!/*条件式*/>::type(), int() ) { }
decltype()の中にコンマ区切りで複数の式が入っていた場合、
結局は最後の式の型が返ってくると言う性質を利用したものです
decltype comma trickとか何とか呼ばれているやり方です
これの何がすばらしいかと言うと、decltypeの中に好きなだけand条件を書く事ができる点です
- おまけ
オーバーロード解決のelse条件は以下のようにして書く事ができるようです
template<typename T> auto func(T t) -> decltype( typename std::enable_if< /*条件式*/>::type(), int() ) { } auto func(...) -> decltype( int() ) { }
可変個引数 (ellipsis)はオーバーロード解決時に、他のオーバーロード候補すべてにマッチしなかった場合に選択されます
したがって、2分岐のパターンマッチでは否定条件を定義するよりも、可変個引数を使用したほうがスマートのようです
http://code-mynote.blogspot.jp/2014/03/sfinae-ellipsis.html
ただリンク先にある型情報の消失に関して、まだ理解が無いため、本当に使うべきかは良くわかってないです・・・
(追記)
引数の数を制限して、2引数以上与えられた場合をそもそもコンパイル時にエラー検出したいときなんかは、使用できなさそうですね
(追記)
メンバ関数を有無を判定するhelperクラスを定義するときに使用されるようです。
http://cpplover.blogspot.jp/2010/03/decltypesfinae.html
引用元で実際に使用されている部分抜粋
template<typename T> struct has_func { private : // check(x)呼び出しでxの型特性によって、戻り値型が変化する // // SFINAE if(引数がfuncメンバ関数を持っている場合) template <typename U> static auto check(U x) -> decltype( x.func(), std::true_type() ) ; // true_typeを返す // SFINAE else static auto check(...) -> decltype( std::false_type() ); // false_typeを返す public : //static bool const value = std::identity<decltype( check( value<T>() ) )>::type::value ;(*1) // (*1): std::identityが消滅したので書き換え static const bool value = decltype( check(T()) )::value // 1) check()の返し値型がtrue_Type()->staticメンバ変数valueあり // // 2) check()の返し値型がfalse_Type()->staticメンバ変数valueなし-> // エラーになる->(enable_if内でhas_func::valueを読んでいればSFINAE発動) } ;