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]
#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 の時代だ!