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のライブラリの多くがそのテンプレートメタプログラミングを使っていますが、私たち使う側はそれを常に意識する必要はありません。使う必要が無ければ使わなくていいわけです。