Description
Problem and solution
The wording for the specification of facilities that propagate exceptions inadvertently exclude some cases of thrown exceptions. The ones I've identified have the form "any exception thrown by the {(selected) constructor,construction} of {T
,some object}". This formulation excludes exceptions thrown by the initialization of the constructor's parameters ([expr.call] p7). These are not thrown by the constructor nor during the construction of the object (after its construction has begun ([class.cdtor] p1)).
Implying that the initialization of the constructor's parameter are part of the effects for which exceptions are propagated can be done with these changes:
- "Any exception thrown by calling the (selected) constructor of
T
. - "Any exception thrown by the
constructioninitialization of {T
,some object}".
The former is already present in the standard, as exemplified below. As for the latter (emphasis mine),
The process of initialization described in [dcl.init] applies to all initializations regardless of syntactic context, including the initialization of a function parameter ([expr.call]), the initialization of a return value ([stmt.return]), or when an initializer follows a declarator.
-- https://eel.is/c++draft/dcl.init p1
Examples
Example, good
Constructors and member functions of pair do not throw exceptions unless one of the element-wise operations specified to be called for that operation throws an exception. -- https://eel.is/c++draft/pairs.pair#1
Talking in terms of operations includes initialization.
Example, bad
For each tuple constructor, an exception is thrown only if the construction of one of the types in Types throws an exception. -- https://eel.is/c++draft/tuple.cnstr#2
We can make a tuple
constructor throw an exception other than by constructing one of the specified types: https://godbolt.org/z/enGThMq4P.
#include <tuple>
struct X { X(int) { } };
struct Y { operator int() { throw 0; } };
int main() { std::tuple<X>(Y{}); }
As described at the top, this throws before beginning the construction of an X
subobject.
Do note that the constructor above is described to initialize:
Effects: Initializes the elements in the tuple with the corresponding value in std::forward(u). -- https://eel.is/c++draft/tuple.cnstr#13
One could argue that [tuple.cnstr] doesn't say "if and only if". That just leaves in the air what happens in this case.
Example, good
Throws: Any exception thrown by the initialization of the selected alternative Tj. -- https://eel.is/c++draft/variant.ctor#18
Throws: Any exception thrown by calling the selected constructor of T. -- https://eel.is/c++draft/variant.ctor#23