1. はじめに


c++98 で STL コンテナを自作するときに enable_if を実装する必要があったので今回はそのまとめです。あくまで、c++98 ベースで作っていきます。

前回の is_integral を使うので、場合によっては参照してください。

2. SFINAE とは


cpprefjp より引用

「SFINAE (Substitution Failure Is Not An Errorの略称、スフィネェと読む)」は、テンプレートの置き換えに失敗した際に、即時にコンパイルエラーとはせず、置き換えに失敗した関数をオーバーロード解決の候補から除外するという言語機能である。

テンプレート引数の展開に不正があっても、エラーにはならず候補から外され、コンパイルは続行される。

残った候補の中から置き換えに成功したものを実行する。

struct Test {
    typedef void type;
};

template <typename T> 
void f(typename T::type) {} // 定義#1

template <typename T> 
void f(T) {}               // 定義#2

int main() {
    f<Test>(10); // 定義#1 の呼び出し
    f<int>(10);  // 定義#2 の呼び出し
}
  • f<Test>(10) について、Test::type が存在するので 定義1 が実体化する。
  • f<int>(10) について、int型は int::type が存在しないので 定義1 が候補から除外され、 定義2 が実体化される。

このようにテンプレート実引数の型を調べて、実体化するテンプレート定義を決定することができる特徴がある。

enable_if


定義

// プライマリー (primary template)
template<bool, class _Tp = void>
struct enable_if {};

// 部分特殊化 (Partial specialization for true)
template <class _Tp>
struct enable_if <true, _Tp> {
    typedef _Tp type;
};

プライマリーの最後のテンプレート仮引数に class _Tp = void を用意し、呼び出し時に enable_if<bool値, 型> で使用することができます。

_Tp は void型が指定されており省略可能なので、実際には enable_if<bool値>::type で使用することが多いです。

整数型かどうか


整数型かどうか判定するis_integralis_integral<T>::value で bool値を取り出すことができました。

#include <iostream>
#include <type_traits>

template <class T>
void f(T, typename std::enable_if<std::is_integral<T>::value>::type* = NULL)
{
    cout << "Tは整数型" << endl;
}

template <class T>
void f(T, typename std::enable_if<!std::is_integral<T>::value>::type* = NULL)
{
    cout << "Tは整数型以外" << endl;
}

int main()
{
    f(3); // Tは整数型
    f("hello"); // Tは整数型以外
}

このように enable_if と組み合わせて書くことができます。

enable_if<true> のときは type が定義されているので enable_if<true>::type を取得できますが、false のときは定義されていないので取得できません。

最後に


今日は enable_if についてまとめてみました。SFINAE を通して改めてメタプログラミング難しいなと思いました。もう少しcppになれないといけないです。

参考URL