メタ関数の実装技法 - その1
そもそもメタ関数とは
(参照:More C++ Idioms/メタ関数(Metafunction) - Wikibooks)
一般に C++ でメタ関数とは、与えられたテンプレート引数から、結果として何か型やコンパイル時定数を返す(定義する)ようなクラステンプレートをいう。
代表的な例としては
ところで、Boost.MPL などでは(統一性のために)テンプレート引数が型名のみになるように設計されているが、
標準ライブラリでは(直観的な記述のために)真理値を指定するテンプレート引数をコンパイル時定数でとるなど、
一口にメタ関数と言ってもその厳密な範囲は設計方針によって多少の差異がある。
(もっとも、Boost.MPL ではメタ関数 xxx に対してコンパイル時定数をとるバージョンの xxx_c を用意していたりするが)
-
- boost::mpl::if_
namespace std { template<typename C, class T , class F> struct if_; }
-
- std::conditional
namespace std { template<bool C, class T, class F> struct conditional; }
メタ関数について概ね共通しているコンベンションは、
-
- 結果として型を返す場合は型名 type を定義する。
- 結果として値を返す場合はコンパイル時定数 value を定義する。
この2点だろう。
また std::iterator_traits のように、複数の型名を結果として定義するようなトレイトもメタ関数と呼ばれる場合があるが、
特定の用途ではなく汎用的に用いたい場合は上に挙げたコンベンションに従うべきだろう。
この記事では、さしあたってごく基本的なメタ関数の定義の仕方について書くものとする。
簡単な条件の組み合わせのメタ関数を書く
まずは
ここでは試しに「型が符号無し整数型であるか」判定するプレディケートを書く。
前提として、std::is_unsigned は算術型が符号無しであるか判定するが、これは浮動小数点型も含むため整数型に絞る必要がある。
-
- is_uint
template<typename T> struct is_uint // 整数型かつ符号無し : std::integral_constant<bool, std::is_integral<T>::value && std::is_unsigned<T>::value> {};
自分で value メンバを定義しても概ね同じことだが、継承を使っているのには理由がある。
なぜなら
という形で規格に定められており、自分でメタ関数を書く場合でもこれに合わせるべきだからである。
それによって value だけでなく type, value_type メンバも自動的に定義される。
(なお、std::true_type/false_type は std::integral_constant
簡単な特殊化によってメタ関数を書く
たとえば、型が特定のクラステンプレート X のインスタンスであるかを判定したいときがある。
-
- is_x_class
template<typename T> class X {}; template<typename T> struct is_x_class : std::false_type; template<typename T> struct is_x_class<X<T> > : std::true_type; template<typename T> struct is_x_class<T const> : is_x_class<T>; template<typename T> struct is_x_class<T volatile> : is_x_class<T>; template<typename T> struct is_x_class<T const volatile> : is_x_class<T>;
この場合は(部分)特殊化によってプレディケートを書くことができる。
デフォルトでは false であり、部分特殊化が X
またこのような場合、X
そのようなときは、cv修飾された型に対しては非修飾型についての結果を返すように丸投げすればうまくいく。
こうすることで、後に別のクラス X2 を条件に追加したくなった場合でも、X2 の部分特殊化だけを追加すればよい。
enable_if によってメタ関数を書く
整数型のサイズを返すメタ関数を考える。
-
- integral_size
template<typename T, typename = void> struct integral_size; template<typename T> struct integral_size<T, typename std::enable_if<std::is_integral<T>::value>::type> : std::integral_constant<std::size_t, sizeof(T)> {};
enable_if については、今更説明の必要もないだろう。
このような宣言をする場合、ユーザに enable_if で特殊化するための口を公開しているのと同じである。
別の場所で異なる型について integral_size を使えるにしようとした場合、ユーザは通常の特殊化によらない自由な条件式を記述できる。
とはいえ、それは良し悪しであって、こうした方法が本当に必要になるケースというのは実際のところあまりお目に掛かったことがない。