Does constexpr imply noexcept?
Asked Answered
S

4

45

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;
}
Sisto answered 1/1, 2016 at 11:39 Comment(5)
@cad anyways the question is very general, don't think there is good specific example.Sisto
Counter-example: constexpr void * foo(int n) { return n == 0 ? nullptr : operator new(n); }. Demo.Plinth
I did abuse this once, see https://mcmap.net/q/246397/-is-is_constexpr-possible-in-c-11Ashly
This is not true, it is valid to have throws in a constexpr function, see my answer below as well.Urbai
See my updated answer, we have a defect report which addresses this exact question.Urbai
U
33

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]),

Urbai answered 1/1, 2016 at 18:0 Comment(1)
Running the wandbox example you provided, now produces two 0's as a result (press the run button again to see that). Something changed in gcc since you first wrote that test. Do you happen to know whether it's a defect or a fix to a defect? Here's an example on godbolt too, that compares the outputs of gcc 8.3 with gcc 9.2: godbolt.org/z/W6-xhBDoolie
O
6

It is said of noexcept that:

The result is false if the expression contains [...] call to any type of function that does not have non-throwing exception specification, unless it is a constant expression.

Also, about constexpr, it is true that:

the noexcept operator always returns true for a constant expression

Under no circumstances does it seem to imply that the constexpr specifier forces a noexcept specifier for the surrounded expression, as someone showed in the comments with a counterexample and you also verified.

Anyway, from the documentation, there is the following interesting note about the relationship between noexcept and constexpr:

Because the noexcept operator always returns true for a constant expression, it can be used to check if a particular invocation of a constexpr function takes the constant expression branch

EDIT: example with GCC

Thanks to @hvd for his interesting comment/example with GCC about my last quote.

constexpr int f(int i) {
    return i == 0 ? i : f(i - 1);
}

int main() {
    noexcept(f(512));
    return noexcept(f(0)) == noexcept(f(0));
}

The code above returns 0, with a warning that the statement noexcept(f(512)) has no effect.
Commenting out that statement that supposedly has no effect, the return value changes to 1.

EDIT: known bug on clang

Again, thanks to @hvd also for this link, that is about a well known bug in clang regarding the code mentioned in the question.

Quote from the bug report:

Quoth the book of C++, [expr.unary.noexcept]p3:

"The result of the noexcept operator is false if in a potentially-evaluated context the expression would contain a potentially-evaluated call to a function, member function, function pointer, or member function pointer that does not have a non-throwing exception-specification (15.4), unless the call is a constant expression (5.19)".

We do not implement that last phrase.

Oberon answered 1/1, 2016 at 12:24 Comment(6)
Perhaps an interesting example with GCC of your last quote: constexpr int f(int i) { return i == 0 ? i : f(i - 1); } int main() { noexcept(f(512)); return noexcept(f(0)) == noexcept(f(0)); }. This returns 0, with a warning that the statement noexcept(f(512)); has no effect. Commenting out that statement that supposedly has no effect, the return value changes to 1.Nymph
@hvd I guess an example can be found in the linked documentation too, anyway thank you, interesting comment.Oberon
As for the code in the question, that's already known as a bug in clang. I'd normally post it as a separate answer, but in this case I think it's small enough that you can include it in your answer, if you want.Nymph
Heh, I meant the link to the clang bug report, but it's fine with me either way. :)Nymph
@hvd two is better than one. ;-)Oberon
That GCC example code returns 1 for me (tested on GCC 5.2 and 6.3). I don't understand how it could ever return 0, given that you're something to itself, or what it's suppsed to illustrate.Smallage
C
4

You are allowed to throw an exception in a constexpr function. It is designed as such so that the implementer can indicate an error to the compiler. Consider that you have the following function:

constexpr int fast_sqrt(int x) {
    return (x < 0) ? throw invalid_domain() : fast_sqrt_impl(x);
}

In this case if x is negative we need to stop compilation immediately and indicate the problem to the user via compiler error. This follows the idea that compiler errors are better than runtime errors (fail fast).

C++ standard says this in (5.20):

A conditional-expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine (1.9), would evaluate one of the following expressions:

— a throw-expression (5.17)

Cretan answered 1/1, 2016 at 16:2 Comment(2)
Your standard does not fully back up your answer, you need to also quote 7.1.5 as I do in my answer to prove this is valid code.Urbai
@Shafik, I was looking for the part that has the criteria for a constexpr function. Thanks for including that!Cretan
M
2

No, in general it doesn't.

A constexpr function can be invoked in a non-constepr context in which it is allowed to throw an exception (except of course if you manually specify it to be noexcept(true)).

However, as part of a constant expression (such as in your example), it should behave as if specified to be noexcept(true) (of course, if the evaluation of the expression would result in an exception being thrown this can't result in a call to std::terminate as the program isn't running yet, but instead leads to a compile time error).

As I would have expected, your example doesn't trigger the static assertion with MSVC and g++. I'm not sure, whether this is a bug in clang or I'm missunderstanding something in the standard.

Mandiemandingo answered 1/1, 2016 at 12:36 Comment(4)
In what way, then, does it imply noexcept (true), if you already say the effect of noexcept (true) does not apply?Nymph
@hvd: As skypjack cited: applying the noexcept operator to a constexpr function in a constexpr context (as in the question yields true, just as if that function was declared (noexcept(true)).Mandiemandingo
A function being declared noexcept(true) has two effects: one, it makes exceptions terminate the program. Two, it makes the function call not affect the result of the noexcept operator. It doesn't have that first effect. It does behave in a way similar to that second effect, but not exactly the same way, and either way, that second effect isn't achieved by implicitly making the function noexcept(true), it's by an additional exception in the rules of the noexcept operator, as shown in @skypjack's answer.Nymph
@hvd: Thanks, I changed the wording of my answer accordingly. Interestingly, when compiled with clang the example triggers the static assertion, when compiled with g++ or msvc it doesn't.Mandiemandingo

© 2022 - 2024 — McMap. All rights reserved.