Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

`noexcept` behavior of `constexpr` functions

The wording of [expr.unary.noexcept] changed in C++17.


Previously (n4140, 5.3.7 noexcept operator [expr.unary.noexcept]), my emphasis:

  1. The result of the noexcept operator is false if in a potentially-evaluated context the expression would contain

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


Now1 (7.6.2.6 noexcept operator [expr.unary.noexcept]):

  1. The result of the noexcept operator is true unless the expression is potentially-throwing ([except.spec]).

And then in 14.5 Exception specifications [except.spec]:

  1. If a declaration of a function does not have a noexcept-specifier, the declaration has a potentially throwing exception specification unless ...

but the unless list of 14.5(3) doesn't list constexpr, leaving it as potentially throwing...

1 a link to C++17 n4659 added by L.F. in a comment.


Test code

constexpr int f(int i) { return i; }

std::cout << boolalpha << noexcept(f(7)) << std::endl;
int a = 7;
std::cout << boolalpha << noexcept(f(a)) << std::endl;

used to print (with gcc 8.3):

true
false

both when compiled with -std=c++11 and -std=c++2a


However the same code prints now (with gcc 9.2):

false
false

both when compiled with -std=c++11 and -std=c++2a


Clang by the way is very consistent, since 3.4.1 and goes with:

false
false

  • What is the right behavior per each spec?
  • Was there a real change in the spec? If so, what is the reason for this change?
  • If there is a change in the spec that affects or contradicts past behavior, would it be a common practice to emphasize that change and its implications? If the change is not emphasized can it imply that it might be an oversight?
  • If this is a real intended change, was it considered a bug fix that should go back to previous versions of the spec, are compilers right with aligning the new behavior retroactively to C++11?

Side Note: the noexcept deduction on a constexpr function affects this trick.

like image 786
Amir Kirsh Avatar asked Mar 08 '20 09:03

Amir Kirsh


Video Answer


1 Answers

Summary

What is the right behavior per each spec?

true false before C++17, false false since C++17.

Was there a real change in the spec? If so, what is the reason for this change?

Yes. See the quote from the Clang bug report below.

If there is a change in the spec that affects or contradicts past behavior, would it be a common practice to emphasize that change and its implications? If the change is not emphasized can it imply that it might be an oversight?

Yes; yes (but CWG found a reason to justify the oversight later, so it was kept as-is).

If this is a real intended change, was it considered a bug fix that should go back to previous versions of the spec, are compilers right with aligning the new behavior retroactively to C++11?

I'm not sure. See the quote from the Clang bug report below.

Detail

I have searched many places, and so far the closest thing I can find is the comments on relevant bug reports:

  • GCC Bug 87603 - [C++17] noexcept isn't special cased for constant expressions anymore

    CWG 1129 (which ended up in C++11) added a special case to noexcept for constant expressions, so that:

    constexpr void f() {} static_assert(noexcept(f()));
    

    CWG 1351 (which ended up in C++14) changed the wording significantly, but the special case remained, in a different form.

    P0003R5 (which ended up in C++17) changed the wording again, but the special case was removed (by accident), so now:

    constexpr void f() {} static_assert(!noexcept(f()));
    

    According to Richard Smith in LLVM 15481, CWG discussed this but decided to keep the behavior as-is. Currently, clang does the right thing for C++17 (and fails for C++14 and C++11, on purpose). g++, however, implemented the special case for C++11 already, but not the change for C++17. Currently, icc and msvc seem to behave like g++.

  • Clang Bug 15481 - noexcept should check whether the expression is a constant expression

    The constant expression special case was removed -- apparently by accident -- by wg21.link/p0003. I'm investigating whether it's going to stay gone or not.

    Did you do anything to avoid quadratic runtime on deeply-nested expressions?

    [...]

    Conclusion from CWG discussion: we're going to keep this as-is. noexcept has no special rule for constant expressions.

    It turns out this is actually essential for proper library functionality: e.g., if noexcept tries evaluating its operand, then (for example) is_nothrow_swappable is broken by making std::swap constexpr, because std::swap<T> then often ends up getting instantiated before T is complete.

    As a result of that, I'm also going to consider this change as an effective DR against C++11 and C++14... but I'm open to reconsidering if we see many user complaints.

In other words, the special rule was accidentally removed by P0003, but CWG decided to keep the removal.

like image 189
L. F. Avatar answered Nov 14 '22 21:11

L. F.