💡 要約:C++のconstファミリー完全ガイド:constとconstexprとconstinit、consteval、constメンバーの違い、そしてC++23のdeducing thisが4つのconst/参照オーバーロードを1つにまとめる方法。
最終更新:2026-10-24
C++ const ファミリー¶
目次
- const
- constexpr
- C++20
- consteval
- constinit
注:本記事のすべてのコード例は星球に掲載しています。
1.const¶
const は C++/C を学ぶ際に最初に触れるキーワードのひとつで、通常は cv-qualifier として使われ、オブジェクトが不変であることを表します。**コンパイル時評価**は保証されず、いつ評価されるかはコンパイラが決定します。例えば:
constはコンパイル時の定数式に使えることもありますが、例外もあります。例えば:
const double count_1 = 3.3;
std::array<double, static_cast<int>(count_1)> moreDoubles1 {1.1, 2.2, 3.3};
この場合、the value of 'count_1' is not usable in a constant expressionというエラーになります。
しかしこれは正常です:
本質はコンパイル時の式評価にあります。そこでC++11ではconstexprが導入されました。
2.constexpr¶
constexprは、変数の値が定数式に現れることを指定できます。先ほどの例のconstをconstexprに変えるだけで、正常に動作します。
constexpr double count_2 = 3.3;
std::array<double, static_cast<int>(count_2)> moreDoubles2 {1.1, 2.2, 3.3};
constexpr は必ずしもコンパイル時にしか実行できないわけではありません。その本質的な意味は、コンパイル時に計算**できる**ということであり、実行時に constexpr 変数や関数を使えば、実行時に実行することもできます。
例えば:result1とresult2はまったく異なります。result1はコンパイル時に計算されますが、result2は実行時に計算されます。ちょっと疑問を投げかけましょう:なぜかわかりますか?
つまり、constexprを指定した関数が必ずコンパイル時に計算されるわけではありません。
constexpr int compileTimeSum(int a, int b) {
return a + b;
}
int main() {
constexpr int result1 = compileTimeSum(3, 5);
int x = 10;
int result2 = compileTimeSum(x, 20);
return 0;
}
では、誰が保証してくれるのでしょうか?そこでC++20の登場です。
3.C++20¶
3.1 consteval¶
constevalは上の問題を解決します。関数にしか使えず、すべての呼び出しをコンパイル時に行うことを強制します。このとき上のconstexprをconstevalに変えると、result2はどうなるでしょうか?
それはエラーです:call to consteval function 'compileTimeSum(x, 20)' is not a constant expression
consteval int compileTimeSum(int a, int b) {
return a + b;
}
int main() {
constexpr int result1 = compileTimeSum(3, 5);
int x = 10;
int result2 = compileTimeSum(x, 20);
return 0;
}
constevalを変数に対して使うと、これもエラーになります:error: a variable cannot be declared 'consteval'。
3.2 constinit¶
constinitは静的またはスレッドローカル変数に定数初期化を強制できます。
- constexpr、consteval と一緒には使えない
- 非型指定子であり、その役割はassert
- 変数が静的初期化されることを保証
- static または thread_local にしか使えない
例えば:以下はコンパイルエラーになります:error: 'constinit' can only be applied to a variable with static or thread storage duration
constinit int a = 100;
int main() {
++a;
constinit auto b = 100; // error
// b has thread storage duration
constinit thread_local auto b = 100;
}
C++23 deducing this:const/参照オーバーロードを一度に書く¶
constファミリーを学び進めると、ほぼ誰もが同じ壁にぶつかります。constオブジェクト・非constオブジェクト・左辺値・右辺値のすべてで正しく振る舞うgetterは、ほぼ同一のオーバーロードを最大4つ書くことになり、どこか一つだけ編集された日に静かに不一致が始まります。
C++23の「deducing this」(明示的オブジェクト引数、提案P0847)はこのコピペを消します。メンバ関数が自分自身のオブジェクトを明示的な引数として宣言すると、コンパイラは型とともにconst性と値カテゴリを推論し、1つのテンプレートで4ケースすべてを賄えます:
class Widget {
int value_{42};
public:
// Before C++23 you needed up to four overloads:
// int& get() & { return value_; }
// int get() const& { return value_; }
// int&& get() && { return std::move(value_); }
// ...
// C++23 deducing this — one template covers them all:
template <typename Self>
auto&& get(this Self&& self) {
return std::forward<Self>(self).value_;
}
};
const左辺値でget()を呼ぶとSelfはconst Widget&、右辺値ではWidget&&と推論され、std::forwardがムーブを運びます。「何をconstにすべきか」を決めるのは依然として前述のconstの知識で、deducing thisは重複労働を取り除くだけです。
この機能はCRTPが担っていた二つの役割——ミックスインと最派生型の推論——も、基底クラステンプレートのボイラープレートなしでこなします。コンパイラ対応:GCC 14+、Clang 18+、MSVCはVS 2022 17.2から。
よくある質問¶
deducing thisはconstオーバーロードを置き換えられますか?¶
よくあるケースでは可能です。Self&&の単一テンプレートとstd::forwardで、&、const&、&&、const&&のすべてで正しく動作します。修飾ごとに振る舞いが本当に異なる場合だけ個別のオーバーロードが必要で、それは稀です。
どのコンパイラがdeducing thisに対応していますか?¶
GCC 14以降、Clang 18以降、MSVCはVisual Studio 2022 17.2以降です。C++23機能なので、-std=c++23(古いドライバではc++2b)でコンパイルしてください。
deducing thisはCRTPと同じですか?¶
重なります:どちらも最派生型を推論します。deducing thisは基底クラステンプレート不要で、lambda(再帰lambda!)でも使え、階層も綺麗なまま。ミックスインを再利用可能な基底クラスにしたい場面ではCRTPが依然 有効です。
関連トピック¶
- C++ 純粋仮想関数と抽象クラス | C++那些事
- C++ assert アサーション | C++那些事
- C++ ビットフィールド | C++那些事
- C 言語による C++ 多態の実現 | C++那些事
- C++ const 詳解:定数・const ポインタとポインタ const の違い | C++那些事
- C++ 間接参照演算子 * | C++那些事
- C++ 初期化と代入 | C++那些事
- C++ 複数戻り値の書き方 | C++那些事
- C++ mutable キーワード | C++那些事
- C++ restrict とコンパイル最適化 | C++那些事