constexpr を使うべき5の理由 - その1
constexpr を使うべき5の理由
「なぜあなたは constexpr を使うべきか?」
「そもそも constexpr とは何か」という基本的な部分から始めて、
どうして C++er が constexpr を使わなければならないか、日にちを分けて5つのケースを書いてみます。
constexpr を既にバリバリ使っているというあなたにはまったく物足りないかもしれませんが、
改めてのおさらいということで読んでいただくのも一興かと存じます。
1.定数を明示的にコンパイル時定数にする
以下のコードは、定数 x のメンバを配列のサイズに指定している。
struct X { int n; }; int main() { const X x = {10}; int a[x.n] = {1}; }
GCC 4.8 でこのコードは、以下のような警告とエラーになる。
warning: ISO C++ forbids variable length array ‘a’ [-Wvla] error: variable-sized object ‘a’ may not be initialized
a が可変長配列になっているため警告が出ている。
また、可変長配列のリスト初期化はできないためエラーになっている。
x.n はもちろん immutable な定数であるはずだが、なぜこうなるか。
C++ でいうところの「定数」は大きく二つに分けられる。
「コンパイル時定数」と「実行時定数」である。
コンパイル時定数はその名の通りコンパイル時に値が決定されると規格によって定められた定数である。
固定長配列のサイズやテンプレート引数に指定することができるのは、コンパイル時定数のみである。
C++ において、非 constexpr 変数がコンパイル時定数になるのは、const 修飾された整数型(enum を含む)のみである。
実行時定数は、コンパイル時定数以外のすべての定数である。
通常は実行時に計算され、コンパイラの最適化によってはコンパイル時に計算されることもあるが、その場合でもコンパイル時定数とはみなされない。
上記コードにおける x は実行時定数である。
だから x のメンバを固定長配列のサイズとすることはできない。
このコードをコンパイルできるようにするのは簡単だ。
const を constexpr に書き換えるだけである。
struct X { int n; }; int main() { constexpr X x = {10}; // <- コンパイル時定数! int a[x.n] = {1}; }
const 修飾された型の変数は、型が整数型であればコンパイル時定数になるし、そうでなければ実行時定数になる。
文脈によって意味が変わってしまう。
しかし constexpr 指定すれば、それは明示的にコンパイル時定数以外の何者でもない。
もしコンパイル時定数がほしいところで非定数式が初期化に使われた場合は、定義時点でエラーにすることができる。
このため C++11 環境において定数を定義したいときは、できるかぎり const 修飾よりも(もちろんマクロよりも) constexpr 指定を使うべきです。