<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/xhtml; charset=utf-8">
</head>
<body>
<div style="font-family:sans-serif"><div style="white-space:normal">
<p dir="auto">On 11 Jun 2020, at 16:00, Zoe Carver wrote:</p>
</div>
<div style="white-space:normal"><blockquote style="border-left:2px solid #777; color:#777; margin:0 0 5px; padding-left:5px"><p dir="auto">> There was a discussion about throwing destructors in bugzilla a few days<br>
ago, and we were talking about how to properly destroy initialized objects<br>
if a destructor throws during the teardown of the full-expression.<br>
<br>
Is it too late to say that the destructor of objects marked with<br>
`trivial_abi` must be noexcept?</p>
</blockquote></div>
<div style="white-space:normal">
<p dir="auto">It’s not too late, but it seems arbitrary, and it doesn’t make any<br>
difference; the only way to actually define these concerns away would<br>
be to make <em>all</em> destructors noexcept.</p>
<p dir="auto">John.</p>
</div>
<div style="white-space:normal"><blockquote style="border-left:2px solid #777; color:#777; margin:0 0 5px; padding-left:5px"><p dir="auto">On Thu, Jun 11, 2020 at 12:10 PM John McCall <rjmccall@apple.com> wrote:<br>
</p>
<blockquote style="border-left:2px solid #777; color:#999; margin:0 0 5px; padding-left:5px; border-left-color:#999"><p dir="auto">On 11 Jun 2020, at 12:57, Richard Smith wrote:<br>
<br>
On Wed, 10 Jun 2020 at 19:52, John McCall via cfe-dev <<br>
cfe-dev@lists.llvm.org> wrote:<br>
<br>
On 10 Jun 2020, at 18:30, Richard Smith wrote:<br>
<br>
On Wed, 10 Jun 2020 at 13:06, John McCall via cfe-dev <<br>
cfe-dev@lists.llvm.org> wrote:<br>
<br>
On 10 Jun 2020, at 15:55, Richard Smith wrote:<br>
<br>
On Wed, 10 Jun 2020 at 12:18, John McCall via cfe-dev <<br>
cfe-dev@lists.llvm.org> wrote:<br>
<br>
On 10 Jun 2020, at 14:32, Richard Smith wrote:<br>
<br>
On Mon, 8 Jun 2020 at 19:52, John McCall via cfe-dev<br>
<cfe-dev@lists.llvm.org><br>
wrote:<br>
<br>
It definitely changes observable semantics, but it’s not<br>
*obviously*<br>
non-conforming; [expr.call]p7 gives us a lot of flexibility here:<br>
<br>
It is implementation-defined whether the lifetime of a parameter<br>
ends when the function in which it is defined returns or at the<br>
end of the enclosing full-expression.<br>
<br>
This is the non-conformance I'm referring to:<br>
<a href="https://godbolt.org/z/cgf5_2" style="color:#999">https://godbolt.org/z/cgf5_2</a><br>
<br>
Even given [expr.call]p7, we are still required to destroy<br>
automatic-storage-duration objects in reverse construction order by<br>
[stmt.jump]p2:<br>
<br>
"On exit from a scope (however accomplished), objects with automatic<br>
storage duration (6.7.5.3) that have been constructed in that scope<br>
are<br>
destroyed in the reverse order of their construction."<br>
<br>
Don’t temporaries not have automatic storage duration formally?<br>
<br>
The intent is that they don't; we have a longstanding open issue to<br>
introduce a notion of "full-expression storage duration" to describe<br>
temporary objects. But in the absence of such a language change, it's<br>
unclear which utterances about "automatic storage duration" apply to<br>
temporaries.<br>
<br>
But in any case, I think that's immaterial here, because function<br>
parameters are local variables, not temporary objects, and do have<br>
automatic storage duration even in the hypothetical world where there's a<br>
different storage duration for temporaries.<br>
<br>
That’s why [class.temporary]p7 has to spell out the interordering<br>
<br>
of destruction of lifetime-extended temporaries.<br>
<br>
[expr.call]p7 is the most specific statement about the destruction<br>
of parameters. Under normal principles of interpretation, it should<br>
take priority.<br>
<br>
Well, p7 says nothing about the relative ordering of parameter<br>
destructions, only the points where such destruction may occur.<br>
<br>
Not relative with each other, but I think it has to be understood<br>
as allowing differences relative to temporaries created by the calling<br>
full-expression.<br>
<br>
Oh, definitely. Prior to C++17, that's a problem (as described in<br>
<a href="http://itanium-cxx-abi.github.io/cxx-abi/argument-destruction.pdf" style="color:#999">http://itanium-cxx-abi.github.io/cxx-abi/argument-destruction.pdf</a>). In<br>
C++17 and later, though, there is never a case where we elide a copy of a<br>
temporary into a parameter, so we never have a function parameter that<br>
might need to outlive its call due to the "longer of the two lifetimes"<br>
rule. The new model is that a prvalue of class type simply initializes its<br>
target in-place, without even notionally creating and copying from a<br>
temporary.<br>
<br>
That’s great, but we can still have temporaries created during the<br>
evaluation of other arguments, and callee-destroy does still mean that<br>
they won’t be destroyed in reverse order of construction, and that has to<br>
be okay.<br>
<br>
For example:<br>
<br>
struct A {<br>
A(const std::string &);<br>
~A();<br>
};<br>
<br>
void foo(A x, A y);<br>
<br>
void test() {<br>
foo(A(“a”), A(“b”));<br>
}<br>
<br>
Assuming left-to-order argument evaluation order, the caller constructs:<br>
1. the std::string for “a”<br>
2. the parameter x<br>
3. the std::string for “b”<br>
4. the parameter y<br>
<br>
If parameters are destroyed at the end of the call, we’ll destroy them<br>
in the order 4,2,3,1 instead of 4,3,2,1. That has to be okay.<br>
<br>
Yes, that must be OK. I think in general there is no requirement that<br>
full-expression-storage-duration objects and automatic-storage-duration<br>
objects are destroyed in reverse construction order relative to each other.<br>
<br>
Right.<br>
<br>
Another example of that:<br>
<br>
A make_a(B &&);<br>
A &a = make_a(B()); // full-expression-storage-duration B is destroyed long<br>
before automatic-storage-duration A.<br>
<br>
Right.<br>
<br>
The basic problem here is that storage duration stops being a<br>
particularly good proxy for lifetime / destruction order when you<br>
start talking about locals. The real categories seem to be: dynamic,<br>
global, thread, local-scope, full-expression, and (optionally) parameters.<br>
<br>
Hmm. There was a discussion about throwing destructors in bugzilla a<br>
few days ago, and we were talking about how to properly destroy initialized<br>
objects if a destructor throws during the teardown of the full-expression.<br>
It just occurred to me that this is *much* thornier than I’d given it<br>
credit<br>
for because you can have arbitrarily many initialized objects and they can<br>
be arbitrarily intermingled with the temporaries, because of lifetime<br>
extension of std::initializer_list and other recursive/branching<br>
extensions. So e.g. if you have:<br>
<br>
struct Temp {<br>
Temp(const char *);<br>
~Temp() noexcept(false);<br>
};<br>
<br>
struct Element {<br>
Element(Temp);<br>
~Element();<br>
};<br>
<br>
std::initializer_list<Element> &&list = {<br>
{ Temp(“a”) },<br>
{ Temp(“b”) },<br>
{ Temp(“c”) }<br>
};<br>
<br>
Then the normal destruction order is:<br>
<br>
Temp_c, Temp_b, Temp_a, /*end of scope*/, Element_c, Element_b, Element_a<br>
<br>
But if Temp_c’s destructor throws, then I guess the exceptional destructor<br>
order is:<br>
<br>
Element_c, Element_b, Temp_b, Element_a, Temp_a<br>
<br>
Unless there’s an argument that the whole std::initializer_list becomes<br>
a single object at some point? This doesn’t affect normal aggregate<br>
list-initialization of an object because each initialization is a separate<br>
full-expression, but I don’t think that’s true of a std::initializer_list<br>
that’s just in the middle of an expression, right?<br>
<br>
This (especially the second bullet) would be an ABI change for existing<br>
users of [[trivial_abi]]. (Alternative simpler rule: if any parameter is<br>
callee-cleanup then all parameter destruction happens in the callee. I'm<br>
not sure if that's better or worse, but it seems simpler.)<br>
<br>
It does, yeah. But I guess we’d still need two entrypoints to get the<br>
ordering right? Seems really unfortunate.<br>
<br>
Yes, but I think we would only need two entry points for non-member<br>
`operator$=` functions that use callee cleanup. I think that's rare enough<br>
that the cost wouldn't be prohibitive.<br>
<br>
Right, good point. As long as the ABI divergence is specific to functions<br>
with callee cleanup, it’s acceptable.<br>
<br>
John.<br>
</p>
</blockquote></blockquote></div>
<div style="white-space:normal">
</div>
</div>
</body>
</html>