- cpp11[meta cpp]
constexpr
は、汎用的に定数式を表現するための機能である。
constexpr
は、「constant expression (定数式)」の略語である。
この機能を使用することで、コンパイル時に値が決定する定数、コンパイル時に実行される関数、コンパイル時にリテラルとして振る舞うクラスを定義できる。
定数式の例として、パラメータの値を2乗して返す関数square()
は、以下のように記述する:
#include <cassert>
constexpr int square(int x)
{
return x * x;
}
int main()
{
constexpr int compile_time_result = square(3);
static_assert(compile_time_result == 9, "result must be 9");
int runtime_result = square(3);
assert(runtime_result == 9);
}
関数square()
の先頭にconstexpr
キーワードを付けることで、その関数は、コンパイル時と実行時、両方で呼び出せる関数となる。
constexpr
関数がコンパイル時に呼び出されるか、実行時に呼び出されるかは、その関数の結果を受け取る、左辺によって決定される。引数が定数でかつ、左辺がconstexpr
修飾された変数であれば、右辺の関数はコンパイル時に呼び出される。そうでなければ、関数は実行時に呼び出される。
constexpr
変数は、書き換えることができない定数となる。
constexpr
関数もまた、その内部で変数の書き換えはできず、戻り値の型をvoid
にすることもできない。変数の書き換えをせずに、計算した結果を返す必要がある。また、constexpr
関数には、その本体において、return
文ひとつのみで処理を行わなければならない、という制限がある。そのため、条件分岐にはif
文の代わりに条件演算子を、ループにはwhile
文やfor
文の代わりに再帰を使用する必要がある。
constexpr int min(int a, int b)
{
// 条件分岐には条件演算子?:を使用する
return a < b ? a : b;
}
constexpr int factorial(int n)
{
// ループには再帰を使用する
return n == 0 ? 1 : n * factorial(n - 1);
}
int main()
{
constexpr int min_val = min(2, 3);
static_assert(min_val == 2, "result must be 2");
constexpr int factorial_val = factorial(5);
static_assert(factorial_val == 120, "result muse be 120");
}
constexpr
にできる変数の型は、リテラル型に分類される必要がある。これには、整数型、浮動小数点数、リテラル型の配列、リテラル型のみをメンバ変数として持つクラスなどが含まれる。
ユーザー定義のクラスで、コンストラクタを定義する場合には、constexpr
をコンストラクタの宣言に付ける。メンバ関数も、コンパイル時に呼び出す必要がある場合には、constexpr
修飾する。この時C++14でも適格なコードであるためにはconst
メンバ関数である必要がある。メンバ変数の宣言にconstexpr
を付ける必要はない。
class Integer {
int value_;
public:
constexpr Integer(int value)
: value_(value) {}
constexpr int get() const
{ return value_; }
};
int main()
{
constexpr Integer x = 3;
static_assert(x.get() == 3, "x value must be 3");
}
constexpr
関数の戻り値の型、およびパラメータの型は、リテラル型でなければならないconstexpr
関数の戻り値の型、およびパラメータの型は、非const
参照にはできないconstexpr
関数の本体は、以下の要素だけを含むことができる:- ヌル文
static_assert
宣言- 型の別名定義
- using宣言と、usingディレクティブ
- 唯一の
return
文
constexpr
関数は暗黙的にインラインとなるconstexpr
関数を再帰的に呼び出せる深さは、512回以上であることが、コンパイラに推奨されるconstexpr
関数は数学的に結果が定義された計算式、および結果が値の表現範囲内である計算式を記述できる- つまり、整数をゼロ割りした場合は、プログラムは不適格となる
main()
関数は、constexpr
関数として定義できないthrow
文は書けるが、それがコンパイル時に評価された場合には、プログラムは不適格となる。
// コンパイル時にtrueを渡すと、コンパイルエラーになる
constexpr int f(bool b)
{ return b ? throw -1 : 0; }
- コンストラクタを明示的に定義する場合には、コンストラクタを
constexpr
修飾する - 暗黙的に定義されるコンストラクタは、自動的に
constexpr
修飾される constexpr
コンストラクタのパラメータの型は、リテラル型でなければならない。constexpr
コンストラクタには、関数try
ブロックは使用できないconstexpr
コンストラクタの本体は、空でなければならない- 仮想基底クラスを持ってはならない
- 基底クラス、および非静的メンバ変数は、メンバ初期化子を使用して初期化しなければならない
constexpr
メンバ関数は自動的にconst
修飾され、明示的なconst
修飾はできない- 仮想関数は、
constexpr
関数として定義できない
constexpr
関数での浮動小数点数は、コンパイル時に実行するとコンパイル環境で計算が行われ、実行時に実行すると実行環境で計算が行われる。これによって、コンパイル時と実行時で、結果が異なる可能性がある。
constexpr
関数では、コンパイル時にはエラー報告にstatic_assert
、実行時にはthrow
文やassert()
関数マクロなどを使用する必要があり、標準機能の範囲内では、コンパイル時と実行時で、エラー報告を統一的に行うことが難しい。
そういった問題を解決するために、ユーザーコミュニティで、エラー報告を統一的に行う仕組みが作られている。ここでは、その問題に取り組んでいる場所へのリンクを記載する。
並行プログラミングについて考えよう。スレッド間の排他処理を行うミューテックスをグローバル変数に保持したとき、ミューテックスの初期化が、スレッドの開始よりも必ず先に行われてほしい。
extern std::mutex m;
std::thread t1{job1};
std::thread t2{job2};
そんな状況のために、std::mutex
クラスのデフォルトコンストラクタには、constexpr
修飾が行われている。そうすることで、その型の非ローカル変数を作ったときに、その変数は他の任意のスレッド開始よりも先に行われることが保証される。
constexpr
では、new
演算子やmalloc()
関数が使用できないために、可変長の配列や文字列を扱うことが難しい。
C++11段階の標準ライブラリでも、そのような機能は標準では提供されていない。
こういった問題を解決するために、ユーザーコミュニティでそのような機能が作られている。ここでは、その問題に取り組んでいる場所へのリンクを記載する。
constexpr
で文字列が扱えることにより、printf()
のフォーマット文字列や、正規表現の間違いをコンパイル時に検出することができるようになる。
コンパイラによっては、コンパイルオプションでコンパイル時におけるconstexpr
関数の再帰回数の上限を設定できる。
GCCとClangでは、-fconstexpr-depth=
オプションで設定できる:
g++ main.cpp -fconstexpr-depth=1024
Visual C++ 2015では、/constexpr:depth
オプションで設定できる:
cl.exe /constexpr:depth1024 main.cpp
ここの1024
を任意の値に変更することで、再帰回数の上限を設定できる。
GCC 5.2、Clang 3.7、Visual C++ 2015時点で、3つともデフォルトは512回。
constexpr
の主な目的は、数値型のプロパティを取得するクラスstd::numeric_limits
の、プロパティ取得の関数を定数式にすることである。
たとえば、std::numeric_limits
のmax()
静的メンバ関数は、int
型に対してはINT_MAX
マクロの値を返すだけであるが、それが関数であるために、INT_MAX
マクロと違って定数として扱えない、という問題があった。抽象化された機能を使うより、抽象化されていない機能の方がよい、というのは、改善すべき事態だった。そのため、関数を静的に評価する仕組みが必要とされた。
また、constexpr
は、値を計算するテンプレートメタプログラムを置き換えて使用できる。テンプレートメタプログラミングでは、非型テンプレートパラメータによって整数型の値をコンパイル時に計算することはできた。しかし、浮動小数点数型の値や、その他多くの値に関する計算が難しく、構文もまた通常の関数とはかけ離れていた(浮動小数点数型の値の計算は、分数形式にすれば、できることはできる)。値をコンパイル時に計算するためには、今後はテンプレートメタプログラミングよりもconstexpr
を積極的に使用していくとよいだろう。
- C++11 ユーザー定義リテラル
- C++14
constexpr
の制限緩和 - C++20 定数式からの仮想関数の呼び出しを許可
- C++20
constexpr
関数内でのtry-catchブロックを許可 - C++20 即時関数
- N1521 Generalized Constant Expressions
- N1972 Generalized Constant Expressions — Revision 2
- N1980 Generalized Constant Expressions — Revision 3
- N2116 Generalized Constant Expressions — Revision 4
- N2235 Generalized Constant Expressions — Revision 5
- CWG Issue 644. Should a trivial class type be a literal type?
- リテラル型のメンバ変数のみを持つクラスは、
constexpr
コンストラクタを明示的に定義しなくても、リテラル型となる
- リテラル型のメンバ変数のみを持つクラスは、
- CWG Issue 699. Must constexpr member functions be defined in the class member-specification?
- ゼロ割りの扱い、再帰回数の規定