💡 Кратко:Полный обзор семейства const в C++: const против constexpr и constinit, consteval и константные члены, а также как deducing this из C++23 сворачивает четыре перегрузки const/ссылки в одну функцию.
Последнее обновление:2026-10-24
C++ всё семейство const¶
План
- const
- constexpr
- C++20
- consteval
- constinit
Примечание: все примеры кода из этой статьи размещены в 知识星球 (Zhishi Xingqiu).
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 решает описанную выше проблему: он применим только к функциям и принуждает все вызовы выполняться во время компиляции. Что произойдёт с result2, если мы заменим в примере выше constexpr на consteval?
Возникнет ошибка: 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 объектов, для lvalue и rvalue, требует до четырёх почти одинаковых перегрузок — и в день, когда кто-то поправит лишь одну, они молча разъедутся.
«Deducing this» из C++23 (явный параметр объекта, предложение P0847) убирает дублирование. Функция-член объявляет собственный объект явным параметром; компилятор выводит его тип вместе с константностью и категорией значения, и один шаблон покрывает все четыре случая:
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_;
}
};
При вызове get() на const-lvalue Self выводится как const Widget&; на rvalue — как Widget&&, и std::forward выполняет перемещение. Что должно быть 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 не требует шаблонного базового класса, работает с лямбдами (рекурсивные лямбды!) и сохраняет иерархию чистой; 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++那些事