技術ブログとか

C/C++/C#/DirectX/Effect

template

いくら使ってもまともに使えてる気がしないので、知っていることを書き溜めてみる。

 

classや関数を定義するときに、中身はほぼ同じなのに型だけ異なるものを定義したいことは非常に多い。

このような場合に、今までは名前にsuffixなどを付けて複数用意して使い分けていた。

// float型の座標構造体
struct Point2D_F
{
    float x, y;
};

// double型の座標構造体
struct Point2D_D
{
    double x, y;
};

今回の場合だとまだ2通りだから良いが、全ての数値型を対象にした場合などはもっと多くの定義を用意しなければならない。

こういう場合にtemplateが役に立つ。

template <typename T>
struct Point2D
{
    T x, y;
};

// int型の座標構造体をつくる
Point2D<int> posInt;
// double型の座標構造体をつくる
Point2D<double> posDouble;

<typename T><class T>と書いてもかまわない、どちらも同じ意味である。

また、Tの部分もただの仮引数であるため、実際はTxでもhogeでも何でもかまわない。

このように、1つの定義のなかの型名を仮の型名に置き換えて、複数の型のあいだで使いまわすことができる。

複数の型を置き換えることもできる。

template <typename T, typename U>
struct Status
{
    T height;
    U weight;
};

// 身長をint型、体重をdouble型で表現
Status<int, double> st;

関数templateの場合に型が推測可能なとき、呼び出し時の型を省略することもできる。

classやstructでは推測が難しいためできない。

template <typename T>
void func(T val)
{
    val *= 2;
}

int main()
{
    int integerNum = 1;

    // 普通に呼び出す
    func<int>(integerNum);
    // 呼び出すときに型を省略
    func(integerNum);
}

 

一部の型だけ異なる実装をしたい場合、templateの特殊化というものが利用できる。

template <typename T>
struct Hoge
{
    T num;
};

// int型の場合はちょっとリッチにしよう
template <>
struct Hoge<int>
{
    T num1, num2;
};

よく悪者にされるstd::vector<bool>なんかはこれで特殊化されてる。

templateがclassなどの中で定義された場合は特殊化を利用できない。

特殊化するtemplateのスコープは必ずnamespaceのレベルにある必要がある。

 

また、関数templateではできず、class(struct) templateで可能なものとして、templateの部分特殊化がある。

template <typename T>
struct Change
{
    // T型へのポインタ
    T* p;
};

template <typename T>
struct Change<T*>
{
    // T*型からポインタを1つ外した先の型
    T num;
};

// int**型なのでint*型のメンバをもつ
Change<int**> hoge;

ポインタのつけ外しだけではなく、constとかstaticとか参照とか配列とかいける。

 

仮引数には型名だけではなく整数の定数値も取ることができる。

template <int i>
struct Hoge
{
    static const int Num = i;
};

// Numの値は1234
Hoge<1234>::Num;

int n = 10;
// nは変数なのでエラー!
Hoge<n>::Num;

仮引数の値は定数値なので配列の大きさとかにも利用できる。

更にこいつとenumとかを利用してやることでテンプレートメタプログラミングってやつができる。

つまるところ、実行時に定数の計算をしてしまうっていうものだ。

// よくあるフィボナッチ数の再帰
template <unsigned int N>
struct Fib
{
    enum { Num = Fib<N - 1>::Num + Fib<N - 2>::Num };
};

template <>
struct Fib<0>
{
    enum { Num = 0 };
};

template <>
struct Fib<1>
{
    enum { Num = 1 };
};

int main()
{
    // 結果は34
    cout << Fib<9>::Num << endl;
}

このアルゴリズムはけして優れたものではないけど、まあそこはご愛嬌。

結果の34はコンパイル時に計算されているため、定数値として利用できる。

再帰だけじゃなくて条件分岐も書ける

// trueの場合はTがセットされる
template <bool C, int T, int F>
struct If
{
    enum { Num = T};
};

// falseの場合はFがセットされる
template <int T, int F>
struct If<false, T, F>
{
    enum { Num = F};
};

// true/falseを1/0に置き換える
template <bool Test>
struct Cast1or0
{
    enum { Num = If<Test, 1, 0>::Num};
};

int main()
{
    // 2 == 3 の結果は 0
    cout << Cast1or0< 2 == 3 >::Num << endl;
}

2, 3番目の引数をtypenameに変えてやれば型を分岐させることもできる。

 

あとはC++11でextern templateとか、特殊化や部分特殊化で型を制限するとかあるけど、ずらずら長くなるのでとりあえずこのくらいで。