Question

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.

Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top