再帰テンプレートでN個のポインタを付けるメタ関数を作る
テンプレートメタプログラミングの練習と定着のために再帰テンプレートを使ってN個のポインタをくっつけるメタ関数を作ります。
参考書
C++テンプレートテクニック第2版
コード
前述したC++テンプレートテクニックのP106にあるサンプルに using
を使用して任意の型を指定できる別名型宣言を追加したものです。
問題があれば記事は消去します。
#include <iostream> #include <utility> // 再帰処理を使用して、T型の変数にN個のポインタを付与するメタ関数 template <int N> struct int_ {}; template <bool cond> struct bool_ {}; // tag despatch struct cont {}; // continue struct end {}; // end template < bool Cond > auto is_end (bool_<Cond>) -> typename std::conditional<Cond, end, cont>::type; template < class T, int N > auto add_pointers_impl( T, int_<N>, cont ) -> decltype( add_pointers_impl( std::declval<T*>(), int_<N - 1>(), std::declval< decltype( is_end( bool_<N - 1 == 0>() ) )>() ) ); template < class T, int N > auto add_pointers_impl( T, int_<N>, end ) -> T; template < class T, int N > auto add_pointers( T, int_<N> ) -> decltype( add_pointers_impl ( std::declval<T>(), int_<N>(), std::declval< decltype( is_end( bool_<N == 0>() ) ) >() ) ); template < class T, int N > using pow_pointer = decltype( add_pointers( std::declval<T>(), int_<N>() ) ); int main() { int* p; pow_pointer< int, 2 > dp = &p; }
解説
pow_pointer
に型と付加したいポインタの数を指定するとそれに対応した型が返ってきます。
pow_pointer
が最初に呼び出しているのは add_pointers
という関数になります。
この関数には具体的な実装がありませんが、この関数を decltype
で評価することによって返り値の型を得ることができるようになります。したがって、テンプレートメタプログラミングの文脈で使用するにかぎっては実装はいりません。
add_pointers
は処理を add_pointer_impl
に委譲します。
処理を委譲する際、型の後置宣言を使用しています。
これは参考書によると、複雑な型宣言をするから、らしい。
add_pointer_impl
は引数を3つ取るので、add_pointers
の返り値にも3つの引数を与える必要があります。
この3つの引数は、ポインタを付与したい型情報、再帰する回数(N)と再帰条件に使用するタグの情報です。
再帰では、再帰終了条件を付ける必要があります。これがないと永遠に関数が呼び出され続けて、スタックオーバーフローを引き起こします。
ここでは、第3引数に空の構造体の名前を入れることで条件を区別しています。また、このように構造体の名前を使用して処理を分岐することをタグディスパッチといいます。
add_pointer_impl
では第3引数が cont
なら続ける、 end
なら型を返して終了です。
もっと詳しく、返り値の宣言を見てみます。
decltype( add_pointers_impl ( std::declval<T>(), int_<N>(), std::declval< decltype( is_end( bool_<N == 0>() ) ) >() ) );
decltype
を使用して add_pointers_impl
の型情報を抜き出す形です。
この際、型Tのインスタンスを作るために std::declval<T>()
という関数を使用しています。
これは型Tにデフォルトコンストラクタが存在しなくても型情報を渡せるようにするために存在します。(中では T&&
を返しているようですがあんまりよくわかってない。)
また、再帰条件の判定には is_end(bool_<T>())
が渡されています。
この実装は以下のようになります。
template < bool Cond > auto is_end (bool_<Cond>) -> typename std::conditional<Cond, end, cont>::type;
最初に宣言した bool_
を使用して真理値を渡し、true
ならは end
, false
ならばcont
を返します。
その際、std::conditionalを使用しています。
このように N が 0 になると、 最後に
template < class T, int N > auto add_pointers_impl( T, int_<N>, end ) -> T;
型情報が返され、結果的に T******...
が手に入ります。
まとめ
- decltypeとdeclvalを使用して型を操作するメタ関数を簡単に作れる。
- 終了条件の判定には、タグディスパッチを使ったメタ関数のオーバーロードが使える。