C++11テンプレートメタプログラミング-モードマッチング

3910 ワード

C++テンプレートメタプログラミングでは、コンパイラがテンプレートの特化バージョンを選択することは、パターンマッチングを行うことに相当します.これはよく知られています.次に,この特性を用いてテンプレートメタプログラミングで最もよく用いられる基礎メタ関数IfThenElseを実現し,これを用いてタイプ選択を完了できる機能を実現する.
template struct IfThenElse;

template
struct IfThenElse
{
    using Result = Then;
};

template
struct IfThenElse
{
    using Result = Else;
};

#define __if(...) typename IfThenElse<__va_args__>::Result

IfThenElseがあれば,条件によるタイプ選択の計算を容易に行うことができる.次のように、IfThenElseを使用して、2つのタイプのメモリ領域がより大きいものを返すことができるメタ関数LargerTypeを実現します.
template
using LargerType = __if(__bool(sizeof(T) > sizeof(U)), T, U);

テンプレート特化に加えて、テンプレートメタプログラミングでモードマッチングを完了するためのツールもあります.それは、C++コンパイラによるリロード関数の選択です.
次の例では、関数の再ロードによってモードマッチングを完了する方法を示します.
C++のいくつかのタイプの間でデフォルトの移行がサポートされていることを知っています.たとえばshortのデフォルトはintに変換され、サブクラスポインタはデフォルトで親ポインタに変換され、任意のポインタタイプはvoid*にデフォルトで変換されます.次に、あるタイプが別のタイプのデフォルトに移行できるかどうかを識別するメタ関数を実装します.
解析により,このメタ関数のプロトタイプを以下のように定義した.IsConvertible :: (typename T -> typename U) -> BoolType
そのパラメータは2つのタイプのTとUであり、TがデフォルトでUに変換できる場合、メタ関数はBoolTypeを返し、そうでなければBoolTypeを返します.
次に、IsConvertibleを実現するために、コンパイラによるリロード関数の選択を用いてモードマッチングを完了する.
// “tlp/traits/IsConvertible.h”

template
struct IsConvertible
{
private:
    using  Yes = char;
    struct No { char dummy[2]; };

    static Yes test(U);
    static No  test(...);
    static T self();

public:
    using Result = BoolType;
};

#define __is_convertible(...)  typename IsConvertible<__va_args__>::Result

上記のコードでは、IsConvertibleで静的関数testの2つのリロードバージョンを定義しました.1つのパラメータタイプはUです.もう1つは、任意のタイプのパラメータ(...がC++関数パラメータ宣言に表示され、パラメータタイプに関心がないことを示す)です.次に、test関数にTを入力してみます.TがUに移行できる場合、コンパイル期間はYes test(U)バージョンを選択します.そうでなければNo test(...)バージョンを選択します.最後に、test戻りタイプのsizeofを計算すると、コンパイラがどのバージョンを選択したかを判断できます.(YesとNoはIsConvertible内部で定義された2つのタイプで、Yesのsizeof結果は1バイト、Noは2バイト、sizeofはコンパイル期間演算子です).
上記の実装では、testに直接Tのオブジェクトを転送していません.そうすると、Tにオブジェクトを生成させるオーバーヘッドに耐えなければなりません.そして、Tの構造関数について何も知らないことが重要です.ここでは、戻りタイプTの静的関数static T self()を宣言し、testに渡す.私たちが前に言った「すべては関数で、すべてはタイプです」を覚えていますか?タイプTのオブジェクトの代わりにself関数を用いてtestに転送し,コンパイル期間中に結果を得ることができ,オブジェクトの作成のオーバーヘッドを回避した.__is_convertibleを用いて,2つのタイプが互いに転換できるかどうかを判断するメタ関数を容易に実現できる.
// “tlp/traits/IsConvertible.h”

#define __is_both_convertible(T, U)     __and(__is_convertible(T, U), __is_convertible(U, T))

上記のコードの__and()は,我々が先に紹介した2つのBoolTypeに対して論理的および演算を行うメタ関数である.
次のように使用できます.
__is_convertible(char, int)   //   __true()
__is_convertible(char, void*) //   __false()
__is_convertible(char*, void*)//   __true()

struct Base{};
struct Derived : Base {};

__is_convertible(Base*, Derived*) //   __false()
__is_convertible(Derived*, Base*) //   __true()

関数の組合せにより,1つのタイプが別の親であるか否かを判断するために__is_base_of()を実現することもできる.
// "tlp/traits/IsBaseOf.h"

#define __is_base_of(T, U)                      \
__and(__is_convertible(const U*, const T*)          \
      __and(__not(__is_eq(const T*, const void*)),          \
            __not(__is_eq(const T, const U))))

上記のように、タイプTがタイプUの親であることを定義すると、const U*const T*に転換することができるが、const T*const void*ではなく、const Tconst Uは同じタイプではない.
再帰
C++11テンプレートメタプログラミング-ディレクトリを返します