constexpr
新しいコンパイル時定数
C言語ではconst
を使用して定数を作ることができますが、C++ではconstexpr
という新しい定数を作ることができます。
const int ci1 = 1;
constexpr int ci2 = 2;
//#defineによる定数
#define 3
constexpr定数はconst定数とマクロ(#define
)による定数の両方の特徴を持ちます。
マクロ定数はコンパイル時に値が決定され、実行ファイルに直接その値が埋め込まれるコンパイル時定数です。
const定数はプログラムの実行時に値が決定され、値が変更できない以外は変数と同じ振る舞いをする実行時定数です。
実行時定数はプログラムの実行の度に計算が必要ですが、コンパイル時定数はコンパイル時に計算を行い、プログラムの実行時には値を読み込むだけなので、処理は高速になります。
constexpr定数はコンパイル時定数で、かつconstのようにデータ型を持てる定数です。
複雑な計算をコンパイル時に行えるので、プログラムの実行速度の向上が期待できます。
コンパイル時定数なので、例えば配列の要素数の指定などにもconstexpr定数を使用することができます。
struct S {
unsigned int n;
};
int f() { return 0; }
int main()
{
constexpr int n1 = 0;
constexpr int n2 = 1 + 2; //リテラル同士の演算もコンパイル時定数
const int n3 = f(); //関数の戻り値は実行時定数にできるが
//constexpr int n4 = f(); //コンパイル時定数にはできない
//後述するconstexpr関数なら可能
S s1 = { 5 };
const S s2 = { 5 };
constexpr S s3 = { 5 };
//int arr1[s1.n]; //NG
//int arr2[s2.n]; //NG
int arr3[s3.n]; //OK
}
1 + 2;
などのリテラル同士の演算は常に同じ結果になります。
こういった演算はコンパイル時に計算してしまったほうが実行時にコストがかからないので、コンパイル時定数となります。
このような式を定数式といいます。
関数f
は常に0
を返すのでこれもコンパイル時定数にできそうに思えますが、これは実行時定数にしかできません。
後述するconstexpr関数ならコンパイル時定数にすることができます。
constexpr定数にできるのはリテラル型のみです。
リテラル型とは、算術型、列挙型、ポインタ型、nullptrと、それらへの参照、それらの配列、それらのみをメンバに持つクラスや構造体です。
(クラスに関しては実際にはもう少し細かい要件があります)
struct S {
std::string str; //stringクラスはリテラル型ではない
};
int main()
{
const S s1; //OK
constexpr S s2; //NG
}
コンパイル時定数は値を実行ファイル(オブジェクトファイル)に埋め込むので、値を変更するには再度コンパイルし直す必要があります。
これはソースコードの分割などを行う場合に注意しないとバグの原因になることがあります。
const定数は実行時定数ですが、整数リテラルを格納する場合はコンパイル時定数となります。
int f() { return 3; }
int main() {
const int c1 = 3;
int arr1[c1]; //OK
const int c2 = f();
//int arr2[c2]; //コンパイル時定数ではないのでNG
}
constexpr関数
constexpr
キーワードは関数にも指定することができます。
この関数はコンパイル時とプログラム実行時の両方で呼び出せる関数となります。
constexpr int max(int x, int y)
{
return x > y ? x : y;
}
int main()
{
int m1 = max(5, 2);
const int m2 = max(5, 2);
constexpr int m3 = max(5, 2);
}
constexpr関数がコンパイル時に実行されるか、プログラム実行時に実行されるかは関数の左辺(戻り値を受け取る変数)によって変わります。
受け取る変数がconstexpr変数ならコンパイル時に実行され、それ以外ならばプログラム実行時に実行されます。
上記コードでは、コンパイル時にconstexpr版のmax関数が実行され、m3
の値が決定されます。
m1
とm2
の値はプログラムの実行時に決定され、通常版のmax関数がプログラム実行時に呼び出されます。
コンパイル時に実行するには、引数の値もコンパイル時定数である必要があります。
非コンパイル定数を渡した場合はコンパイルエラーです。
C++11
constexprが導入されたのはC++11(2011年)というバージョンからで、このときのconstexpr関数には強い制限がありました。
constexpr関数内に書ける文はreturn文のみとなります。
つまりあまり複雑な処理は書けません。
(using宣言やusingディレクティブなどは書けます)
return文の一行しか記述できないので、if文なども使用できません。
自分自身の再帰呼び出しは可能です。
また、引数と戻り値の型はリテラル型に限られ、非const参照を指定することはできません。
C++14以降
C++14(2014年)のバージョンからは、いくつかの制限が撤廃されています。
return文のみを記述できるという制限がなくなり、変数の使用やif文、switch文、for文、while文(do while文)の使用が可能になりました。
これにより複雑な値の計算も容易になります。
ただし変数は必ず宣言と同時に初期化が必要で、static変数は使用できません。
また、goto文は許可されていません。
戻り値の型にvoid型を使用できるようになりました。
戻り値と引数に非const参照を指定でるようになりました。
これにより、引数の値を(参照を通して)書き換えることが可能になりました。
他にもいくつか変更点はありますが、それらはクラスという機能に関係するもので、まだ説明していないため省略します。
参照透過性
constexpr関数は通常の関数に比べて制限がありますが、その制限により定数を返す以外の処理は行われない(行えない)ことが保証されています。
通常の関数はコンパイル時に実行できないため、constexpr関数内から実行できる関数はconstexpr関数のみです。
標準関数であってもconstexpr関数でなければ呼び出すことは出来ません。
(constexprが付けられた標準関数はいくつかあります)
constexpr関数は、「ある決まった値を渡すと、必ず同じ値を返し、それ以外の不必要な処理は行わない」という関数です。
例えば上記のconstexpr関数max
は、引数に「5, 2」を渡せば必ず「5」が返ってきます。
これはその関数呼び出しの箇所を「5」という数値リテラルに置き換えても、プログラムの動作には影響しないことを意味します。
これを参照透過性といいます。
(値がどこを参照しているかを考える必要がないこと。値が「5」であるならそれ以上は考えなくて良い)
言い換えれば関数の呼び出しに副作用がありません。
int g = 0;
void unknown_function()
{
g = 10;
}
int maxA(int x, int y)
{
unknown_function(); //何か余計な処理かもしれない
return x > y ? x : y;
}
constexpr int maxB(int x, int y)
{
//↓を書くとコンパイルエラー
//unknown_function();
return x > y ? x : y;
}
int main()
{
//以下の二つは等価ではない
const int a = maxA(5, 2);
const int b = 5;
//以下の二つは等価である
constexpr int c = maxB(5, 2);
constexpr int d = 5;
}
上記の関数maxA
とmaxB
は、コード内部を知らないプログラマからすれば同じ処理に見えますが、関数maxA
のほうは「値を比較して大きいほうを返す」以外の処理も行っています。
つまり関数maxA
とmaxB
を実行した場合とでは、プログラムの動作が異なる可能性があります。
constexpr関数は、関数の戻り値をコード中にそのまま書くのと同じ扱いができる関数ということです。