新しい enable_if を楽に使うマクロ ETUDE_ENABLE_IF を作ってみた

元ネタ: 本の虫「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:テンプレートではなく、通常のデフォルト引数