久しぶりにメタプログラミングをしようと思う。特に、has_xxxをC++11で書くことに挑戦してみる。has_xxxとは、ある型がネストされた名前を持っているかどうかを確認するメタ関数である。名前は、型、もしくは非型のどちらかになる。
まず、型の方から。
namespace detail { template < typename T, typename U = typename T::type > std::true_type check_type( int ) ; template < typename T > std::false_type check_type( long ) ; } template < typename T > struct has_type : decltype( detail::check_type<T>( 0 ) ) { } ;
なんと、たったのこれだけのコードで、UnaryTypeTrait要求を満たしたメタ関数が書ける。
次に非型の方。
namespace detail { template < typename T, decltype(&T::value) = &T::value > std::true_type check_value( int ) ; template < typename T > std::false_type check_value( long ) ; } template < typename T > struct has_value : decltype( detail::check_value<T>( 0 ) ) { } ;
これまた非常に簡単だ。もちろん、ちゃんとUnaryTypeTrait要求を満たしたメタ関数である。例えば以下のように使う。
struct Yes { using type = int ; static constexpr int value = 0 ; } ; struct Yes_for_value { void value() { } // 非型には関数名も含まれる } ; struct No { } ; int main() { has_type< Yes >::value ; // true has_value< Yes >::value ; // true has_type< Yes_for_value >::value ; // false has_value< Yes_for_value >::value ; // true has_value< int >::value ; // false }
何故こんなに簡単になったのかというと、decltypeの存在が大きい。C++11以前では、sizeofを利用した変なコードを書かなければならなかったのだが、C++11では、decltypeで式の型を直接得られるため、std::true_typeやstd::false_typeを返す関数を書き、decltypeの中で関数呼び出し式を書き、そのdecltypeをそのままベースクラスとして書くだけでよくなったのだ。
しかし、これだけ簡単になってしまうと、昔が懐かしく感じる。もはや、C++のメタプログラミングは、規格をつつきまわさずとも、だれにでも書けるほど簡単になったのだ。
ちなみに、ある型に、ネストされた型名xxxがあるかどうかというメタ関数は、標準ライブラリの実装に必要である。たとえば、std::allocator_traitsなどで必要になる。
追記、SFINAEをテンプレートパラメーターで行うように修正。また、リファレンス型にも対応。ellipsisではなくlongを使うように変更。
追記:int型の引数を渡している理由は、オーバーロード解決を行わせるためである。typename U = typename T::typeというデフォルト実引数付きのテンプレート仮引数で、SFINAEのトリックを使っている。もし、型Tにネストされた型名typeがなければ、つまり、substitionが失敗すれば、そのテンプレートは候補関数にはならない。ただし、substitution自体はオーバーロード解決には影響しないので、別の方法でオーバーロードの優先度を指定する必要がある。そのため、実引数にint型の0を渡し、仮引数にint型と、...を指定して、オーバーロード解決を行わせている。これは、オーバーロード解決でより優先されれば何でもいい。だから、intとlongでもよい。
No comments:
Post a Comment