元ネタ: 本の虫「C++0xにおけるenable_ifの新しい使い方」
Boost の ML で、新しい enable_if の使い方が示されているようです:
// Never defined extern void * enabler ; template < typename T, typename std::enable_if< std::is_arithmetic<T>::value >::type *& = enabler > void f( T ) { std::cout << "T is arithmetic" << std::endl ;} template < typename T, typename std::enable_if< std::is_pointer<T>::value >::type *& = enabler > void f( T ) { std::cout << "T is pointer" << std::endl ; } int main() { int arithmetic = 0 ; f( arithmetic ) ; // T is arithmetic int * pointer = nullptr ; f( pointer ) ; // T is pointer }
詳しい解説は上記の記事を読んでもらうとして、
これ、かなり便利そうだし、工夫すれば C++98/03 でも十分に実用に耐えそうなので、
この技法をマクロとして扱いやすくしてみました。
定義
namespace etude { template<class T> struct enabler_ { static enabler_* value; }; template<class T> enabler_<T>* enabler_<T>::value = 0; typedef enabler_<void> enabler; template<bool Cond> struct enable_if_ { typedef enabler*& type; }; template<> struct enable_if_<false> {}; #define ETUDE_ENABLE_IF( cond ) \ typename ::etude::enable_if_<(cond)>::type = ::etude::enabler::value }
無駄にテンプレートを使っているのは ODR を守るためで、
C++98/03 でこの技法を使う時に、関数のデフォルト引数*1として使ってもリンクエラーにならないよう、
きちんと実体も定義し、かつ ODR 違反にならないようテンプレートにしています。
使い方
#include <type_traits> #include <iostream> template < class T, ETUDE_ENABLE_IF(( std::is_arithmetic<T>::value )) > void f( T ) { std::cout << "arithmetic" << std::endl; } template < class T, ETUDE_ENABLE_IF(( std::is_pointer<T>::value )) > void f( T ) { std::cout << "pointer" << std::endl; } int main() { int arithmetic = 0; f( arithmetic ); int* pointer = 0; f( pointer ); }
デフォルトテンプレート引数を使わなければ、 C++98/03 でも問題なし。
#include <boost/type_traits.hpp> #include <iostream> template < class T > void f( T, ETUDE_ENABLE_IF(( boost::is_arithmetic<T>::value )) ) { std::cout << "arithmetic" << std::endl; } template < class T > void f( T, ETUDE_ENABLE_IF(( boost::is_pointer<T>::value )) ) { std::cout << "pointer" << std::endl; } int main() { int arithmetic = 0; f( arithmetic ); int* pointer = 0; f( pointer ); }
改良 ( 17:15 )
上記の実装だと、 C++98/03 版の時、 VC++ においては
f( x, 0 );
という表現がコンパイル通ってしまうので、
念には念を入れて改良してみました。
namespace etude { template<class T> struct enabler_ { typedef enabler_& type; static enabler_ value; private: struct forbid_construct_ {}; enabler_( forbid_construct_ ) {} }; /* // リンクエラーになる場合にはコメントを外す template<class T> enabler_<T> enabler_<T>::value = typename enabler_<T>::forbid_construct_(); */ typedef enabler_<void> enabler; template<bool Cond> struct enable_if_ { typedef enabler::type type; }; template<> struct enable_if_<false> {}; #define ETUDE_ENABLE_IF( cond ) \ typename ::etude::enable_if_<(cond)>::type = ::etude::enabler::value }
なお、デフォルトテンプレート引数としてのみ使う場合や、最適化オプションを ON にした場合は、
enabler::value の実体は定義する必要ないので、コメントアウトしています。
*1:テンプレートではなく、通常のデフォルト引数