とくにあぶなくないRiSKのブログ

危ないRiSKのブログだったかもしれない。本当はRiSKだけどググラビリティとか取得できるIDの都合でsscriskも使ったり。

type_traits入門 std::enable_if

std::enable_if

template <bool B, class T = void>
struct enable_if;

enable_if は B が true の時に typedef T type; を持つ。B が false の時は type を持たない。T のデフォルトは void

#include<type_traits>

static_assert(std::is_same<std::enable_if<true>::type, void>::value, "::type");
static_assert(std::is_same<std::enable_if<true, int>::type, int>::value, "::type");
static_assert(std::is_same<std::enable_if<true, unsigned>::type, unsigned>::value, "::type");

// std::enable_if<false> には type が無い
// std::enable_if<false, int> には type が無い
// std::enable_if<false, unsigned> には type が無い

SFINAE

関数テンプレートで enable_if を使うと条件に合うときのみ,その関数テンプレートを有効にできる。次の例では is_same という条件に合う g(a) の時に関数テンプレートgが有効になっていることが分かると思う。

#include<type_traits>


// 条件無し。T は何型でもOK
template<class T>
void f(T const &){}


// T がint型と等しい時のみ有効
extern void* enabler;
template<class T, typename std::enable_if<std::is_same<T, int>::value>::type*& = enabler>
void g(T const &){}


int main()
{
 int a = 0;
 struct s{} b;

 f(a);    // OK
 f(b);    // OK

 g(a);    // OK
 // g(b); // NG
}

大雑把な解説をすると

typename std::enable_if<std::is_same<T, int>::value>::type*& = enabler

は, is_same が true なら

void*& /* dummy */ = enabler

という引数名が省略された単なる初期化になる。この場合は何も起こらず,意味もない。
一方 is_same が false なら,std::enable_if となり,std::enable_if には type は定義されていないので std::enable_if::type は不正な形になる。この時コンパイラは,すぐさまコンパイルエラーにするのではなく,この関数をオーバーロード呼出しの候補から外す。つまり,この関数がなかったことにしてくれる。この仕組みを SFINAE(すふぃ姉(すふぃねえ)※かわいい絵を募集中) と呼ぶ。
上の g(b) は最終的にオーバーロード呼出しの候補が0個になるので,結局コンパイルエラーになるんだけど,関数テンプレート g はいわば if then の形。オーバーロードを加えて else を用意すれば,if then else の形にできてコンパイル通せる。さらに増やせば switch もどきの形にできたりして,夢がひろがりんぐ,というのが std::enable_if なの。