16

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.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • 2
    Haven't you asked something similar recently? Doesn't the evaluation of the throw-expression simply make the evaluated constexpr function a *not-a-constant-expression*? – dyp Jan 01 '14 at 15:47
  • I think [expr.const]/2 applies: A conditional expression is *not* a core constant expression if it contains "an invocation of a `constexpr` function with arguments that, when substituted by function invocation substitution, do not produce a core constant expression;" – dyp Jan 01 '14 at 15:51
  • @DyP: I have asked about [throwing from the ternary operator](http://stackoverflow.com/q/20848132/1120273) before with `constexpr`s in mind. However, I didn't expect exceptions to cause compile-time errors when they are thrown and caught: after all, the initialization of the `constexpr` would never be reached... – Dietmar Kühl Jan 01 '14 at 15:51
  • I'd just say the initializer is not a constant expression, therefore ill-formed. That's at least how I understand it. Note that [expr.const] contains an example for constexpr functions being resolved to non-constant-expressions. – dyp Jan 01 '14 at 15:53
  • 3
    Maybe I'm oversimplifying the question, but I found the clang++ error `test.cpp:17:23: error: constexpr variable 'f42' must be initialized by a constant expression` to be rather clear...? – Joachim Isaksson Jan 01 '14 at 15:56
  • @JoachimIsaksson: I understand the error message. However, since an exception is thrown while evaluating the initializer of the `constexpr` variable, the code never gets to the initialization. I'm not saying that is wrong that an error is produced rather than exception (I think I even prefer it that way) but I still consider it odd. – Dietmar Kühl Jan 01 '14 at 15:58

1 Answers1

13

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.

Community
  • 1
  • 1
Joseph Mansfield
  • 108,238
  • 20
  • 242
  • 324