<div dir="ltr"><div dir="ltr"><div dir="ltr">> <span style="font-family:sans-serif">There was a discussion about throwing destructors in bugzilla a </span><span style="font-family:sans-serif">few days ago, and we were talking about how to properly destroy initialized </span><span style="font-family:sans-serif">objects if a destructor throws during the teardown of the full-expression.</span></div><div dir="ltr"><span style="font-family:sans-serif"><br></span></div><div dir="ltr">Is it too late to say that the destructor of objects marked with `trivial_abi` must be noexcept?<span style="font-family:sans-serif"><br></span></div></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Thu, Jun 11, 2020 at 12:10 PM John McCall <<a href="mailto:rjmccall@apple.com">rjmccall@apple.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><u></u>




<div>
<div style="font-family:sans-serif"><div style="white-space:normal">
<p dir="auto">On 11 Jun 2020, at 12:57, Richard Smith wrote:</p>

</div>
<div style="white-space:normal"><blockquote style="border-left-width:2px;border-left-style:solid;border-left-color:rgb(119,119,119);color:rgb(119,119,119);margin:0px 0px 5px;padding-left:5px"><p dir="auto">On Wed, 10 Jun 2020 at 19:52, John McCall via cfe-dev <<br>
<a href="mailto:cfe-dev@lists.llvm.org" target="_blank">cfe-dev@lists.llvm.org</a>> wrote:<br>
</p>
<blockquote style="border-left-width:2px;border-left-style:solid;color:rgb(153,153,153);margin:0px 0px 5px;padding-left:5px;border-left-color:rgb(153,153,153)"><p dir="auto">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>
<a href="mailto:cfe-dev@lists.llvm.org" target="_blank">cfe-dev@lists.llvm.org</a>> 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>
<a href="mailto:cfe-dev@lists.llvm.org" target="_blank">cfe-dev@lists.llvm.org</a>> 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>
<<a href="mailto:cfe-dev@lists.llvm.org" target="_blank">cfe-dev@lists.llvm.org</a>><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:rgb(153,153,153)" target="_blank">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:rgb(153,153,153)" target="_blank">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>
</p>
</blockquote><p dir="auto">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.</p>
</blockquote></div>
<div style="white-space:normal">

<p dir="auto">Right.</p>

</div>
<div style="white-space:normal"><blockquote style="border-left-width:2px;border-left-style:solid;border-left-color:rgb(119,119,119);color:rgb(119,119,119);margin:0px 0px 5px;padding-left:5px"><p dir="auto">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.</p>
</blockquote></div>
<div style="white-space:normal">

<p dir="auto">Right.</p>

<p dir="auto">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.</p>

<p dir="auto">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 <em>much</em> thornier than I’d given it 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 <code style="background-color:rgb(247,247,247);border-top-left-radius:3px;border-top-right-radius:3px;border-bottom-right-radius:3px;border-bottom-left-radius:3px;margin:0px;padding:0px 0.4em" bgcolor="#F7F7F7">std::initializer_list</code> and other recursive/branching<br>
extensions.  So e.g. if you have:</p>

<pre style="background-color:rgb(247,247,247);border-top-left-radius:5px;border-top-right-radius:5px;border-bottom-right-radius:5px;border-bottom-left-radius:5px;margin-left:15px;margin-right:15px;max-width:90vw;overflow-x:auto;padding:5px" bgcolor="#F7F7F7"><code style="background-color:rgb(247,247,247);border-top-left-radius:3px;border-top-right-radius:3px;border-bottom-right-radius:3px;border-bottom-left-radius:3px;margin:0px;padding:0px" bgcolor="#F7F7F7">struct Temp {
  Temp(const char *);
  ~Temp() noexcept(false);
};

struct Element {
  Element(Temp);
  ~Element();
};

std::initializer_list<Element> &&list = {
  { Temp(“a”) },
  { Temp(“b”) },
  { Temp(“c”) }
};
</code></pre>

<p dir="auto">Then the normal destruction order is:</p>

<p dir="auto">Temp_c, Temp_b, Temp_a, /<em>end of scope</em>/, Element_c, Element_b, Element_a</p>

<p dir="auto">But if Temp_c’s destructor throws, then I guess the exceptional destructor order is:</p>

<p dir="auto">Element_c, Element_b, Temp_b, Element_a, Temp_a</p>

<p dir="auto">Unless there’s an argument that the whole <code style="background-color:rgb(247,247,247);border-top-left-radius:3px;border-top-right-radius:3px;border-bottom-right-radius:3px;border-bottom-left-radius:3px;margin:0px;padding:0px 0.4em" bgcolor="#F7F7F7">std::initializer_list</code> 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 <code style="background-color:rgb(247,247,247);border-top-left-radius:3px;border-top-right-radius:3px;border-bottom-right-radius:3px;border-bottom-left-radius:3px;margin:0px;padding:0px 0.4em" bgcolor="#F7F7F7">std::initializer_list</code><br>
that’s just in the middle of an expression, right?</p>

</div>
<div style="white-space:normal"><blockquote style="border-left-width:2px;border-left-style:solid;border-left-color:rgb(119,119,119);color:rgb(119,119,119);margin:0px 0px 5px;padding-left:5px"><blockquote style="border-left-width:2px;border-left-style:solid;color:rgb(153,153,153);margin:0px 0px 5px;padding-left:5px;border-left-color:rgb(153,153,153)"><p dir="auto">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>
</p>
</blockquote><p dir="auto">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.</p>
</blockquote></div>
<div style="white-space:normal">

<p dir="auto">Right, good point.  As long as the ABI divergence is specific to functions<br>
with callee cleanup, it’s acceptable.</p>

<p dir="auto">John.</p>
</div>
</div>
</div>

</blockquote></div>