2013年12月14日土曜日

初めてテンプレートメタプログラミングに触れてみた

初めてテンプレートメタプログラミングというものに触れた。 正直今でも何が起こっているのかは理解できてない。 以下はテンプレートメタプログラミングで整数の整数乗を求めるプログラム。 尚、型は全てint。

#include <iostream>

// default
template< int base, int iexp, bool = iexp >= 0 >
struct Pow{
	// fallback for iexp < 0
	static const int value = 0;
};

// specialization for iexp >= 0
template< int base, int iexp >
struct Pow< base, iexp, true >{
	// base ^ iexp = base * base ^ ( iexp - 1 )
	static const int value = base * Pow< base, iexp - 1 >::value;
};

// specialization for iexp == 0
template< int base >
struct Pow< base, 0, true >{
	// base ^ 0 = 1
	static const int value = 1;
};

int main( void ){
	std::cout << "2 ^ -2 = " << Pow< 2, -2 >::value << std::endl;
	std::cout << "2 ^ -1 = " << Pow< 2, -1 >::value << std::endl;
	std::cout << "2 ^  0 = " << Pow< 2,  0 >::value << std::endl;
	std::cout << "2 ^  1 = " << Pow< 2,  1 >::value << std::endl;
	std::cout << "2 ^  2 = " << Pow< 2,  2 >::value << std::endl;

	return 0;
}
コンパイル時定数では浮動小数点実数は扱えない(constexpr等の例外を除く)らしい(嘘をついているかも)ので、 扱う変数valueの型はstatic const int型としている。

そのため、負数乗は全て0とした。 iexpが0未満の時は全てデフォルトの定義になる。

0乗は1、自然数乗は再帰を行うように定義している。

よく分かっていないのは bool = iexp >= 0 という式。 型だけ書いて変数名(識別子)を書かないのはテンプレートだから許されるのだろうか。 おそらく、関数のプロトタイプ宣言をする時に、形名だけを書くのと似たようなものだろうと思っている。 どちらも同じ「引数」だし。

あと、2つ目の定義と3つ目の定義のどちらを使用するのかをコンパイラがどうやって決めているのかもよく分かんない。 C++怖い。

あ、間違いあったら教えて下さい。

2013.12.16追記 ※constexprが使える環境ならconstexprを使うべきと思われる。
このコードはMSVC++2008で書いたのでconstexprは使えなかった。

2 件のコメント:

匿名 さんのコメント...

呼び出し側が指定しているテンプレート引数が2つしかないので、まずdefaultのところでiexpが非負数かどうかが評価され、非負数であれば3番目の引数がtrueになるので特殊化が行われて2番目の定義に進みます。
2番めの定義で再帰が行われ、iexpが0に達すると3番目の定義に進みます。

コンパイラは、呼び出し側と特殊化された(つまりstruct Pow< base, 0, true >のようにテンプレートの引数が固定された)テンプレートで、引数が一致するものを優先的に選ぶようになっています。
また、引数が足りない場合には通常の関数呼び出しと同様にデフォルト引数を使うことができます。
そして、デフォルト引数はそれより前の引数から演算で導くことができます。
これがコンパイラ側の絡繰りです。

ね、簡単でしょう?

Agate さんのコメント...

ご教授ありがとうございます。

Powの実体化が行われる際に、
既にその時点で3種類の定義をコンパイラが把握しており、
尚且つ int iexp よりも 0 の方がマッチングを行う上で優先度が高いのですね。
int iexp と 0 がマッチングを行う上で等価なのでは、と思っていました。