When computing constant expressions to initialize a constexpr
it is possible to throw exceptions. For example here is an example where the computation of a constant expression is guarded against overflow:
#include <iostream>
#include <stdexcept>
constexpr int g(int n, int n0, int n1) {
return n == 0? n1: g(n - 1, n1, n0 + n1);
}
constexpr int f(int n) {
return n < 42? g(n, 0, 1): throw std::out_of_range("too big");
}
int main()
{
try {
constexpr int f41 = f(41); // OK: constexpr
int f43 = f(43); // OK: throws an exception
constexpr int f42 = f(42); // not OK but what happens?
}
catch (std::exception const& ex) {
std::cout << "ERROR: " << ex.what() << "\n";
}
}
The first call to f()
just shows that a constexpr
can be computed. The second call to f()
isn't used to initialize a constexpr
and throws a run-time exception. The third call to f()
is used to initialize a constexpr
but that point won't ever reached because an exception is thrown. However, what should happen in this case? I would expect the handler in the catch
-clause is executed but both gcc and clang produce a compile-time error.
The initialiser for a constexpr
variable must be a constant expression (C++11 §7.1.5/9):
A constexpr
specifier used in an object declaration declares the object as const
. Such an object shall have literal type and shall be initialized. If it is initialized by a constructor call, [...]. Otherwise, or if a constexpr
specifier is used in a reference declaration, every full-expression that appears in its initializer shall be a constant expression.
Note the following requirements for a constant expression (§5.19/2):
A conditional-expression is a core constant expression unless it involves one of the following as a potentially evaluated subexpression, but subexpressions of [...] conditional operations that are not evaluated are not considered
[...]
an invocation of a constexpr
function with arguments that, when substituted by function invocation substitution (7.1.5), do not produce a constant expression;
[...]
a throw-expression (15.1).
Function invocation substitution for a constexpr
function is defined as follows (§7.1.5/5):
Function invocation substitution for a call of a constexpr
function [...] means implicitly converting each argument to the corresponding parameter type as if by copy-initialization, substituting that converted expression for each use of the corresponding parameter in the function-body, and [...] implicitly converting the resulting returned expression or braced-init-list to the return type of the function as if by copy-initialization. Such substitution does not change the meaning.
As we saw above (§5.19/2), subexpressions of conditional operations that are not evaluated are not considered. f(42)
is not a constant expression because when you perform function invocation substitution on f
, it results in an expression with a throw
expression on the side of the conditional operation that is evaluated. On the other hand, for f(41)
, the throw
ends up on the side that isn't evaluated.
So the program is ill-formed. It doesn't matter whether the initialiser is actually reached or not because the program shouldn't compile.