C++11 で enable_if を書いてみる

C++er は C++11 で書くことを強いられているんだ!!(集中線


ってことで C++03 の enable_if を使ったコードを C++11 で書きなおしてみたいと思います。
べ、べつにぼっち Boost.勉強会じゃないんだからねっ!
まだ C++11 のコードは手探りで書いているので、もしかしたら今後もっとスマートな書き方が出てくるかも知れません。
そういう意味でも今後が楽しみですね。

[元にする C++03 コード]

#include <boost/type_traits/is_integral.hpp>
#include <boost/utility/enable_if.hpp>

template<typename T>
void
func(T x, typename boost::enable_if<boost::is_integral<T> >::type* =0){
    std::cout << "integral:" << x << std::endl;
}

template<typename T>
void
func(T x, typename boost::disable_if<boost::is_integral<T> >::type* =0){
    std::cout << "other:" << x << std::endl;
}

今回はこのコードを元に C++11 のコードに書きなおしていきたいと思います。

[標準ライブラリを使う]

C++11 の標準ライブラリには Boost からいくつかのライブラリが追加されました。
上記で使用している type_traits もその1つで、標準ライブラリで用意されているものを使用します。

// 標準ライブラリを使用する
#include <type_traits>

template<typename T>
void
func(T x,
    // std::enable_if を使う
    // boost::enable_if とは違い bool 値を渡すので注意
    typename std::enable_if<std::is_integral<T>::value>::type* =0
){
    std::cout << "integral:" << x << std::endl;
}

template<typename T>
void
func(T x,
    // std::disable_if はないので、std::enable_if を使用する
    typename std::enable_if<!std::is_integral<T>::value>::type* =0
){
    std::cout << "other:" << x << std::endl;
}


std::enable_if を使用する場合の注意点として上記のコードのように boost::enable_if と std::enable_if では、

namespace std{

    // std::enable_if は bool 値を渡す
    template<bool, typename _Tp = void>
    struct enable_if{};

}  // namespace std


namespace boost{

    // boost::enable_if は型を渡す
    template <class Cond, class T = void> 
    struct enable_if : public enable_if_c<Cond::value, T> {};

}  // namespace boost


このように第一引数の型が違っており、使用する場合には注意する必要があります。
(ちなみに std::enable_if == boost::enable_if_c です。
また std::disable_if は定義されていないので、std::enable_if を使用します。

[関数のデフォルトテンプレート引数を使う]

C++11 では関数テンプレートにもデフォルト引数を書くことが出来るので、関数の引数ではなくテンプレートの引数で SFINAE を行うことが出来ます。
また、今回の場合、

template<
    typename T,
    typename SFINAE = typename std::enable_if<std::is_integral<T>::value>::type
>
void
func(T x);


のような書き方では、オーバーロードが出来ないので、enabler を使用します。
enabler に関しては下記のサイトに詳しく載っているので参照して下さい。
http://cpplover.blogspot.com/2011/04/c0xenableif.html

#include <type_traits>

extern void* enabler;

template<
    typename T,
    typename std::enable_if<std::is_integral<T>::value>::type *& = enabler
>
void
func(T x){
    std::cout << "integral:" << x << std::endl;
}

template<
    typename T,
    typename std::enable_if<!std::is_integral<T>::value>::type *& = enabler
>
void
func(T x){
    std::cout << "other:" << x << std::endl;
}

C++03 の書き方に慣れていると違和感を覚えるかも知れませんが、これで関数の引数に enable_if 記述する必要がなくなりました。

[::value ではなく {} を使う]

分岐に使用しているメタ関数 std::is_integral は、

static_assert( std::is_integral<int>{}, "");
static_assert(!std::is_integral<void>{}, "");


のように constexpr bool に変換する事が出来るので ::value ではなく {} を使用する事が出来ます。
他にも std::integral_constant を継承しているメタ関数であれば同様に使用することが出来ます。

#include <type_traits>

extern void* enabler;

template<
    typename T,
    typename std::enable_if<std::is_integral<T>{}>::type *& = enabler
>
void
func(T x){
    std::cout << "integral:" << x << std::endl;
}

template<
    typename T,
    typename std::enable_if<!std::is_integral<T>{}>::type *& = enabler
>
void
func(T x){
    std::cout << "other:" << x << std::endl;
}

ちょっぴりコードが短くなりましたね。

[Template Aliases を使用して typename 〜 ::type を取り除く]

最後に Template Aliases を使用して std::enable_if の typename 〜 ::type を取り除きます。

#include <type_traits>

extern void* enabler;

template<bool B, typename T = void>
using enabler_if = typename std::enable_if<B, T>::type*&;


template<
    typename T,
    enabler_if<std::is_integral<T>{}> = enabler
>
void
func(T x){
    std::cout << "integral:" << x << std::endl;
}

template<
    typename T,
    enabler_if<!std::is_integral<T>{}> = enabler
>
void
func(T x){
    std::cout << "other:" << x << std::endl;
}

これは C++03 だとちょっと見慣れない書き方になりますね。
enabler_if がプリプロセッサのようにその場に展開されるイメージをすれば分かりやすいと思います。
これで メタプログラマの悩みの種であった typename ::type を消すことが出来ましたヒャッハー。

[比較]

さて、このように C++03 から C++11 のコードに書きなおしてみました。
実際に C++03 と C++11 のコードを比較してみるとこんな感じです。

[C++03]

#include <boost/type_traits/is_integral.hpp>
#include <boost/utility/enable_if.hpp>

template<typename T>
void
func(T x, typename boost::enable_if<boost::is_integral<T> >::type* =0){
    std::cout << "integral:" << x << std::endl;
}

template<typename T>
void
func(T x, typename boost::disable_if<boost::is_integral<T> >::type* =0){
    std::cout << "other:" << x << std::endl;
}

[C++11]

#include <type_traits>

extern void* enabler;

template<bool B, typename T = void>
using enabler_if = typename std::enable_if<B, T>::type*&;


template<
    typename T,
    enabler_if<std::is_integral<T>{}> = enabler
>
void
func(T x){
    std::cout << "integral:" << x << std::endl;
}

template<
    typename T,
    enabler_if<!std::is_integral<T>{}> = enabler
>
void
func(T x){
    std::cout << "other:" << x << std::endl;
}


どうでしょう、C++03 に比べて全体的に見やすくなっていると思います。
また、Boost に依存しないでこういうテクニカルなコードをかけるのが地味に嬉しいですね!


このように C++11 であればより簡潔なコードを書くことが出来ます。
まだ、C++11 でコードを書いている人は少ないと思いますが、今後もこういう C++11 ライクなコードが増えていくとうれしいですね。
C++11 はまだ本気を出していないのじゃ…。
そんな感じで環境が許すのであればどんどん C++11 を使って(広めて)行きましょう!
C++03 の時代は終わった!これからは C++11 の時代だ!