Константные выражения
Определяет выражение, которое может быть вычислено во время компиляции.
Такие выражения можно использовать в качестве нетиповых аргументов шаблонов, размеров массивов и в других контекстах, требующих константных выражений, например:
int n = 1; std::array<int, n> a1; // Error: “n” is not a constant expression const int cn = 2; std::array<int, cn> a2; // OK: “cn” is a constant expression
Определение
| Выражение, принадлежащее к любой из перечисленных ниже категорий константных выражений, является константным выражением.
| (до C++11) | ||
| Следующие сущности являются допустимыми результатами константного выражения :
Константное выражение — это либо glvalue основного константного выражения, ссылающееся на сущность, являющуюся допустимым результатом константного выражения, либо prvalue основного константного выражения, значение которого удовлетворяет следующим ограничениям:
|
(начиная с C++11) (до C++26) |
||
| Константное выражение — это либо glvalue основного константного выражения, ссылающееся на объект или функцию, не являющуюся немедленной, либо prvalue основного константного выражения, значение которого удовлетворяет следующим ограничениям:
| (начиная с C++26) |
При определении того, является ли выражение константным, предполагается, что устранение копирования не выполняется.
Определение константных выражений в C++98 целиком находится в сворачиваемом блоке. Следующее описание относится к C++11 и более поздним версиям C++.
Литеральный тип
Следующие типы в совокупности называются литеральными типами :
| (начиная с C++14) |
- скалярный тип
- ссылочный тип
- массив литерального типа
- возможно, cv-квалифицированный тип класса, удовлетворяющий всем следующим условиям:
- У него есть тривиальный деструктор(до C++20)constexpr-деструктор(начиная с C++20).
- Все его нестатические невариативные члены данных и базовые классы имеют нелетучие литеральные типы.
- Он является одним из следующих типов:
| (начиная с C++17) |
- тип объединения-агрегата, удовлетворяющий одному из следующих условий:
- У него нет вариативных членов.
- У него есть хотя бы один вариативный член нелетучего литерального типа.
- тип агрегата, не являющийся объединением, каждый член анонимного объединения которого удовлетворяет одному из следующих условий:
- У него нет вариативных членов.
- У него есть хотя бы один вариативный член нелетучего литерального типа.
- тип, имеющий хотя бы один constexpr-конструктор (шаблон), не являющийся конструктором копирования или перемещения
Внутри константного выражения можно создавать только объекты литеральных типов.
Основное константное выражение
Основное константное выражение — это любое выражение, вычисление которого не приводит к вычислению какой-либо из следующих языковых конструкций:
- указатель
this, за исключением constexpr-функции, вычисляемой как часть выражения, или случая, когда он встречается в неявном или явном выражении доступа к члену класса - (начиная с C++23) поток управления, проходящий через объявление блочной переменной со статической длительностью хранения или длительностью хранения потока, которая не является доступной в константных выражениях
- выражение вызова функции, вызывающее функцию (или конструктор), не объявленную как constexpr
constexpr int n = std::numeric_limits<int>::max(); // OK: max() is constexpr constexpr int m = std::time(nullptr); // Error: std::time() is not constexpr
- вызов constexpr-функции, которая объявлена, но не определена
- вызов инстанцирования шаблона constexpr-функции/конструктора, если инстанцирование не удовлетворяет требованиям к constexpr-функции/конструктору.
- вызов виртуальной constexpr-функции для объекта, динамический тип которого неизвестен для constexpr
- выражение, которое превысило бы ограничения, определяемые реализацией
- выражение, вычисление которого приводит к любой форме неопределённого или ошибочного(начиная с C++26) поведения основного языка, за исключением потенциального неопределённого поведения, вызванного стандартными атрибутами.
constexpr double d1 = 2.0 / 1.0; // OK constexpr double d2 = 2.0 / 0.0; // Error: not defined constexpr int n = std::numeric_limits<int>::max() + 1; // Error: overflow int x, y, z[30]; constexpr auto e1 = &y - &x; // Error: undefined constexpr auto e2 = &z[20] - &z[3]; // OK constexpr std::bitset<2> a; constexpr bool b = a[2]; // UB, but unspecified if detected
- (до C++17) лямбда-выражение
- неявное преобразование lvalue-to-rvalue, кроме случаев, когда оно применяется к...
- glvalue типа (возможно, cv-квалифицированного)
std::nullptr_t - glvalue нелетучего литерального типа, обозначающему объект, который доступен в константных выражениях
int main() { const std::size_t tabsize = 50; int tab[tabsize]; // OK: tabsize is a constant expression // because tabsize is usable in constant expressions // because it has const-qualified integral type, and // its initializer is a constant initializer std::size_t n = 50; const std::size_t sz = n; int tab2[sz]; // Error: sz is not a constant expression // because sz is not usable in constant expressions // because its initializer was not a constant initializer } - glvalue нелетучего литерального типа, ссылающемуся на нелетучий объект, время жизни которого началось в ходе вычисления этого выражения
- glvalue типа (возможно, cv-квалифицированного)
- преобразование lvalue-to-rvalue или модификация, применённые к неактивному члену объединения или его подобъекту (даже если он входит в общую начальную последовательность с активным членом)
- преобразование lvalue-to-rvalue для объекта, значение которого неопределено
- вызов неявного конструктора/оператора присваивания копирования или перемещения для объединения, активный член которого является изменяемым (если он есть), если время жизни началось вне вычисления этого выражения
- (до C++20) выражение присваивания, изменяющее активный член объединения
- преобразование указателя на void в тип указателя на объект
T*, если только указатель не содержит значение нулевого указателя или не указывает на объект, тип которого подобенT(начиная с C++26) -
dynamic_cast, операндом которого является glvalue, ссылающееся на объект, динамический тип которого неизвестен для constexpr(начиная с C++20) -
reinterpret_cast - (до C++20) вызов псевдодеструктора
- (до C++14) оператор инкремента или декремента
- (начиная с C++14) модификация объекта, если только объект не имеет нелетучий литеральный тип и его время жизни не началось в ходе вычисления выражения
constexpr int incr(int& n) { return ++n; } constexpr int g(int k) { constexpr int x = incr(k); // Error: incr(k) is not a core constant // expression because lifetime of k // began outside the expression incr(k) return x; } constexpr int h(int k) { int x = incr(k); // OK: x is not required to be initialized // with a core constant expression return x; } constexpr int y = h(1); // OK: initializes y with the value 2 // h(1) is a core constant expression because // the lifetime of k begins inside the expression h(1) - (начиная с C++20) вызов деструктора или псевдодеструктора для объекта, время жизни которого не началось в ходе вычисления этого выражения
- выражение
typeid, применённое к glvalue полиморфного типа, если этот glvalue ссылается на объект, динамический тип которого неизвестен для constexpr(начиная с C++20) - выражение new, если не выполняется одно из следующих условий:(начиная с C++20)
- Выбранная функция выделения памяти является заменяемой глобальной функцией выделения памяти, а выделенное хранилище освобождается в ходе вычисления этого выражения.
(начиная с C++20) - Выбранная функция выделения памяти является формой без выделения памяти с выделяемым типом
T, а аргумент размещения удовлетворяет всем следующим условиям: - Он указывает на:
- объект, тип которого подобен
T, еслиTне является типом массива, или - первый элемент объекта типа, подобного
T, еслиTявляется типом массива. - Он указывает на хранилище, длительность существования которого началась в ходе вычисления этого выражения.
(начиная с C++26) - выражение delete, если только оно не освобождает область хранилища, выделенную в ходе вычисления этого выражения(начиная с C++20)
- (начиная с C++20) сопрограммы: выражение await или выражение yield
- (начиная с C++20) трёхстороннее сравнение, если его результат не определён
- оператор равенства или отношения, результат которого не определён
- (до C++14) оператор присваивания или составного присваивания
- (до C++26) выражение throw
- (начиная с C++26) создание объекта исключения, если только объект исключения и все его неявные копии, созданные вызовами
std::current_exceptionилиstd::rethrow_exception, не уничтожаются в ходе вычисления этого выраженияconstexpr void check(int i) { if (i < 0) throw i; } constexpr bool is_ok(int i) { try { check(i); } catch (...) { return false; } return true; } constexpr bool always_throw() { throw 12; return true; } static_assert(is_ok(5)); // OK static_assert(!is_ok(-1)); // OK since C++26 static_assert(always_throw()); // Error: uncaught exception - объявление asm
- вызов макроса
va_arg - оператор
goto - выражение
dynamic_castилиtypeidили выражение new(начиная с C++26), которое вызвало бы исключение при отсутствии достижимого определения типа исключения(начиная с C++26) - внутри лямбда-выражения — ссылка на
thisили на переменную, определённую вне этой лямбды, если эта ссылка была бы использованием в смысле ODRvoid g() { const int n = 0; constexpr int j = *&n; // OK: outside of a lambda-expression [=] { constexpr int i = n; // OK: 'n' is not odr-used and not captured here. constexpr int j = *&n; // Ill-formed: '&n' would be an odr-use of 'n'. }; }обратите внимание: если использование в смысле ODR происходит при вызове функции-замыкания, оно не относится к
thisили к переменной во внешней области видимости, поскольку вместо этого обращается к члену данных замыкания// OK: 'v' & 'm' are odr-used but do not occur in a constant-expression // within the nested lambda auto monad = [](auto v){ return [=]{ return v; }; }; auto bind = [](auto m){ return [=](auto fvm){ return fvm(m()); }; }; // OK to have captures to automatic objects created during constant expression evaluation. static_assert(bind(monad(2))(monad)() == monad(2)());(начиная с C++17)
Дополнительные требования
Даже если выражение E не вычисляет ничего из перечисленного выше, вопрос о том, является ли E основным константным выражением, определяется реализацией, если вычисление E привело бы к неопределённому поведению во время выполнения из-за использования [[noreturn]] или [[assume]].
Даже если выражение E не вычисляет ничего из перечисленного выше, не указано, является ли E основным константным выражением, если вычисление E привело бы к вычислению чего-либо из следующего:
- Операция со стандартной библиотекой, имеющая неопределённое поведение.
- Вызов макроса
va_start.
При определении того, является ли выражение основным константным выражением, вычисление тела функции-члена std::allocator<T> игнорируется, если T является литеральным типом.
При определении того, является ли выражение основным константным выражением, считается, что вызов тривиального конструктора копирования/перемещения или оператора присваивания копированием/перемещением объединения копирует/перемещает активный член объединения, если он есть.
| При определении того, является ли выражение основным константным выражением, вычисление выражения-идентификатора, именующего структурированную привязку
| (начиная с C++26) |
Во время вычисления выражения как основного константного выражения все имена и использования *this, относящиеся к объекту или ссылке, время жизни которых началось вне вычисления выражения, рассматриваются как относящиеся к конкретному экземпляру этого объекта или ссылки, время жизни которого, а также всех подобъектов (включая все члены объединения), охватывает всё константное вычисление.
- Для такого объекта, который не является доступным в константных выражениях(начиная с C++20) его динамический тип является неизвестным для constexpr.
- Для такой ссылки, которая недоступна в константных выражениях(начиная с C++20) считается, что ссылка привязана к неопределённому объекту ссылочного типа, время жизни которого и всех подобъектов охватывает всё константное вычисление, а его динамический тип неизвестен для constexpr.
Целочисленное константное выражение
Целочисленное константное выражение — это выражение целочисленного типа или нескоупированного типа перечисления, неявно преобразованное к prvalue, причём преобразованное выражение является основным константным выражением.
Если выражение типа класса используется там, где ожидается целочисленное константное выражение, оно неявно преобразуется в контексте к целочисленному типу или нескоупированному типу перечисления.
Преобразованное константное выражение
Преобразованное константное выражение типа T — это выражение, неявно преобразованное к типу T, причём преобразованное выражение является константным выражением, а последовательность неявных преобразований содержит только:
- пользовательские преобразования constexpr
- преобразования lvalue-to-rvalue
- целочисленные продвижения
- не сужающие целочисленные преобразования
- продвижения типов с плавающей точкой
- не сужающие преобразования типов с плавающей точкой
| (начиная с C++17) |
Если при этом происходит привязка ссылки, она может быть только прямой привязкой.
Следующие контексты требуют преобразованного константного выражения:
- constant-expression меток case
- инициализаторы элементов перечисления, если базовый тип фиксирован
- целочисленные и перечислимые (до C++17)нетиповые аргументы шаблонов
| (начиная с C++14) |
| (начиная с C++26) |
Контекстно преобразованное константное выражение типа bool — это выражение, контекстно преобразованное к bool, причём преобразованное выражение является константным выражением, а последовательность преобразований содержит только перечисленные выше преобразования.
Следующие контексты требуют контекстно преобразованного константного выражения типа bool:
| (до C++23) | |
|
(начиная с C++17) (до C++23) |
|
| (начиная с C++20) |
Составляющие сущности
Неформально, составляющие значения и составляющие ссылки объекта obj — это скалярные значения и ссылки, из которых состоит obj.
Формально, составляющие значения объекта obj определяются следующим образом:
- Если
objимеет скалярный тип, составляющим значением является значениеobj. - В противном случае составляющими значениями являются составляющие значения любых прямых подобъектов
obj, кроме неактивных членов объединения.
Составляющие ссылки объекта obj включают следующие ссылки:
- любые прямые члены
obj, имеющие ссылочный тип - составляющие ссылки любых прямых подобъектов
obj, кроме неактивных членов объединения
Составляющие значения и составляющие ссылки переменной var определяются следующим образом:
- Если
varобъявляет объект, составляющими значениями и ссылками являются составляющие значения и ссылки этого объекта. - Если
varобъявляет ссылку, составляющей ссылкой является эта ссылка.
Для любой составляющей ссылки ref переменной var, если ref привязана к временному объекту или его подобъекту, время жизни которого продлено до времени жизни ref, составляющие значения и ссылки этого временного объекта также являются составляющими значениями и ссылками var, рекурсивно.
Сущности, представимые в constexpr
Любой объект со статической длительностью хранения является доступным для ссылки в constexpr в любой точке программы.
| Объект с автоматической длительностью хранения является доступным для ссылки в constexpr начиная с точки void f() {
constexpr int x = 42;
constexpr const int& ref = x; // OK, x is constexpr-referenceable here
const int& r = 42; // r binds to a temporary object with automatic storage duration
constexpr const int& ref2 = r; // OK, the temporary is constexpr-referenceable here
[&] {
constexpr const int& ref = x; // Error: x is not constexpr-referenceable here
};
}
| (начиная с C++26) |
Другие объекты (например, объекты с потоковой длительностью хранения) никогда не доступны для ссылки в constexpr.
Объект или ссылка x является представимым в constexpr в точке P, если выполняются все следующие условия:
- Для каждого составляющего значения
x, указывающего на объектobj,objдоступен для ссылки в constexpr изP. - Для каждого составляющего значения
x, указывающего за пределы объектаobj,objдоступен для ссылки в constexpr изP. - Для каждой составляющей ссылки
x, ссылающейся на объектobj,objдоступен для ссылки в constexpr изP.
Сущности с константной инициализацией
| Переменная или временный объект
| (до C++26) |
| Переменная
Переменная, инициализируемая константным выражением, имеет константную инициализацию, если у неё есть инициализатор либо её тип является конструируемым по умолчанию для const. void f() {
int ax = 0; // ax is constant-initialized
thread_local int tx = 0; // tx is constant-initialized
static int sx; // sx is not constant-initialized (it doesn't have an initializer)
constexpr int& raa = ax; // OK, raa is constant-initialized
constexpr int& rat = tx;
// Error: rat is not constant-initialized
// because it's not constexpr-representable here
static constexpr int& rsa = ax;
// Error: rsa is not constant-initialized
// because it's not constexpr-representable at the nearest namespace scope
}
| (начиная с C++26) |
Использование в константных выражениях
Переменная является потенциально константной, если она является переменной constexpr либо имеет ссылочный тип или неквалифицированный volatile целочисленный тип или тип перечисления с квалификатором const.
Переменная var может использоваться в константных выражениях в точке P, если:
-
varимеет константную инициализацию, -
varявляется потенциально константной, - инициализирующее объявление
varDдостижимо изP, и - выполняется одно из следующих условий:
-
varявляется переменной constexpr. -
varне инициализирована значением, локальным для единицы трансляции. -
Pнаходится в той же единице трансляции, что иD.
Объект или ссылка могут использоваться в константных выражениях в точке P, если они представимы в constexpr в P и(начиная с C++26) являются одной из следующих сущностей:
- переменная, которую можно использовать в константных выражениях в
P - временный объект литерального типа с квалификатором const и без volatile, время жизни которого продлено до времени жизни переменной, которую можно использовать в константных выражениях в
P
| (начиная с C++20) |
- объект строкового литерала
- немодифицируемый подобъект любой из перечисленных выше сущностей
- член-ссылка любой из перечисленных выше сущностей
Выражения с явным вычислением на этапе компиляцииСледующие выражения (включая преобразования к целевому типу) явно вычисляются на этапе компиляции:
Определить, выполняется ли вычисление в контексте явного вычисления на этапе компиляции, можно с помощью | (начиная с C++20) |
Функции и переменные, необходимые для вычисления на этапе компиляции
Следующие выражения или преобразования являются потенциально вычисляемыми на этапе компиляции:
- выражения с явным вычислением на этапе компиляции
- потенциально вычисляемые выражения
- непосредственные подвыражения списка инициализаторов в фигурных скобках (для определения того, является ли преобразование сужающим, может потребоваться вычисление на этапе компиляции)
- выражения взятия адреса, встречающиеся внутри шаблонной сущности (вычисление на этапе компиляции может потребоваться, чтобы определить, является ли такое выражение зависимым от значения)
- подвыражения одного из перечисленных выше выражений, которые не являются подвыражениями вложенного невычисляемого операнда
Функция необходима для вычисления на этапе компиляции, если она является функцией constexpr и именуется выражением, потенциально вычисляемым на этапе компиляции.
Переменная необходима для вычисления на этапе компиляции, если она является переменной constexpr или имеет целочисленный тип с квалификатором const и без volatile либо ссылочный тип, а идентификаторное выражение, обозначающее её, потенциально вычисляется на этапе компиляции.
Определение функции с defaulted-реализацией и инстанцирование специализации шаблона функции или специализации шаблона переменной(начиная с C++14) выполняются, если функция или переменная(начиная с C++14) необходима для вычисления на этапе компиляции.
Константное подвыражение
Константное подвыражение — это выражение, вычисление которого как подвыражения выражения e не препятствовало бы тому, чтобы e было основным константным выражением, где e не является ни одним из следующих выражений:
| (начиная с C++20) |
Примечания
| Макрос проверки поддержки возможностей | Значение | Стандарт | Возможность |
|---|---|---|---|
__cpp_constexpr_in_decltype | 201711L |
(C++20) (DR11) | Создание определений функций и переменных, когда они необходимы для вычисления на этапе компиляции |
__cpp_constexpr_dynamic_alloc | 201907L | (C++20) | Операции с динамической длительностью хранения в функциях constexpr |
__cpp_constexpr | 202306L | (C++26) | Приведение constexpr из void*: на пути к стиранию типа constexpr |
202406L | (C++26) | Размещение new и new[] с constexpr | |
__cpp_constexpr_exceptions | 202411L | (C++26) | Исключения constexpr |
Пример
Отчёты об ошибках
Следующие отчёты об ошибках, изменяющие поведение, были применены ретроактивно к ранее опубликованным стандартам C++.
| DR | Применён к | Опубликованное поведение | Корректное поведение |
|---|---|---|---|
| CWG 94 | C++98 | арифметические константные выражения не могли содержать переменные и статические члены данных | могут |
| CWG 366 | C++98 | выражения со строковыми литералами могли быть целочисленными константными выражениями | не могут |
| CWG 457 | C++98 | выражения с volatile-переменными могли быть целочисленными константными выражениями | не могут |
| CWG 1293 | C++11 | не было ясно, можно ли использовать строковые литералы в константных выражениях | можно использовать |
| CWG 1311 | C++11 | volatile glvalue можно было использовать в константных выражениях | запрещено |
| CWG 1312 | C++11 |
reinterpret_cast запрещён в константных выражениях, но преобразование к void* и обратно позволяло достичь того же эффекта | запрещены преобразования из типа cv void* к типу указателя на объект |
| CWG 1313 | C++11 | неопределённое поведение допускалось; любое вычитание указателей было запрещено | неопределённое поведение запрещено; вычитание указателей в пределах одного массива допустимо |
| CWG 1405 | C++11 | для объектов, которые можно использовать в константных выражениях, их изменяемые подобъекты также можно было использовать | их использовать нельзя |
| CWG 1454 | C++11 | передача констант через функции constexpr посредством ссылок не допускалась | допускается |
| CWG 1455 | C++11 | преобразованные константные выражения могли быть только prvalue | могут быть lvalue |
| CWG 1456 | C++11 | адресное константное выражение не могло обозначать адрес сразу за концом массива | допускается |
| CWG 1535 | C++11 | выражение typeid с операндом полиморфного типа класса не являлось основным константным выражением, даже если проверка времени выполнения не требовалась | ограничение на операнд распространяется только на glvalue полиморфных типов классов |
| CWG 1581 | C++11 | функции, необходимые для вычисления на этапе компиляции, не требовалось определять или инстанцировать | требуется |
| CWG 1613 | C++11 | основные константные выражения могли вычислять любые ODR-используемые ссылки внутри лямбда-выражений | некоторые ссылки вычислять было нельзя |
| CWG 1694 | C++11 | привязка prvalue-указателя на локальную переменную к ссылке была константным выражением | это не является константным выражением |
| CWG 1872 | C++11 | основные константные выражения могли вызывать инстанцирования шаблонов функций constexpr, не удовлетворяющие требованиям к функциям constexpr | такие инстанцирования вызывать нельзя |
| CWG 1952 | C++11 | требовалось диагностировать неопределённое поведение стандартной библиотеки | не определено, должна ли выдаваться диагностика |
| CWG 2022 | C++98 | результат определения константного выражения мог зависеть от выполнения устранения копирования | считать, что устранение копирования выполняется всегда |
| CWG 2126 | C++11 | временные объекты литеральных типов с квалификатором const, инициализированные константой и с продлённым временем жизни, нельзя было использовать в константных выражениях | можно использовать |
| CWG 2129 | C++11 | целочисленные литералы не являлись константными выражениями | являются |
| CWG 2167 | C++11 | ссылки на нешаблонные сущности, локальные для вычисления, делали вычисление не constexpr | ссылки на нешаблонные сущности разрешены |
| CWG 2278 | C++98 | решение проблемы CWG 2022 нельзя было реализовать | считать, что устранение копирования никогда не выполняется |
| CWG 2299 | C++14 | не было ясно, можно ли использовать макросы из <cstdarg>при вычислении на этапе компиляции |
va_arg запрещён, va_start не определено |
| CWG 2400 | C++11 | вызов виртуальной функции constexpr для объекта, который нельзя использовать в константных выражениях и время жизни которого началось вне выражения, содержащего вызов, мог быть константным выражением | это не является константным выражением |
| CWG 2490 | C++20 | вызовы (псевдо)деструкторов не имели ограничений при вычислении на этапе компиляции | добавлено ограничение |
| CWG 2552 | C++23 | при вычислении основного константного выражения поток управления не мог проходить через объявление переменной вне блока | может |
| CWG 2558 | C++11 | неопределённое значение могло быть константным выражением | не является константным выражением |
| CWG 2647 | C++20 | переменные volatile-квалифицированных типов могли быть потенциально константными | не являются |
| CWG 2763 | C++11 | нарушение [[noreturn]] не требовалосьобнаруживать при вычислении на этапе компиляции | требуется |
| CWG 2851 | C++11 | преобразованные константные выражения не допускали преобразований чисел с плавающей точкой | допускать не сужающие преобразования чисел с плавающей точкой |
| CWG 2907 | C++11 | основные константные выражения не могли выполнять преобразования lvalue-to-rvalue для glvalue std::nullptr_t | могут выполнять такие преобразования |
| CWG 2909 | C++20 | переменная без инициализатора могла иметь константную инициализацию, только если её инициализация по умолчанию приводила к выполнению некоторой инициализации | константная инициализация возможна, только если тип является конструируемым по умолчанию для const |
| CWG 2924 | C++11 C++23 | не было определено, является ли выражение, нарушающее ограничения [[noreturn]] (C++11) или[[assume]] (C++23), основным константным выражением | определяется реализацией |
| P2280R4 | C++11 | вычисление выражения, содержащего идентификаторное выражение или *this, ссылающееся на объект или ссылку, время жизникоторых началось вне данного вычисления, не является константным выражением | может быть константным выражением |
См. также
спецификатор constexpr(C++11) | указывает, что значение переменной или функции может быть вычислено во время компиляции |
|
(C++11)(устарело в C++17)(удалено в C++20) | проверяет, является ли тип литеральным (шаблон класса) |
| Документация по C для константных выражений | |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/constant_expression