#новичкам
Хоть constexpr появился в С++11, многие так до конца и не понимают смысла этого ключевого слова. Давайте сегодня на базовом уровне посмотрим, чем отличаются const и constexpr, чтобы наглядно увидеть разницу.
Начнем с const. Наш старый добрый друг с первых дней C++ (а также C), может быть применен к объектам, чтобы указать на их неизменяемость. Попытка изменить const переменную напрямую приведет к ошибке компиляции, а через грязные хаки может привести к UB.
И все. Здесь ничего не говорится о том, когда инициализируется такая переменная. Компилятор может оптимизировать код и выполнить инициализацию на этапе компиляции. А может и не выполнить. Но в целом объекты const инициализируются во время выполнения:
// might be optimized to compile-time if compiled decides...
const int importantNum = 42;
std::map<std::string, double> buildMap() { /.../ }
// will be inited at runtime 100%
const std::map<std::string, double> countryToPopulation = buildMap();
const иногда даже может использоваться в compile-time вычислениях, если она имеет интегральный тип и инициализировано compile-time значением. В любых других случаях это не так.
Есть простой способ проверить, может ли переменная быть использована в compile-time вычислениях - попробовать инстанцировать с ней шаблон:
const int count = 3;
std::array<double, count> doubles {1.1, 2.2, 3.3}; // int with literal initializer is OK
// при использовании const переменной в compile-time контексте она инициализируется в compile-time
// but not double:
const double dCount = 3.3;
std::array<double, static_cast<int>(dCount)> moreDoubles {1.1, 2.2, 3.3};
// error: the value of 'dCount' is not usable in a constant expression
// не то, чтобы double нельзя было использовать как constant expression, просто const double - не constant expression.
Отсюда мы приходим к понятию constant expression - выражение, которое может быть вычислено во время компиляции. Такие выражения могут использоваться как нетиповые шаблонные параметры, размеры массивов и в других контекстах, которые требуют compile-time значений. Если такое выражение используется в других вычислениях, которые требуют compile-time значений, то оно гарантировано вычислится в compile-time.
Видно, что просто пометив переменную const нам никто не гарантирует, что ее можно будет использовать в качестве constant expression, кроме пары случаев.
Хотелось бы четко говорить компилятору, что мы собираемся использовать данную переменную в compile-time вычислениях.
И ровно для этой цели можно помечать переменную constexpr. Вы так приказываете компилятору попытаться вычислить переменную в compile-time. Главное, чтобы инициализатор также был constant expression. Помечая переменную constexpr, вы фактически добавляете константность к ее типу.
// fine now:
constexpr double dCount = 3.3; // literal is a constant expression
std::array<double, static_cast<int>(dCount)> doubles2 {1.1, 2.2, 3.3};
Просто const double шаблон не переварил, а constexpr double - спокойно.
constant expression - не обязан быть какого-то рода одной чиселкой. С каждым стандартом constexpr все больше распространяется на стандартные классы и их операции и мы можем даже создать constexpr std::array, вызвать у него метод и все это в compile-time:
constexpr std::array<int, 3> modify() {
std::array<int, 3> arr = {1, 2, 3};
arr[0] = 42; // OK в C++20 (изменение в constexpr)
return arr;
}
constexpr auto arr1 = modify(); // arr == {42, 2, 3}
static_assert(arr1.size() == 3);
static_assert(arr1[0] == 42);Есть принципиальные ограничения на constexpr объекты. Например, довольно сложно и непонятно, как работать в compile-time с динамическими аллокациями. Поэтому нельзя например создать constexpr объект std::unordered_map. В некоторых случаях с большими оговорками динамические аллокации возможны, но это для другого поста.
Но в целом, в сочетании с constexpr функциями вы можете делать намного больше, чем просто создавать массивы из даблов.
Don't be confused. Stay cool.
#cpp11