meryngii.neta

今日も新たな"ネタ"を求めて。

constexprの再帰を認めるべき

C++0xではconstexprによってコンパイル時に扱える問題の範囲が広がります。例えば、コンパイル時に評価できるユーザ定義型(複素数型など)をROM領域に置くことができるようになります。
現状の仕様でconstexprで定義された関数は再帰ができません。コンパイル時は副作用が認められないので、再帰が無ければ反復処理を直接に表現することができないのです。
しかし、もしconstexprで再帰が認められないとしても、func0, func1, func2, ...というように、再帰を力技で生成するコードが書けます。

constexpr double func0(double x) { /* ... */}
constexpr double func1(double x) { /* call for func0 */ }
constexpr double func2(double x) { /* call for func1 */ }
/* ... */

さらにこれをプリプロセッサで生成すれば、再帰が無くても(擬似的ではありますが)反復処理を表現できるのです。
しかし、これはとてもエレガントとは言えません。プリプロセッサは廃止されるべき存在なのです。だったら、こういうコードを書かずに済むように、最初から再帰を認めるべきではないでしょうか。

コンパイル時と実行時

constexprはコンパイル時にも実行時にも評価することができます。再帰する場合もこの二つを考える必要があります。
実行時評価はただ単に実行するだけです。constexprキーワードを取ってしまえば現状でも実行できます。現代のコンパイラなら、末尾再帰は容易に最適化してくれるでしょう。
コンパイル時評価は再帰したテンプレートと同じことです。浮動小数点についても対応する必要がありますが、コンパイル時の計算自体はすでに可能なので特に問題ありません。

サンプル

サンプルを付けておきます。平方根を求めるアルゴリズムです。

/*constexpr*/ double SqrtHelper(double x, double a, int n)
{
	return n == 0 ? a : SqrtHelper(x, (x / a + a) / 2.0, n - 1);
}

/*constexpr*/ double Sqrt(double x)
{
	return SqrtHelper(x, x, 20);
}

/*constexpr*/ double root2 = Sqrt(2.0); // 1.41421...

問題点

constexprの再帰には問題点もあります。
まず、本来の目的からは少し逸脱しています。しかし、もしそういう考え方でテンプレートの機能を制約していたら、C++ジェネリックプログラミング*1は発展しなかったのではないでしょうか。プログラマの野心をかきたてることは重要だと思うのです。
また、再帰には関数型プログラミングの雰囲気があり、C++の感覚に合わないのも確かです。しかし、テンプレートメタプログラミング再帰のオンパレードです。また、Boostのライブラリの多くがそのテンプレートメタプログラミングを使っていますが、私たち使う側はそれを常に意識する必要はありません。使う必要が無ければ使わなくていいわけです。

おまけ

constexprの再帰が認められるかどうかに関わらず、テンプレート引数で浮動小数点数を渡せるようにしていただきたいです。整数型が認められて浮動小数点数はダメという理由が思い当たらないです。

*1:テンプレートは最初ただ単にコンテナを書くための道具として考案されました。また、コンセプトはもともとコンパイルエラーを簡潔にするために作られましたが、ジェネリックプログラミングを支える重要機能になる予定です。