C++ 基礎からマスターまで — 第17回
constexpr と consteval でコンパイル時に計算する
本ブログの記事は、講義を目的として作成しています。 内容に誤りが含まれる可能性があるため、対象コンパイラと標準ライブラリの資料も確認してください。
C++ は一部の計算をプログラム開始前に実行できます。constexpr、consteval、constinit は、それぞれ定数評価、必須のコンパイル時呼び出し、静的初期化を表す別の契約です。
constexpr は二つの状況で動く
constexpr int Square(int value)
{
return value * value;
}
constexpr int tableSize = Square(8); // コンパイル時
int input = ReadInput();
int result = Square(input); // 実行時
constexpr 関数は定数評価が可能ですが、すべての呼び出しがコンパイル時に行われるわけではありません。引数が定数で、結果が constant expression を必要とする位置で使われる場合は、コンパイラが評価します。
consteval はコンパイル時呼び出しを強制する
consteval unsigned int HashLiteral(std::string_view text)
{
unsigned int hash = 2166136261u;
for (char ch : text)
hash = (hash ^ static_cast<unsigned char>(ch)) * 16777619u;
return hash;
}
constexpr auto playerId = HashLiteral("Player");
consteval 関数の呼び出しは必ず constant expression でなければなりません。ビルド時に固定すべきアセットキーやプロトコルタグに向いています。
ハッシュには衝突があります。コンパイル時に計算しても一意性は保証されないため、衝突方針と元のキーの検証が必要です。
if constexpr で無効な分岐を除外する
template <typename T>
void Serialize(const T& value, Writer& writer)
{
if constexpr (std::integral<T>)
writer.WriteInteger(value);
else
value.Serialize(writer);
}
通常の if と異なり、選ばれなかった if constexpr の分岐はそのテンプレート実体から破棄されます。選ばれない側の操作が対象型に存在しなくても構いません。
constinit は静的初期化を検査する
constinit int globalLimit = 64;
constinit は static storage duration のオブジェクトが静的初期化されることを確認します。値を変更不能にするものではなく、それは const の役目です。
コンパイル時処理は実行時初期化を減らし、誤りを早く見つけられますが、ビルド時間や診断、binary サイズを増やすことがあります。固定規則の早期検証に価値がある場面で使います。