constexprの使い方

C++11以降で導入されたconstexprの便利な使い方


constexprはC++11で導入された指定子ですが、明示的にリテラル定数であることを宣言できます。constと何が違うのかというと、constはあくまでオブジェクトが変更できないことを宣言しているのみで、リテラル定数扱いとなるかどうかはコンパイラ任せです。

例えば、昨今のコンパイラの場合下記のようなコードはconstでもconstexprでも同じリテラル定数として扱われたコードが生成されると思います

int normal_i = 1;
const int const_i = -2;
constexpr int constexpr_i = 3;

int main() {
  return normal_i + const_i + constexpr_i;
}

実際、clang++ 9.0.1でコンパイルした結果のアセンブリ出力は以下のようになります

	movl	normal_i(%rip), %eax
	addl	$-2, %eax  # const_iの加算
	addl	$3, %eax   # constexpr_iの加算

ただし、constの場合、以下のようにリテラル定数でない結果で初期化した場合はリテラル定数として扱われません。対して、constexprはそのような初期化自体がコンパイル時にエラーとなるため、意図しない非リテラル化をコンパイル時に検出することが可能となります

int max(int x, int y) { return (x > y) ? x : y; }

int normal_i = 1;
const int const_i = max(-2, -3);
// constexpr int constexpr_i = max(0, 2);  // Error!
constexpr int constexpr_i = 2; 

int main() {
  return normal_i + const_i + constexpr_i;
}

例えば、上記コードではコンパイル結果は以下のようになり、const_iの値を計算するのに、実際max関数が呼び出されていることがわかります

	movl	normal_i(%rip), %eax
	addl	_ZL7const_i(%rip), %eax
	addl	$2, %eax

ちなみに#define max(x, y) ((x > y) ? x : y)とマクロにした場合は最初のコードと同じように最適化されます。


ではconstexprの初期化にはマクロしか使用できないのか?というとそうではなく、関数もconstexpr宣言することでそれが可能となります

int max(int x, int y) { return (x > y) ? x : y; }
constexpr int constexpr_max(int x, int y) { return (x > y) ? x : y; }

int normal_i = 1;
const int const_i = max(-2, -3);
constexpr int constexpr_i = constexpr_max(0, 2);  // OK!

実際はconstexprconstの意味を包含しているので、

constexpr int max(int x, int y) { return (x > y) ? x : y; }

max()を宣言しておけばOKです。 (実際std::max()はそのように宣言されています)


またconstexprによって、リテラル定数しか使用できないenum定義でもconstexpr func()を使用することが出来るようになります

constexpr int kParamOne = 1;
constexpr int kParamTwo = -2;
enum { kCoefMax = max(kParamOne, kParamTwo)};

おすすめ