Avatar ·

const vs constexpr для переменных в C++: в чем разница и что выбрать?

Всем привет! Я изучаю C++11/14 и запутался в разнице между `const` и `constexpr` при объявлении переменных. Понимаю, что оба ключевых слова делают переменную неизменяемой, но везде пишут, что это не одно и то же. Вот пример, который меня смущает: ```cpp const int x = 42; // Вариант 1 constexpr int y = 42; // Вариант 2 const int z = foo(); // Работает constexpr int w = foo(); // Ошибка компиляции, если foo() не constexpr ``` Я читал, что `constexpr` гарантирует вычисление на этапе компиляции, а `const` — нет. Но тогда зачем нужен `const`, если `constexpr` строже? И почему я вижу в реальных проектах повсеместное использование `const` для переменных, которые явно известны во время компиляции? Еще один вопрос: если я напишу `const int size = 10;` и потом объявлю `int arr[size];` — это сработает? А если `size` будет вычисляться через функцию? Я слышал про VLA (variable-length arrays) и что это нестандартное расширение в C++, но не понимаю, как это связано с `const` и `constexpr`. Объясните, пожалуйста, на пальцах, когда использовать `const`, а когда `constexpr` для переменных. Мне нужны практические рекомендации, а не просто "constexpr — это константа времени компиляции". Спасибо! <<>>

Ключевое отличие: время вычисления vs гарантия неизменности

Давайте разберем на примерах. Главное различие — constexpr требует, чтобы значение было известно на этапе компиляции, а const только запрещает изменение переменной после инициализации. При этом const может быть инициализирован значением, которое вычисляется во время выполнения.

// Пример 1: const с runtime-значением
int getUserAge() { return 25; } // обычная функция
const int age = getUserAge();   // OK: age неизменяема, но значение известно только в runtime

// Пример 2: constexpr требует compile-time
constexpr int compileTimeAge = getUserAge(); // ОШИБКА: getUserAge() не constexpr

Практические сценарии использования

Используйте const для:

  • API и интерфейсов — когда переменная не должна изменяться, но её значение может быть результатом вызова функции (например, размер вектора).
  • Параметров функций — const int& value защищает от случайного изменения.
  • Контейнеров и объектов — const std::string name = getName();

Используйте constexpr для:

  • Размеров массивов — в стандартном C++ размер массива должен быть константой времени компиляции.
  • Шаблонных параметров — std::array<int, size> требует constexpr.
  • Математических констант — constexpr double pi = 3.141592653589793;
  • Флагов компиляции — constexpr bool debugMode = false;

Почему const для массивов может работать (но не всегда)

Вопрос про int arr[size] — это ловушка. В C++ стандартные массивы требуют constexpr размера. Однако некоторые компиляторы (например, GCC с расширениями) поддерживают VLA (Variable Length Arrays) — это нестандартное поведение.

// Правильный C++:
constexpr int size = 10;
int arr[size]; // OK: size известен на этапе компиляции

// Некорректный C++ (но может скомпилироваться с расширениями):
int runtimeSize = 10;
const int size2 = runtimeSize;
int arr2[size2]; // Ошибка в стандартном C++ (VLA — нестандартное расширение)

Когда constexpr строже — это хорошо

Если вы можете сделать переменную constexpr — делайте. Это даёт:

  • Гарантию нулевого оверхеда — значение встраивается в код.
  • Возможность использовать в контекстах, требующих compile-time (например, static_assert, шаблоны).
  • Оптимизацию — компилятор может вычислить больше на этапе компиляции.

Итоговое правило

Простое эмпирическое правило: если значение может быть вычислено во время компиляции — используйте constexpr. Если нет — используйте const. Не бойтесь комбинировать: constexpr const int x = 5; — избыточно, но не ошибочно (в C++17 constexpr уже подразумевает const).

Пример из реального кода:

class Config {
public:
    static constexpr double PI = 3.14159;          // compile-time
    static const std::string APP_NAME = "MyApp";   // const, т.к. std::string не constexpr
};
<<>>

Дополнение: constexpr в C++17 и далее

Стоит отметить, что в C++17 constexpr стало более гибким. Теперь можно объявлять constexpr лямбды и даже constexpr if. А в C++20 появились constexpr виртуальные функции и constexpr динамическая память (ограниченно).

Ещё один важный момент: constexpr переменные по умолчанию имеют внутреннюю линковку (internal linkage), если они не объявлены как extern. Это значит, что вы можете определить constexpr int x = 5; в заголовочном файле без риска множественного определения — каждый translation unit получит свою копию. Для const переменных такое поведение тоже работает, но только если они инициализированы константным выражением (с C++17).

Практический совет: если вы пишете библиотеку или хедер-онли код — используйте constexpr для числовых констант. Это безопаснее и даёт больше возможностей для оптимизации. Если же константа зависит от рантайма (например, результат вызова clock()) — только const.