auto・decltype・constexpr・consteval(型推論とコンパイル時計算)¶
auto(型推論)¶
一言で言うと¶
auto は「初期化子から型をコンパイラに推論させる」機能。動的型付けではありません——型はコンパイル時に完全に確定し、以後変わりません(C#の var と同じ)。
// C++20
auto hp = 100; // int
auto ratio = 0.5f; // float
auto it = enemies.begin(); // std::vector<Enemy>::iterator(書くと長い型の主用途)
auto& e = enemies[0]; // Enemy&(参照が欲しければ&を書く — 重要!)
const auto& cfg = GetConfig(); // const参照
落とし穴: autoは参照とconstを「落とす」¶
Enemy& GetBoss();
auto b = GetBoss(); // bは Enemy(コピー!)— 参照は推論で剥がれる
auto& b2 = GetBoss(); // Enemy&(意図通り)
「auto = コピー、auto& = 参照、const auto& = 読み取り参照」と覚える。C#のvarは参照型なら常に参照なので、この罠はC++特有です。
使いどころの指針¶
- 書く: イテレータ、ラムダの型(書けない)、明らかに冗長な型名、範囲for(
for (const auto& e : enemies)) - 書かない: 型自体が情報であるとき(
auto damage = Calc();— int? float? 単位は?)。数値型は明示が安全(意図しない整数/浮動小数の推論)
decltype¶
「式の型」をそのまま取り出す道具。テンプレートの戻り値型や、変数と同じ型の別変数を作るときに使う中級機能です。
日常コードでは登場は少なめ。「テンプレートを書く側になったら本格的に使う」でOKです。
constexpr(コンパイル時計算)¶
一言で言うと¶
「この計算はコンパイル時に実行できる」と宣言する仕組み。実行ファイルには計算結果だけが埋め込まれ、実行時コストがゼロになります。
// C++20
constexpr int kMaxLevel = 99; // コンパイル時定数(#defineの安全な代替)
constexpr int ExpForLevel(int level) { // コンパイル時にも実行時にも使える関数
int exp = 0;
for (int i = 1; i < level; ++i) exp += i * 100;
return exp;
}
constexpr int kMaxExp = ExpForLevel(kMaxLevel); // コンパイル時に計算→定数として埋め込み
int runtime = ExpForLevel(playerLevel); // 実行時にも普通に呼べる
// 経験値テーブルをコンパイル時に生成する例(C++20: constexprでループ・配列OK)
constexpr auto MakeExpTable() {
std::array<int, 100> t{};
for (int i = 0; i < 100; ++i) t[i] = ExpForLevel(i);
return t;
}
constexpr auto kExpTable = MakeExpTable(); // 実行ファイルに完成済みテーブルが焼き込まれる
検証済みサンプル: samples/constexpr_table.cpp
#define との違い¶
#define MAX_LEVEL 99 はプリプロセッサの文字列置換(型なし・スコープなし・デバッガに出ない)。constexpr は型付き・スコープあり・言語の一部。定数は constexpr 一択です。
constexpr if(テンプレートとの相棒)¶
template <typename T>
void Log(const T& v) {
if constexpr (std::is_floating_point_v<T>) { // コンパイル時分岐: 選ばれない側はコンパイルすらされない
// 小数用フォーマット
} else {
// それ以外
}
}
consteval(C++20)¶
「必ずコンパイル時に実行される関数」(constexprは「できれば」、constevalは「必ず」)。実行時に呼ぼうとするとコンパイルエラー。
consteval std::uint32_t Fnv1a(std::string_view s) { // 文字列ハッシュを必ずコンパイル時に
std::uint32_t h = 2166136261u;
for (char c : s) { h ^= static_cast<std::uint8_t>(c); h *= 16777619u; }
return h;
}
constexpr auto kHash = Fnv1a("player_idle"); // 実行ファイルには数値だけが残る
用途: 「実行時に走ってはいけない」ことを保証したい計算(ID生成、鍵の埋め込み検査など)。
C#との違い¶
- C#の
constはコンパイル時定数だが単純な値のみ。ループや関数呼び出しを含む計算のコンパイル時実行(constexpr関数)に相当する仕組みはない(Source Generatorが部分的な代替) - C#の
varとC++のautoはほぼ同じ思想だが、C++は参照/constが剥がれる点が異なる(上記)
Unity開発者が誤解しやすい点¶
- 「テーブルはScriptableObjectかJSONで持つもの」— C++ではコンパイル時生成という第3の選択肢がある(ロード時間ゼロ・改竄不能・ただし変更にはリビルド)
autoを「動的型」と誤解しない(varと同じ静的推論)- 文字列比較をIDに使う場面(アニメ名、イベント名)は、constevalハッシュでコンパイル時に数値化するのがC++ゲーム開発の定石(UnityのAnimator.StringToHashと同じことが言語機能でできる)
ゲーム開発での使用例¶
- 定数・調整値(
constexpr float kGravity = -9.8f;) - ルックアップテーブル(経験値、sin近似、CRCテーブル)のコンパイル時生成
- 文字列ハッシュによるID化(アセットID、ステートID)
- コンパイル時の設定検証(テーブルの整合性チェックをstatic_assertで)
使う場面 / 使わない場面¶
- constexpr: 定数はすべて。コンパイル時に決まる計算・テーブル
- 使わない: 実行時データに依存する計算(当然不可)。巨大な計算のコンパイル時実行はビルド時間を食う(テーブル生成が数秒かかり始めたら外部ツール生成へ)
- auto: 冗長な型名の省略。型が情報になる場所では書かない
理解度チェック¶
auto b = GetBoss();の罠は何ですか。- constexprとconstevalの違いは?
-
defineよりconstexprが優れる点を3つ挙げてください。¶
演習¶
samples/constexpr_table.cpp の経験値テーブルを「レベル間の必要経験値が単調増加であること」を static_assert で検証するように拡張してください(コンパイル時にバランスデータの整合性チェックが走る体験が目的です)。
前: 例外 | カテゴリ目次 | 次: static・inline・friend