Abstract
This paper proposes a new syntax for noexcept-specification, noexcept(auto), which deduces the exception specification of a function from its body or member initializer list. This feature aims to simplify the use of conditional noexcept and avoid the redundancy and ugliness of manually writing noexcept expressions.
This idea has been raised a few times in the past, cf. N4473, N3202 and others.
The problems and objections raised by those papers need to be reviewed and analysed for any new proposal to go forward.
Motivation
Since C++11 introduced the noexcept specifier, conditional noexcept has been widely used in the standard library. It is undeniable that noexcept can avoid the compiler generating code to handle exceptions and improve performance (both at compile time and run time). Therefore, [P2517] and [P2401] added conditional noexcept for some simple functions. However, there are still many functions that do not use conditional noexcept, but potentially-throwing, such as ranges algorithms, std::min, std::greater, etc.
I noticed that Microsoft STL orginally added conditional noexcept for std::greater::operator(): [snip]
(P2401 author here)
MS-STL seems to be a bit more generous than other stdlib
implementation at sprinkling noexcept; when doing research for
P2401 it seems that the presence of a conditional noexcept
improves MSVC's debug codegen. Implementations are anyhow allowed
to do so by [res.on.exception.handling]/5 .
Proposal
I propose to add noexcept(auto) as a placeholder for noexcept-specification, which is equivalent to noexcept(false), except in the following cases where it is equivalent to noexcept(true):
- It is not a non-throwing function as specified by the standard, and
I don't understand what this means.
- Any function call within the function body or member initializer list does not throw exceptions, or
- The function body is a try block, and the last catch catches all exceptions, or
- Within the function body, any function call that throws an exception is wrapped by a try block, and the last catch catches all exceptions
This is a classification which is way too simple. What if you
re-throw from a catch?
I suspect you really need a long and detailed list, along the
lines of [expr.const], to determine what would make a function
noexcept(false).
Furthermore, in constexpr if and consteval if, the discarded branch does not participate in this deduction.
Design Decisions
The motivations for this proposal is to simplify the use of conditional noexcept in the standard library and user code.
As noted before, this simplification has to consider a bunch of things:
1) Is it implementable? Does it require full-program analysis? Does it risk hitting implementation-defined limits? Do you run into Rice's theorem? A POC would be very much desirable.
Consider:
void a(int i) noexcept(auto) { if (i > 0) b(i-1); }
void b(int i) noexcept(auto) { if (i > 0) a(i-1); }
What is the compiler supposed to deduce here?
2) Would be acceptable that a "minor" modification of a noexcept(auto) function does, in fact, change its noexcept-ness, with possibly massive consequences for callers?
Say you have a move constructor defined as such:
MyClass::MyClass(MyClass &&other) noexcept(auto)
data(std::exchange(other.data), nullptr)
{
countObjectCreated(); // add this for debugging reasons
}
If someone forgets to add noexcept(true) to
countObjectCreated(), or simply cannot add it (it's a C
function; it's in another library; ...), then now the whole move
constructor is noexcept(false) with terrible consequences on
algorithms and so on.
My 2 c,
-- Giuseppe D'Angelo | giuseppe.dangelo@kdab.com | Senior Software Engineer KDAB (France) S.A.S., a KDAB Group company Tel. France +33 (0)4 90 84 08 53, http://www.kdab.com