В чем разница между constexpr и const в C++?
Основное различие: время жизни и гарантии
Главное отличие между const и constexpr заключается в том, когда значение становится известным и какие гарантии дает компилятор.
const — это квалификатор типа, который говорит: "Это значение нельзя изменить после инициализации". Однако само значение может быть вычислено во время выполнения программы (runtime). Например:
int x = 42;
const int y = x; // y — константа, но ее значение не известно до запуска программы
constexpr — это спецификатор, который требует, чтобы значение (или результат функции) был известен на этапе компиляции (compile-time). Если компилятор не может этого гарантировать, программа не скомпилируется:
constexpr int z = 42; // OK: известно на этапе компиляции
int x = 42;
constexpr int w = x; // Ошибка! x не является константой времени компиляции
Когда использовать constexpr для переменных?
- Когда вам нужно объявить размер массива (размер должен быть известен на этапе компиляции).
- Когда вы работаете с шаблонами и параметрами, которые должны быть константами времени компиляции (например,
std::array<int, N>). - Когда вы хотите быть уверены, что значение не только не изменится, но и будет вычислено до запуска программы (оптимизация).
Когда использовать constexpr для функций?
Ключевое слово constexpr перед функцией говорит компилятору: "Я гарантирую, что эту функцию можно вычислить на этапе компиляции, если все аргументы — тоже константы времени компиляции". Пример:
constexpr int factorial(int n) {
return (n <= 1) ? 1 : (n * factorial(n - 1));
}
constexpr int val = factorial(5); // 120, вычислено в compile-time
int runtime_val = factorial(10); // Тоже работает, но вычисляется в runtime
Важно: в C++11 тело constexpr функции должно состоять из одного return (без циклов и сложной логики). Начиная с C++14, ограничения смягчены.
Может ли constexpr заменить const?
Нет, не может и не должен. constexpr — это более строгая гарантия. Используйте constexpr только тогда, когда вам действительно нужно compile-time значение. В остальных случаях используйте const:
constдля переменных, которые не изменяются, но инициализируются данными из внешнего мира (например, из файла, из ввода пользователя).constexprдля настоящих констант (математические константы, размеры буферов, таблицы значений).
Наглядный пример
#include <iostream>
#include <array>
constexpr int compile_time_square(int x) { return x * x; }
int main() {
// 1. constexpr — работает
constexpr int arr_size = compile_time_square(10);
std::array<int, arr_size> arr; // OK: размер известен
// 2. const — не сработает для размера массива
int runtime_val = 10;
const int bad_size = compile_time_square(runtime_val);
// std::array<int, bad_size> bad_arr; // Ошибка! bad_size не constexpr
// 3. const — отлично работает для runtime-констант
std::cout << "Enter a number: ";
int input;
std::cin >> input;
const int user_val = input; // Нельзя изменить, но значение из runtime
std::cout << user_val << std::endl;
return 0;
}
Итог
Используйте const для логической неизменяемости (semantic constness). Используйте constexpr для гарантии вычислений на этапе компиляции (compile-time evaluation). Это разные инструменты для разных задач.
Дополнение: производительность и метапрограммирование
Коллега выше отлично объяснил базовую разницу. Я добавлю несколько нюансов, которые часто упускают новички.
1. constexpr не всегда быстрее. Многие думают, что если пометить функцию constexpr, программа станет быстрее. Это не совсем так. Если функция вызывается с runtime-аргументами, она выполняется как обычная функция. Если с compile-time аргументами — компилятор может вычислить результат заранее, но это не гарантирует ускорения (иногда код разворачивается и становится больше).
2. Использование в шаблонах. constexpr незаменим в современном C++ для compile-time вычислений:
template<int N>
struct Factorial {
static constexpr int value = N * Factorial<N-1>::value;
};
template<>
struct Factorial<0> {
static constexpr int value = 1;
};
// Использование:
constexpr int f5 = Factorial<5>::value; // 120
Это старый трюк (метапрограммирование шаблонов), но с constexpr функциями он стал проще.
3. Ограничения C++11 vs C++14/17. В C++11 constexpr функция могла содержать только один return. В C++14 можно использовать циклы, переменные, if. В C++17 появились constexpr if и constexpr lambda. Если ваш проект использует старый стандарт, помните об этом.
4. Практический совет: Начинайте с const. Если компилятор говорит, что нужно compile-time значение (например, для размера массива), меняйте на constexpr. Не пытайтесь сделать всё constexpr — это усложняет код без явной выгоды.