とくにあぶなくないRiSKのブログ

危ないRiSKのブログだったかもしれない。本当はRiSKだけどググラビリティとか取得できるIDの都合でsscriskも使ったり。

constexprで変数宣言

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:表現が不正確ですがここでは変数と表記します。定数…