New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[lib] Extend exception propagation to cover the initialization LWG3640 #4863
Comments
I agree with the problem statement. Regarding the solutions offered, phrasing this in terms of initialization seems preferable, because it also covers the case where there is no constructor (e.g. for built-in types). Is there a full list of offending wording somewhere? |
I, too, prefer initialization, if at least for uniformity. But I believe the change is big enough, so inserting "calling" could be a first step. Unless the initialization of fundamental types are allowed to throw exceptions, this would not be wrong. But it would require saying "if any" to account for when there's no actual constructor involved (which can also apply to class types). There's no full list. I found this problem while searching for "exception" in the library. But for the case of exception propagation, the actual suspects (so far) are uses of "constructor" and "construction". More generally, it's the reference to particular steps of initialization, when the intention is to refer to the initialization itself. Feel free to assign this to me. |
This comment has been minimized.
This comment has been minimized.
Me included. On further analysis of sibling vocabulary types, all of them use type traits instead of named requirements for these operations. So I will proceed on the assumption there's no such defect. |
Even "initialization" doesn't encompass enough. An exception thrown by the destruction of a temporary created as part of the initialization of a constructor's parameter is not part of the initialization (https://eel.is/c++draft/expr.call#7.sentence-9, https://eel.is/c++draft/full#class.temporary-4.sentence-5). #include <tuple>
struct Z { ~Z() noexcept(false) { throw 0; } };
struct X { X(const Z&) noexcept { } };
struct Y { operator Z() { return {}; } };
int main() try { std::tuple<X>(Y{}); } catch(...) { throw 0ull; }
// Terminate on thrown int means throw within noexcept Exceptions thrown by destructors of returned prvalues seem to have the same problem: https://godbolt.org/z/xeaaEToMq.
#include <tuple>
struct Y { ~Y() noexcept(false) { throw 0; } };
struct X { Y operator=(const X&) noexcept { return {}; } };
int main()
try {
std::tuple<X> x{};
x = x;
throw '0';
}
catch(int) {
throw 0ull; // Terminate on thrown int means throw within noexcept
} I propose the following amendment to the proposed solution:
The use of "propagated" was taken from https://eel.is/c++draft/futures.async#3. This use of side effect is novel. |
@jwakely , there seems to be material for at least two library issues here. The first one is for optional.assign p7 and vicinity: "This does not require T to meet the Cpp17CopyAssignable requirements. So the assignment as specified can return something other than T&. Particularly a prvalue whose destruction throws. Only T's destructor is required to not throw." A type traits check (e.g. The second one is bad phrasing around "thrown by the construction", because it ignores exceptions thrown by the destruction of temporaries and by the destruction of prvalue results. LWG should form an opinion what the appropriate phrasing should be (possibly in conjunction with a front-matter explanation), and apply that uniformly (or have it editorially applied uniformly). And no, "side effects" is a defined term in [intro.execution]; we can't use that here. "full-expression" might help, though. |
|
I propose amending the proposed solution to the following:
|
That could even be defined as exception-propagating. +3.21 [defns.exception-propagating]
+exception-propagating
+an effect whose exceptions thrown due to the evaluation of its implementing expression are propagated
-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.
+For constructors and member functions of pair, each element-wise
+operation specified to be called for that operation is exception-propagating.
-For each \tcode{tuple} constructor, an exception is thrown only if the construction of
-one of the types in \tcode{Types} throws an exception.
+For each \tcode{tuple} constructor, the construction of each type in \tcode{Types} is exception-propagating.
\item
\throws
-any exceptions thrown by the function, and the conditions that would cause the exception.
+any exceptions thrown by the function and the conditions that would cause the exception, and
+effects specified as exception-propagating.
-Throws: Any exception thrown by the initialization of the selected alternative Tj.
+Throws: The initialization of the selected alternative Tj is exception-propagating.
-Throws: Any exception thrown by the selected constructor of T.
+Throws: Calling the selected constructor of T is exception-propagating. |
All uses of type traits are suspects. For these siblings vocabulary types, |
Amended that. It seems that libstdc++ is more conforming here as it behaves as if it had a non-throwing exception specification. Whereas libc++ lets the exception propagate (opened https://bugs.llvm.org/show_bug.cgi?id=51758). |
And not just in the context of Throws:. More generally, when exceptions are involved, it's referring to anything else other than any exception that could be thrown due to an evaluation of a subexpression up to the complete evaluation of its enclosing full-expression. For example, this is an exception safety guarantee for: constexpr optional<T>& operator=(const optional& rhs);
An implementation that destroys the parameters at the end of the enclosing full-expression could detect this. And it can be done portably for a returned prvalue whose destruction throws. On another point: Can one really say "no effect" when one has to destroy
|
Another example for the need of more generalized wording regarding thrown exceptions:
This should be, with minimal changes, "and that construction does not cause an exception to be thrown". |
There is some overlap with #3207. |
"does not exit via an exception"? (An exception could be thrown inside and caught again, without anyone noticing.) |
This is probably too large to be handled editorially, and needs at least LWG guidance on the approach. Please submit an LWG issue and let us know the issue number. |
Definitely. |
This is now https://wg21.link/LWG3640. |
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:
T
.constructioninitialization of {T
,some object}".The former is already present in the standard, as exemplified below. As for the latter (emphasis mine),
Examples
Example, good
Talking in terms of operations includes initialization.
Example, bad
We can make a
tuple
constructor throw an exception other than by constructing one of the specified types: https://godbolt.org/z/enGThMq4P.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:
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
The text was updated successfully, but these errors were encountered: