constexpr な関数内では変数 *1 を宣言することができません。
// 例1 (説明用。コンパイル不可。) constexpr double sqrt(double x) { constexpr double x = s / 2.0; // NG! 変数を宣言できない! constexpr double last = 0.0; // NG! 変数を宣言できない! // : return /*...*/; }
というわけで、他のヘルパー関数を用意して引数を変数がわりに使ったりします。
// 例2 constexpr double sqrt_impl(double s, double x, double last) // ココ! { return x != last ? sqrt_impl(s, (x + s / x) / 2.0, x) : x; } constexpr double sqrt(double s) { return sqrt_impl(s, s / 2.0, 0.0); // ココ! }
あたかも sqrt_impl で s, x, last という変数を宣言し、それぞれを sqrt の s, s / 2.0, 0.0 で初期化しているかのようです。
これでいいのです。これでいいのですが…。
問題
こういうことを繰り返してるうちに、次のように考えるはずです。
- もし変数を増やす必要が生じたら、ヘルパー関数とその引数が増えてカオスにならね?
- 例で挙げた sqrt_impl の s って一切変更してないのに、再帰的に s を渡すのっておかしくね?
- スタックの無駄遣いじゃね?
- やっぱり値を保存しておきたい時ってあるよね。
解法
constexpr でも変数宣言と代入っぽいことはできる。クラスを使ってメンバを初期化すればよいのだ!
というわけで例2をクラスを使って書きかえたコードは次のとおり。
// 例3 class sqrt_t { double const s; // ここで変数宣言 public: constexpr sqrt_t(double s) // ここで変数を初期化! : s(s) {} constexpr double sqrt_impl(double x, double last) // 引数で s を持ちまわる必要がなくなった! { return x != last ? sqrt_impl((x + s / x) / 2.0, x) : x; } constexpr operator double() { return sqrt_impl(s / 2.0, 0.0); } }; constexpr double sqrt(double s) { return sqrt_t(s); } #include<iostream> int main() { constexpr double x = 123; constexpr auto s = sqrt(x); std::cout << s << std::endl; }
実行結果:
11.0905
元ネタ
*1:表現が不正確ですがここでは変数と表記します。定数…