Orient Orient - 3 months ago 18
C++ Question

Does constexpr imply noexcept?

Does

constexpr
specifier imply
noexcept
specifier for a function? Answer to the similar question says "yes" concerning
inline
specifier, but Eric Niebler's article makes me wonder about possible answer to the current one. On my mind answer can depends on context of a using a
constexpr
function: is it constant expression context or run-time context, i.e. are all the parameters of the function known at compile time or not.

I expected that the answer is "yes", but simple check shows that it is not the case.

constexpr
bool f(int) noexcept
{
return true;
}

constexpr
bool g(int)
{
return true;
}

static_assert(noexcept(f(1)));
static_assert(noexcept(g(2))); // comment this line to check runtime behaviour

#include <cassert>
#include <cstdlib>

int
main(int argc, char * [])
{
assert(noexcept(f(argc)));
assert(noexcept(g(argc)));
return EXIT_SUCCESS;
}

Answer

No this can not be the case, because not every inovocation of constexpr function has to be able to be evaluated as subexpression of a core constant expression. We only need one argument value that allows for this. So a constexpr function can contain a throw statement as long as we have a argument value which does not invoke that branch.

This is covered in the draft C++14 standard section 7.1.5 The constexpr specifier [dcl.constexpr] which tells us what is allowed in a constexpr function:

The definition of a constexpr function shall satisfy the following constraints:

  • it shall not be virtual (10.3);

  • its return type shall be a literal type;

  • each of its parameter types shall be a literal type;

  • its function-body shall be = delete, = default, or a compound-statement that does not contain

    • an asm-definition,

    • a goto statement,

    • a try-block, or

    • a definition of a variable of non-literal type or of static or thread storage duration or for which no initialization is performed.

which as we can see does not forbid throw and in fact forbids very little since the Relaxing constraints on constexpr functions proposal became part of C++14.

Below we see the rule that says a constexpr function is well-formed if at least one argument value exists such that it can be evaluated as a subexpression of a core constant expression:

For a non-template, non-defaulted constexpr function or a non-template, non-defaulted, non-inheriting constexpr constructor, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression (5.19), the program is ill-formed; no diagnostic required.

and below this paragraph we have the following example, which shows a perfect example for this case:

constexpr int f(bool b)
  { return b ? throw 0 : 0; } // OK
constexpr int f() { return f(true); } // ill-formed, no diagnostic required

So we would expect the output for the following example:

#include <iostream>

constexpr int f(bool b)   { return b ? throw 0 : 0; } 

int main() {
    std::cout << noexcept( f(1) ) << "\n" 
              << noexcept( f(0) ) << "\n" ; 
}

to be (see it live with gcc):

 0
 1

Visual Studio via webcompiler also produces the same result. As hvd noted, clang has a bug as documented by the bug report noexcept should check whether the expression is a constant expression.

Defect Report 1129

Defect report 1129: Default nothrow for constexpr functions asks the same question:

A constexpr function is not permitted to return via an exception. This should be recognised, and a function declared constexpr without an explicit exception specification should be treated as if declared noexcept(true) rather than the usual noexcept(false). For a function template declared constexpr without an explicit exception specification, it should be considered noexcept(true) if and only if the constexpr keyword is respected on a given instantiation.

and the response was:

The premise is not correct: an exception is forbidden only when a constexpr function is invoked in a context that requires a constant expression. Used as an ordinary function, it can throw.

and it modified 5.3.7 [expr.unary.noexcept] paragraph 3 bullet 1 (addition noted with emphasis):

a potentially evaluated call80 to a function, member function, function pointer, or member function pointer that does not have a non-throwing exception-specification (15.4 [except.spec]), unless the call is a constant expression (5.20 [expr.const]),