Consider the following program:
#include <iostream> #include <type_traits> constexpr int f() { if (std::is_constant_evaluated()) return -1; else return 1; } int main() { int const i = f(); std::cout << i; }
It prints -1
when run (wandbox).
However, if I make the function throw
when evaluated at compile time::
#include <iostream> #include <type_traits> constexpr int f() { if (std::is_constant_evaluated()) throw -1; // <----------------------- Changed line else return 1; } int main() { int const i = f(); std::cout << i; }
it compiles fine and outputs 1 (wandbox). Why didn't I get a compile failure instead?
Constant evaluation is the process of computing the result of expressions during compilation. Only a subset of all expressions can be evaluated at compile-time.
An integer constant is a value that is determined at compile time and cannot be changed at run time. An integer constant expression is an expression that is composed of constants and evaluated to a constant at compile time.
A constant value is one that doesn't change. C++ provides two keywords to enable you to express the intent that an object is not intended to be modified, and to enforce that intent. C++ requires constant expressions — expressions that evaluate to a constant — for declarations of: Array bounds.
A constant expression is an expression that can be evaluated at compile time. Constants of integral or enumerated type are required in several different situations, such as array bounds, enumerator values, and case labels. Null pointer constants are a special case of integral constants.
Isn't constant evaluation fun?
There are a few places in the language where we try to do constant evaluation and if that fails, we fallback to doing non-constant evaluation. Static initialization is one such place, initializing constant integers is another.
What happens with:
int const i = f();
is that this could be constant evaluation, but it doesn't necessarily have to be. Because (non-constexpr
) constant integers can still be used as constant expressions, if they meet all the other conditions, we have to try. For instance:
const int n = 42; // const, not constexpr std::array<int, n> arr; // n is a constant expression, this is ok
So try we do - we call f()
as a constant expression. In this context, std::is_constant_evaluated()
is true
, so we hit the branch with the throw
and end up failing. Can't throw
during constant evaluation, so our constant evaluation fails.
But then we fallback, and we try again - this time calling f()
as a non-constant expression (i.e. std::is_constant_evaluated()
is false
). This path succeeds, giving us 1
, so i
is initialized with the value 1
. But notably, i
is not a constant expression at this point. A subsequent static_assert(i == 1)
would be ill-formed because i
's initializer was not a constant expression! Even though the non-constant initialization path happens to (otherwise) entirely satisfy the requirements of a constant expression.
Note that if we tried:
constexpr int i = f();
This would have failed because we cannot fallback to the non-constant initialization.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With