# constexpr
(opens new window) as having only one of its branches selected to be compiled.
# constexpr variables
A variable declared constexpr is implicitly const and its value may be used as a constant expression.
Comparison with #define
A constexpr is type-safe replacement for #define based compile-time expressions. With constexpr the compile-time evaluated expression is replaced with the result. For example:
will produce the following code:
A pre-processor based compile-time macro would be different. Consider:
which will obviously be converted to cout << 10 + 2; . However, the compiler would have to do more work. Also, it creates a problem if not used correctly.
For example (with #define ):
But a pre-evaluated constexpr would correctly give 24 .
Comparison with const
A const variable is a variable which needs memory for its storage. A constexpr does not. A constexpr produces compile time constant, which cannot be changed. You may argue that const may also not be changed. But consider:
With most compilers the second statement will fail (may work with GCC, for example). The size of any array, as you might know, has to be a constant expression (i.e. results in compile-time value). The second variable size2 is assigned some value that is decided at runtime (even though you know it is 10 , for the compiler it is not compile-time).
This means that a const may or may not be a true compile-time constant. You cannot guarantee or enforce that a particular const value is absolutely compile-time. You may use #define but it has its own pitfalls.
Therefore simply use:
A constexpr expression must evaluate to a compile-time value. Thus, you cannot use:
Unless the function ( abs ) is itself returning a constexpr .
All basic types can be initialized with constexpr .
Interestingly, and conveniently, you may also use auto :
# Static if statement
The if constexpr statement can be used to conditionally compile code. The condition must be a constant expression. The branch not selected is discarded. A discarded statement inside a template is not instantiated. For example:
In addition, variables and functions that are odr-used only inside discarded statements are not required to be defined, and discarded return statements are not used for function return type deduction.
if constexpr is distinct from #ifdef . #ifdef conditionally compiles code, but only based on conditions that can be evaluated at preprocessing time. For example, #ifdef could not be used to conditionally compile code depending on the value of a template parameter. On the other hand, if constexpr cannot be used to discard syntactically invalid code, while #ifdef can.
# constexpr functions
A function that is declared constexpr is implicitly inline and calls to such a function potentially yield constant expressions. For example, the following function, if called with constant expression arguments, yields a constant expression too:
Thus, the result of the function call may be used as an array bound or a template argument, or to initialize a constexpr variable:
Note that if you remove constexpr from function’s return type specification, assignment to S will not work, as S is a constexpr variable, and must be assigned a compile-time const. Similarly, size of array will also not be a constant-expression, if function Sum is not constexpr .
Interesting thing about constexpr functions is that you may also use it like ordinary functions:
Sum will not be a constexpr function now, it will be compiled as an ordinary function, taking variable (non-constant) arguments, and returning non-constant value. You need not to write two functions.
It also means that if you try to assign such call to a non-const variable, it won’t compile:
The reason is simple: constexpr must only be assigned a compile-time constant. However, the above function call makes Sum a non- constexpr (R-value is non-const, but L-value is declaring itself to be constexpr ).
The constexpr function must also return a compile-time constant. Following will not compile:
Because a1 is a non-constexpr variable, and prohibits the function from being a true constexpr function. Making it constexpr and assigning it a will also not work — since value of a (incoming parameter) is still not yet known:
Furthermore, following will also not compile:
Since abs(a) is not a constant expression (even abs(10) will not work, since abs is not returning a constexpr int !
What about this?
We crafted our own Abs function which is a constexpr , and the body of Abs also doesn’t break any rule. Also, at the call site (inside Sum ), the expression evaluates to a constexpr . Hence, the call to Sum(-10, 20) will be a compile-time constant expression resulting to 30 .
# Remarks
The constexpr keyword was added in C++11 but for a few years since the C++11 standard was published, not all major compilers supported it. at the time that the C++11 standard was published. As of the time of publication of C++14, all major compilers support constexpr .
Спецификатор constexpr в C++: зачем он нужен и как работает
Новички могут путать спецификатор constexpr , появившийся в C++ с версии 11, с похожим на него квалификатором const .
constexpr обозначает константное выражение и используется для определения переменной или функции в качестве константного выражения, которое может вычисляться на этапе компиляции. Это его главная особенность — код может быть выполнен до его запуска.
constexpr можно использовать с переменными и функциями, в том числе конструкторами и операторами if . Разберемся в этом подробнее.
Переменные
Квалификатор const указывает компиляторам и другим программистам, что переменная доступна только для чтения. Любая попытка изменить ее приведет к ошибке компиляции.
В данном случае constexpr похож на const тем, что подразумевает const . Его тоже нельзя изменить. Разница в том, что const может быть вычислен как на стадии компиляции, так при выполнении программы, в зависимости от выбранного варианта инициализации.
В этом примере val вычисляется во время компиляции. При выполнении кода val всегда равно 3 . Однако в следующем примере val вычисляется во время выполнения кода, поскольку включает вызов функции.
Поскольку первый пример вычисляется во время компиляции, мы можем заменить его на constexpr .
Но это не применимо ко второму примеру. Мы получим ошибку компиляции из-за невозможности вычисления на этом этапе.
Чтобы устранить эту проблему в данном примере, можно преобразовать функцию в constexpr .
Код выглядит так же, но теперь выполняется во время компиляции. При выполнении компилятор модифицирует код так:
Здесь не указаны const и constexpr , потому что после запуска программы они больше не используются. И const , и constexpr применяются только в процессе компиляции.
Функции и конструкторы
constexpr можно также использовать с функциями и конструкторами. Как и в предыдущем примере, можно определить функцию или конструктор как функцию constexpr .
Функции constexpr отличаются гибкостью. Одна и та же функция может быть вычислена во время компиляции и выполнения. Все зависит от того, как они вызываются.
В этом примере есть функция constexpr под названием Sum , которая вызывается в строках 6 и 8. Строка 6 вычисляется при компиляции (как и в примере предыдущего раздела), а строка 8 — при выполнении, поскольку включает неконстантную переменную var .
Очевидно, что функция constexpr весьма полезна. Мы можем предоставить компилятору выбор: вычислять ее при компиляции или при выполнении. Для вычисления во время компиляции должен выполняться целый ряд условий (подробнее об этом по ссылке).
Операторы if
Начиная с C++17, можно определять if -операторы constexpr . Если вы не часто сталкиваетесь с общим кодом в повседневной работе, то, возможно, пользуетесь этой возможностью. Чтобы выяснить, зачем она необходима, нужно понимать концепцию SFINAE (Substitution Failure is not an Error, ошибка замены не является ошибкой) в метапрограммировании шаблона C++.
SFINAE и std::enable_if
При вызове функции компилятор должен выполнить проверку, чтобы узнать, какую из функций вызывать. Учитывайте перегрузку функций (function overloading), позволяющую давать нескольким функциям одинаковые имена. Говоря упрощенно, есть несколько шагов:
- Name lookup (поиск по имени);
- Template Argument Deduction (дедукция аргумента шаблона);
- Template Argument Substitution (замена аргумента шаблона);
- Overload Resolution (разрешение перегрузки).
В первых трех шагах создается набор функций перегрузки, которые будут использованы на последнем шаге. Overload Resolution выберет функцию с наиболее точно подходящими параметрами.
Алгоритм SFINAE проявляется на шаге 3 (Template Argument Substitution), когда функция-кандидат проваливает тест Substitution (замены). Ошибка компиляции не возникает, а функция просто удаляется из списка кандидатов, как в следующем примере.
Нам нужно написать общую функцию Square() , которая может быть как арифметического, так и определяемого пользователем типа. Определяемый пользователем тип — это шаблон класса, показанный ниже.
Если бы не поддержка этого шаблона класса, мы могли бы легко реализовать шаблон функции следующим образом.
Однако этот шаблон функции не работает, когда ему передается объект типа Number<int> .
Строка 9 не скомпилируется, потому что Number<int> не реализует operator* .
Для решения этой проблемы нужно знать тип, переданный шаблону функции Square() , и добавить в него оператор проверки if-else , который будет вычисляться во время компиляции:
Но такое решение не работает: при вызове, например с int , эта функция пытается найти int.value , которого не существует. Чтобы увидеть это более четко, смотрим результат создания экземпляра шаблона.
Теперь понятно, почему он не работает. Часть else не удалена из функции, и мы получаем ошибку компиляции.
Чтобы решить эту проблему, нужны два шаблона функций, проверяющих, является ли передаваемый тип арифметическим.
Здесь указаны два шаблона функций: для арифметических и неарифметических типов. У std::enable_if с typedef :: type , если ему передается значение true , будет спецификатор доступа public. В противном случае члены с typedef с спецификатором доступа public будут отсутствовать.
При передаче Number<int> в Square() первый шаблон функции завершает замену шаблона с ошибкой, а второй — успешно. Ошибки для первой функции не возникает, она просто удаляется из списка функций-кандидатов. Затем компилятор выбирает вторую функцию.
С помощью двух и более шаблонов функций с std::enable_if мы в некотором роде имитируем if-else во время компиляции.
Как оператор constexpr if улучшает SFINAE
Взаимодействие SFINAE и std::enable_if работает и часто используется, но не очень интуитивно понятно. Такой излишне подробный код и незнакомый синтаксис порой трудно читать.
Сделать его более читаемым, начиная с C++17, позволяют if -операторы constexpr . Можно использовать настоящий if-else во время компиляции в одной функции, а не имитировать его, используя несколько функций с std::enable_if . Ниже показана реализация с помощью if -оператора constexpr .
В этом примере используется только один шаблон функции, который к тому же намного ближе к знакомому нам оператору if-else . Этот способ работает, потому что компилятор берет только ветку с истинным условием ( true ) и отбрасывает другие.
Спецификатор constexpr в C++11 и в C++14
Одна из новых возможностей C++11 — спецификатор constexpr . С помощью него можно создавать переменные, функции и даже объекты, которые будут рассчитаны на этапе компиляции. Это удобно, ведь раньше для таких целей приходилось использовать шаблоны. Но тут все не так просто. У тех, кто не так хорошо знаком с constexpr , может сложиться впечатление, что теперь не будет никаких проблем с расчетами на этапе компиляции. Но на constexpr -выражения наложены серьезные ограничения.
В первой части будет рассказано про constexpr , о том, какие будут изменения в стандарте C++14, а во второй части будет пример использования constexpr : библиотека, которая считает результат математического выражения в строке.
С помощью нее можно будет написать следующий код:
И ответ в виде дроби будет получен на этапе компиляции:
Answer is 37/24
Сразу предупреждаю, код этой библиотеки сложно понять.
Кому эта тема интересна, добро пожаловать под кат!
Что такое constexpr?
Сначала пара слов о том, что вообще такое спецификатор constexpr . Как уже было сказано, с помощью него можно производить какие-то операции на этапе компиляции. Выглядит это так:
constexpr-функция
constexpr возвращаемое_значение имя_функции (параметры)
Ключевое слово constexpr , добавленное в C++11, перед функцией означает, что если значения параметров возможно посчитать на этапе компиляции, то возвращаемое значение также должно посчитаться на этапе компиляции. Если значение хотя бы одного параметра будет неизвестно на этапе компиляции, то функция будет запущена в runtime (а не будет выведена ошибка компиляции).
constexpr-переменная
constexpr тип = expression;
Ключевое слово в данном случае означает создание константы. Причем expression должно быть известно на этапе компиляции.
Рассмотрим такой пример:
constexpr -переменная является константой ( const ), но константа не является constexpr-переменной.
В случае «утери» constexpr -спецификатора переменной вернуть обратно его уже не получится, даже если значение может посчитаться на этапе компиляции. constexpr -спецификатор нельзя добавить с помощью const_cast, так как constexpr не является cv-спецификатором (это const и volatile ). Такой код не заработает:
Параметры функций не могут быть constexpr . То есть не получится создать исключительно constexpr -функцию, которая может работать только на этапе компиляции.
Также constexpr -функции могут работать с классами, это будет рассмотрено позже.
GCC, начиная с версии 4.4, поддерживает constexpr -функции, Clang также поддерживает с версии 2.9, а Visual Studio 2013 не поддерживает (но в Visual Studio «14» CTP наконец добавили поддержку).
Ограничения
Теперь, когда вы поняли, как это все удобно, можно добавить ложку дегтя в бочку меда. Причем довольно большую ложку.
- Скалярный тип
- Указатель
- Массив скалярных типов
- Класс, который удовлетворяет следующим условиям:
- Имеет деструктор по умолчанию
- Все нестатические члены класса должны быть литеральными типами
- Класс должен иметь хотя бы один constexpr -конструктор (но не конструктор копирования и перемещения) или не иметь конструкторов вовсе
- Ее тип должен быть литеральным
- Ей должно быть сразу присвоено значение или вызван constexpr -конструктор
- Параметры конструктора или присвоенное значение могут содержать только литералы или constexpr -переменные и constexpr -функции
- Она не может быть виртуальной ( virtual )
- Она должна возвращать литеральный тип ( void вернуть нельзя*)
- Все параметры должны иметь литеральный тип
- Тело функции должно содержать только следующее:
- static_assert
- typedef или using , которые объявляют все типы, кроме классов и перечислений ( enum )
- using для указания видимости имен или пространств имен ( namespace )
- Ровно один return , который может содержать только литералы или constexpr -переменные и constexpr -функции
На constexpr -конструкторы наложены такие же ограничения, как и на функции, за исключением пункта про return и с добавлением одного нового пункта:
Все нестатические члены класса и члены базовых классов должны быть инициализированы каким-либо образом (в конструкторе, используя списки иницилизации или иницилизацией членов класса при объявлении), причем присвоенные им выражения должны содержать только литералы или constexpr -переменные и constexpr -функции.Получается, что в функциях нельзя инициализировать переменные, создавать циклы и конструкции if-else . С одной стороны, эти ограничения сделаны из-за того, что компилятору нужно хоть как-то отслеживать выполнение программы во время компиляции (рекурсию проще прерывать, чем циклы). С другой — писать сложные функции становится проблематично.
Конечно, все равно все эти возможности можно реализовать. Вместо циклов использовать рекурсию, вместо конструкции if-else — оператор « ? : », а вместо создания переменных использовать значения функции.
Все это сильно напоминает функциональное программирование. В функциональных языках программирования, как правило, также нельзя заводить переменные и отсутствуют циклы. Действительно, функции вызывать можно, функции высших порядков тоже можно реализовать, используя указатели на функции (к сожалению, анонимные функции (лямбды) нельзя использовать в constexpr -конструкциях). Также все constexpr-функции являются чистыми функциями (зависят только от своих параметров и возвращают только свой результат). Чтобы писать constexpr -алгоритмы, нужно иметь хотя бы начальные знания функционального программирования.
Но тут у C++ большие проблемы с синтаксисом: анонимные функции нельзя использовать, все действия функции являются одним длинным выражением, а с добавлением оператора « ? : » код вовсе становится нечитабельным. Также все это сопровождается непонятными сообщениями об ошибке, которые могут занимать сотни строк.
Но на этом проблемы не заканчиваются. Когда пишешь какую-то constexpr -функцию, которую потом будут часто использовать, хорошо бы возвращать читабельную ошибку. Тут можно ошибочно предположить, что static_assert как раз для этого подходит. Но static_assert использовать не получится, так как параметры функций не могут быть constexpr , из-за чего значения параметров не гарантированно будут известны на этапе компиляции.
Как же выводить ошибки? Единственный более-менее нормальный способ, который я нашел, заключается в выбрасывании исключения:В случае вызова функции во время компиляции мы увидим ошибку, что конструкция throw не может находиться в constexpr-функции, а в runtime функция выбросит исключение.
Ошибку сложно будет найти, но хоть что-то.Такой способ вывода ошибки еще не соответствует стандарту языка, ничего не запрещает компилятору всегда выдавать ошибку о том, что нельзя использовать throw в constexpr -функции. В GCC 4.8.2 это работает, а в Visual Studio «14» CTP C++ compiler — уже нет.
В итоге сложно писать, сложно отлаживать, сложно понимать такие конструкции.
Но все не так плохо, в C++14 очень многие ограничения уберут.Изменения в C++14
Как уже было сказано, в новом стандарте void также будет литеральным типом, и теперь можно будет создавать функции, которые, например, будут проверять значения параметров на правильность.
Второе незначительное изменение заключается в том, что теперь constexpr функции-члены класса не являются константными.
В C++11 следующие строчки были равносильными, а в С++14 это уже не так:Объяснение этому можно найти тут.
- Ассемблерных вставок
- Ключевого слова goto
- Определения переменных нелитерального типа или static и thread_safe -переменных. Все переменные должны инициализироваться при определении.
- Он должен соответствовать всем условиям constexpr -функции
- Все его нестатические члены должны иметь литеральный тип
- Аналогичное условие про то, что все нестатические члены класса должны каким-либо способом инициализироваться
- Появилась возможность использовать union ‘ы, но с некоторыми ограничениями
В итоге после появления компиляторов, которые поддерживают C++14, можно будет писать constexpr-функции, которые почти ничем не будут отличаться от обычных. А пока приходится писать довольно запутанный код.
Пример использования constexpr на C++11
В качестве примера использования constexpr будет приведена библиотека, которая будет считать результат математического выражения, находящегося в строке.
Итак, мы хотим, чтобы можно было писать такой код:
Тут используется еще одна новая возможность C++11: пользовательские литералы. В данном случае они хороши тем, что функция гарантированно будет вызвана на этапе компиляции, даже если получившееся значение будет присвоено не constexpr -переменной.
Объявляется пользовательский литерал таким образом:
В качестве ассерта будет использоваться следующий макрос:
Библиотека может складывать, вычитать, умножать, делить, возводить в степень, также есть поддержка скобок. Реализовано это будет с помощью рекурсивного спуска.
- Сложение и вычитание
- Умножение и деление
- Возведение в целую степень
А возвращать эти функции будут структуру SMathValue . В ней хранятся посчитанное значение и end — переменная, в которую записан конец числа, суммы, произведения или чего-то еще:
Для считывания числа будут 3 функции (одна основная и две вспомогательных):
Вот такая запутанная конструкция получается. get_number проверяет, что на текущем индексе действительно число и вызывает _get_number , передавая в качестве первой итерации конец числа (число читается справа налево).
Работа со скобками:
Если на текущем индексе число, то функция вызывает get_number , в противном случае функция считает выражение в скобках.
Дальше идет функция возведения в степень:
В функции _get_pow проверяется, что текущий символ ‘^’ . Если это так, то функция вызывает сама себя (точнее get_pow ), передав туда новое значение, равное value в степени прочитанное_значение.
Получается, что строка «25» правильно обработается, если для нее вызвать get_pow . Так как в этом случае просто прочитается число, после чего оно вернется.
math_pow — простая constexpr -функция возведения в целую степень.Произведение и деление обрабатываются в одной функции:
Довольно сложно понять эту конструкцию, писать ее также затруднительно. Тут идет проверка, является ли текущий символ ‘*’ , если это так, то функция вызывает сама себя, перемножая value на прочитанное число (или выражение). В случае с ‘/’ функция ведет себя аналогично, только перед этим идет проверка на то, что знаменатель не равен нулю. Если текущий символ не является ‘*’ или ‘/’ , то просто возвращается значение.
Аналогично происходит с суммой и разностью:
И наконец, осталось реализовать функцию solve :
И последнее, что можно сделать: использовать свой класс чисел, в котором будут храниться числитель и знаменатель как отдельные переменные. Тут ничего особенного, просто все функции и конструктор имеют спецификатор constexpr .
После этого надо немного изменить код рекурсивного спуска и в итоге получить требуемое.
Написание рекурсивного спуска на constexpr-функциях заняло где-то день, хотя обычный рекурсивный спуск без проблем пишется за час. Проблемы были с путаницей со скобками, со сложностью отладки, с непонятными ошибками, с непродуманностью архитектуры (да, теперь даже для рекурсивного спуска надо тщательно все продумывать).Name already in use
cpp-docs / docs / cpp / constexpr-cpp.md
- Go to file T
- Go to line L
- Copy path
- Copy permalink
- Open with Desktop
- View raw
- Copy raw contents Copy raw contents
Copy raw contents
Copy raw contents
The keyword constexpr was introduced in C++11 and improved in C++14. It means constant expression. Like const , it can be applied to variables: A compiler error is raised when any code attempts to modify the value. Unlike const , constexpr can also be applied to functions and class constructors. constexpr indicates that the value, or return value, is constant and, where possible, is computed at compile time.
A constexpr integral value can be used wherever a const integer is required, such as in template arguments and array declarations. And when a value is computed at compile time instead of run time, it helps your program run faster and use less memory.
To limit the complexity of compile-time constant computations, and their potential impacts on compilation time, the C++14 standard requires the types in constant expressions to be literal types.
constexpr literal-type identifier = constant-expression ;
constexpr literal-type identifier <constant-expression > ;
constexpr literal-type identifier ( params ) ;
constexpr ctor ( params ) ;params
One or more parameters, each of which must be a literal type and must itself be a constant expression.A constexpr variable or function must return a literal type.
The primary difference between const and constexpr variables is that the initialization of a const variable can be deferred until run time. A constexpr variable must be initialized at compile time. All constexpr variables are const .
A variable can be declared with constexpr , when it has a literal type and is initialized. If the initialization is performed by a constructor, the constructor must be declared as constexpr .
A reference may be declared as constexpr when both these conditions are met: The referenced object is initialized by a constant expression, and any implicit conversions invoked during initialization are also constant expressions.
All declarations of a constexpr variable or function must have the constexpr specifier.
A constexpr function is one whose return value is computable at compile time when consuming code requires it. Consuming code requires the return value at compile time to initialize a constexpr variable, or to provide a non-type template argument. When its arguments are constexpr values, a constexpr function produces a compile-time constant. When called with non- constexpr arguments, or when its value isn’t required at compile time, it produces a value at run time like a regular function. (This dual behavior saves you from having to write constexpr and non- constexpr versions of the same function.)
A constexpr function or constructor is implicitly inline .
The following rules apply to constexpr functions:
A constexpr function must accept and return only literal types.
A constexpr function can be recursive.
Before C++20, a constexpr function can’t be virtual, and a constructor can’t be defined as constexpr when the enclosing class has any virtual base classes. In C++20 and later, a constexpr function can be virtual. Visual Studio 2019 version 16.10 and later versions support constexpr virtual functions when you specify the /std:c++20 or later compiler option.
The body can be defined as = default or = delete .
The body can contain no goto statements or try blocks.
An explicit specialization of a non- constexpr template can be declared as constexpr :
An explicit specialization of a constexpr template doesn’t also have to be constexpr :
The following rules apply to constexpr functions in Visual Studio 2017 and later:
It may contain if and switch statements, and all looping statements including for , range-based for , while , and do-while.
It may contain local variable declarations, but the variable must be initialized. It must be a literal type, and can’t be static or thread-local. The locally declared variable isn’t required to be const , and may mutate.
A constexpr non- static member function isn’t required to be implicitly const .
[!TIP] In the Visual Studio debugger, when debugging a non-optimised Debug build, you can tell whether a constexpr function is being evaluated at compile time by putting a breakpoint inside it. If the breakpoint is hit, the function was called at run-time. If not, then the function was called at compile time.
The /Zc:externConstexpr compiler option causes the compiler to apply external linkage to variables declared by using extern constexpr. In earlier versions of Visual Studio, either by default or when /Zc:externConstexpr- is specified, Visual Studio applies internal linkage to constexpr variables even when the extern keyword is used. The /Zc:externConstexpr option is available starting in Visual Studio 2017 Update 15.6, and is off by default. The /permissive- option doesn’t enable /Zc:externConstexpr.
The following example shows constexpr variables, functions, and a user-defined type. In the last statement in main() , the constexpr member function GetValue() is a run-time call because the value isn’t required to be known at compile time.