10

I'm trying to figure out whether the following is undefined behaviour. I have a feeling it's not UB, but my reading of the standard makes it look like it is UB:

#include <iostream>

struct A {
    A() { std::cout << "1"; }
    ~A() { std::cout << "2"; }
};

int main() {
    A a;
    new (&a) A;
}

Quoting the C++11 standard:

basic.life¶4 says "A program may end the lifetime of any object by reusing the storage which the object occupies"

So after new (&a) A, the original A object has ended its lifetime.

class.dtor¶11.3 says that "Destructors are invoked implicitly for constructed objects with automatic storage duration ([basic.stc.auto]) when the block in which an object is created exits ([stmt.dcl])"

So the destructor for the original A object is invoked implicitly when main exits.

class.dtor¶15 says "the behavior is undefined if the destructor is invoked for an object whose lifetime has ended ([basic.life])."

So this is undefined behaviour, since the original A no longer exists (even if the new a now exists in the same storage).

The question is whether the destructor for the original A is called, or whether the destructor for the object currently named a is called.

I am aware of basic.life¶7, which says that the name a refers to the new object after the placement new. But class.dtor¶11.3 explicitly says that it's the destructor of the object which exits scope which is called, not the destructor of the object referred to by a name that exits scope.

Am I misreading the standard, or is this actually undefined behaviour?

Edit: Several people have told me not to do this. To clarify, I'm definitely not planning on doing this in production code! This is for a CppQuiz question, which is about corner cases rather than best practices.

curiousguy
  • 8,038
  • 2
  • 40
  • 58
knatten
  • 5,191
  • 3
  • 22
  • 31
  • 1
    Found another answer that agrees: https://stackoverflow.com/a/35395517/3980929 (maybe less of a dupe, but still interesting) – Rakete1111 Sep 03 '18 at 17:23
  • You should not use placement new on an existing object as the previously constructed object would not be destroyed. If by running the above program, the output is '112' or `1122`, then it is undefined behavior. You just need some imagination like what would happen if there was a pointer inside A (possibly indirectly) and you would be able to guess that it is undefined behavior. – Phil1970 Sep 03 '18 at 17:37
  • 1
    @Phil1970 [basic.life]p5 [would like to have a word with you](http://eel.is/c++draft/basic.life#5.sentence-2). Also, it says there that if the output is 1122, then the compiler has a bug because the standard prohibits this explicitly! – Rakete1111 Sep 03 '18 at 17:48
  • @Rakete1111 I don't see anything in that question pertaining to this one, except that it is a very broad question asking just about everything _else_ about placement new - on that basis alone I reckon it's a poor dupe target tbh – Lightness Races in Orbit Sep 03 '18 at 17:58
  • @Rakete1111 is right about p5 although I find the wording "depends on the side effects" to be somewhat wooly and difficult to quantify scientifically (so personally I would avoid this and ensure calling the dtor myself first, except for objects of built-in type) – Lightness Races in Orbit Sep 03 '18 at 18:00
  • @Light Ah right, agreed that it's a poor dupe target. Oh yeah, that wording is very weird. I think even removing that part of the sentence wouldn't change anything. – Rakete1111 Sep 03 '18 at 18:03
  • The "maybe less of a dupe"-answer goes straight to the heart of my question imo. That is: Does the destructor get called for 1: The first `A`, 2: The second `A`, or 3: The object behind the variable `a`? The answer you link to argues that 3 is the case, which I think makes sense. However, I don't think that's very clear from [class.dtor]. – knatten Sep 03 '18 at 18:41
  • @knatten: The conditions under which `a` refers to the new object are sufficient to ensure "the destructor for the original `A`" and "the destructor for the object currently named `a`" are the same. – Ben Voigt Sep 03 '18 at 18:50
  • @BenVoigt: The conditions are sufficient to explain what happens if 3 is true, and that the *effect* in this case is the same as if 2 was true. They don't say which one is *actually* true, which is what I'm trying to understand. – knatten Sep 03 '18 at 18:59
  • @knatten: If the conditions are met, *both* are true. The destructor that is called, is the destructor of the original object. And it is the destructor of the new object. (Both destructors are the same) Whichever way you choose to look at it, the object it destroys is the new object; the original no longer exists and attempting to refer to it finds the new object. If the conditions are not met, the behavior is undefined. – Ben Voigt Sep 03 '18 at 19:04
  • @BenVoigt The old and the new objects are two different objects, how can they have the same destructor? If `A` had a data member that differed between the two, which we printed in the destructor, it would surely matter which object we called the destructor for? – knatten Sep 03 '18 at 19:57
  • @knatten: The destructor is a function (specifically, a special member function). Member functions are shared between all instances of a type. The target object is a parameter to the member function which is accessed through the `this` keyword. If you access a data member inside the destructor, you do so via `this` (implicitly or explicitly, there is still an object access to `*this`), and as you know, `this` is a pointer which refers to the object currently existent in that location. – Ben Voigt Sep 03 '18 at 20:41
  • Even if by "the destructor" you meant a *bound* member function, that's still a pair (function handle, target object handle -- may be pointer or reference) and the object handle locates the object currently in that memory location even if it isn't the original object. In C++, pointers and references bind to memory locations, not objects. – Ben Voigt Sep 03 '18 at 20:43
  • Yeah, I think we all agree what happens in practice. The question is how the standard explains this in [class.dtor], which I think we've established needs clarification (as per Rakete1111's answer). – knatten Sep 03 '18 at 20:53
  • 2
    The Standard requires that it happens that way, but I agree it could be explained better. The reason that the rule in [Peter's comment](https://stackoverflow.com/questions/52153673/why-isnt-it-undefined-behaviour-to-destroy-an-object-that-was-overwritten-by-pl?noredirect=1#comment91258324_52153686) has to exist is because otherwise it would be confusing whether or not the "existing pointers / references / variable names for the overwritten object now refer to the new object" included the destructor call. – Ben Voigt Sep 03 '18 at 20:58

4 Answers4

6

You're misreading it.

"Destructors are invoked implicitly for constructed objects" … meaning those that exist and their existence has gone as far as complete construction. Although arguably not entirely spelled out, the original A does not meet this criterion as it is no longer "constructed": it does not exist at all! Only the new/replacement object is automatically destructed, then, at the end of main, as you'd expect.

Otherwise, this form of placement new would be pretty dangerous and of debatable value in the language. However, it's worth pointing out that re-using an actual A in this manner is a bit strange and unusual, if for no other reason than it leads to just this sort of question. Typically you'd placement-new into some bland buffer (like a char[N] or some aligned storage) and then later invoke the destructor yourself too.

Something resembling your example may actually be found at basic.life¶8 — it's UB, but only because someone constructed a T on top of an B; the wording suggests pretty clearly that this is the only problem with the code.

But here's the clincher:

The properties ascribed to objects throughout this International Standard apply for a given object only during its lifetime. [..] [basic.life¶3]

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • 2
    Good find. The location you mention actually contains wording to condone the OP's specific case: "the program **must ensure that an object of the original type occupies that same storage location** when the implicit destructor call takes place; otherwise the behavior of the program is undefined." Emphasis by me. Note the "original type", not "original object" ;-). – Peter - Reinstate Monica Sep 03 '18 at 17:25
  • @PeterA.Schneider That doesn't really tell us anything about what may now happen to the original object, but it doesn't need to :) – Lightness Races in Orbit Sep 03 '18 at 18:00
  • So "constructed" means "constructed and its lifetime has not ended"? Also, what exactly is it in your opinion that causes the destructor to be called? 1: The fact that the name `a` goes out of scope, and it now refers to the new object, or 2: The fact that the new object itself goes out of scope? – knatten Sep 03 '18 at 18:07
  • I agree that this is unusual btw., this came up as a user contributed question on cppquiz.org. And that site shouldn't be taken as a source of best practices! ;) – knatten Sep 03 '18 at 18:08
  • @PeterA.Schneider's link is interesting. It seems to imply that what's actually going on is that the *original* dead object (with automatic storage duration) goes out of scope, but that it's then ok to call its destructor since another live object of the same type has taken its place. (This as opposed to something like "the new object also has automatic storage duration, and it's this new object's destructor which is called.) – knatten Sep 03 '18 at 18:13
  • 1
    @knatten names have scope, objects (and variables in the standard are objects) have lifetimes ;-). The object's liefetime ends (because its memory is being reused). Nothing has gone out of scope yet though ... the name `a` (still in scope) refers to a new object whose lifetime began when it was placement-new-created. The standard often uses the word "variable" which in my book is a combination of name and writable object; in these re-use cases that connection is severed, and we must distinguish the two. Is there a definition of "variable" anywhere? – Peter - Reinstate Monica Sep 03 '18 at 18:24
  • Variable: ["Named object in a scope"](http://www.stroustrup.com/glossary.html#Gvariable) – Peter - Reinstate Monica Sep 03 '18 at 18:31
  • Yeah, I was a bit imprecise. The thing is that class.dtor#11.3 says that the destructor is invoked "for constructed objects with automatic storage duration when the block in which an object is created exits". So it explicitly refers to the object itself, not the variable named `a`. And this is the essence of my original question. – knatten Sep 03 '18 at 18:32
  • @knatten That's right - the object, then, is the one you put where the old one used to belong (to which the name `a` now refers, per the other rules). – Lightness Races in Orbit Sep 04 '18 at 10:16
  • @knatten _"Also, what exactly is it in your opinion that causes the destructor to be called? 1: The fact that the name a goes out of scope, and it now refers to the new object, or 2: The fact that the new object itself goes out of scope?"_ Both, either. I don't think any distinction between the two is meaningful. – Lightness Races in Orbit Sep 04 '18 at 10:17
  • I think it's an interesting distinction. The second object is not destroyed because the block in which it was created exits, but because the block in which the *first* object was created exits, and the second object now has re-used that memory. If we extract `new (&a) A;` into a function, this distinction suddenly has practical consequences. – knatten Sep 04 '18 at 11:08
  • @knatten The scope of the name hasn't changed, and the object's lifetime is bound to the scope of its name just as it always has been. It's just that it took over that name from another, older, now-dead object. I can't see any practical consequences of a distinction between your 1 and 2, given that. The only complication arises if you do _another_ object replacement! – Lightness Races in Orbit Sep 04 '18 at 11:14
  • I think we're just talking slightly past each other. I agree that what you're describing is what actually happens and is supposed to happen. I was imprecise when I tried to distinguish between `a` going out of scope and the new object going out of scope, which indeed overlaps. What I was trying to distinguish between was the block in which `a` is in scope and the block in which the second object was created. In which block the second object is created does not affect when it is destroyed, it is only affected by when `a` goes out of scope, aka. the block in which the first object was created. – knatten Sep 04 '18 at 11:28
  • @knatten I also think that we basically agree. There are more academic ways to conceptualise the situation than I am choosing to employ. But I can confirm that the scope of the name `a` is still the key factor. _Objects don't have scope; names do._ – Lightness Races in Orbit Sep 04 '18 at 11:35
  • This is what I meant by my distinction: https://godbolt.org/z/Y19_Ev. But we both agree that the scope of `a` is the key factor here. I think it would help if the standard said something closer to that, rather than saying the first object itself should be destroyed. The explanation from the core member in Rakete's answer is good I think. – knatten Sep 04 '18 at 11:42
  • @knatten The standard says both. It says both that a name going out of scope results in the destruction of the object to which it [currently] refers, and it says that re-using a name ends the lifetime of the originally referred-to object. I'm not seeing any room for interpretation here to be honest. – Lightness Races in Orbit Sep 04 '18 at 11:43
  • Oh, then I've missed something which could explain my misunderstanding! Where does it say that a name going out of scope results in the destruction of the object to which it [currently] refers? I was only aware of class.dtor, and that discusses the object itself, not the name. – knatten Sep 04 '18 at 11:46
  • @knatten Have only looked very quickly but probably [stmt.dcl]2. Admittedly that wording is not as precise as I was expecting. I think this probably comes under the "could do with being clarified" notion that the Core group member aluded to – Lightness Races in Orbit Sep 04 '18 at 11:50
  • In fact looking at this further, the entire lifetime spec is a total mess and needs rewriting. – Lightness Races in Orbit Sep 04 '18 at 11:53
  • Hum, [stmt.cld]¶2 does indeed help! It is worded differently from (and imo closer to reality than) [class.dtor]. – knatten Sep 04 '18 at 12:07
  • @LightnessRacesinOrbit: What the Standard should have done ages ago is drop the notion *that regions of storage* which are fully owned by user code have types [non-PODS classes often include regions that are owned by the implementation, and thus may have types]. What compilers need to know for purposes of optimization is whether various *accesses* to storage might interact, and that in turn should depend upon the ways in which addresses are derived and used (for objects that are exposed to the outside world, or whose address is taken, use of an lvalue involves deriving its address and then... – supercat Sep 11 '18 at 18:47
  • Types are good don't get rid of types and compilers do more than optimization and optimization is not all that matters – Lightness Races in Orbit Sep 11 '18 at 18:48
  • ...using that address). Types are good, but they're a property of pointers and references, not a property of the underlying storage. – supercat Sep 11 '18 at 18:50
  • @supercat No, types are a property of values. Storage holds values of a certain type. That's what it's for. This ensures correctness and is really important. – Lightness Races in Orbit Sep 11 '18 at 18:54
  • If there only exists one pointer anywhere in the universe that identifies a piece of storage, and that pointer is converted to and stored in a `uint32_t*` and the original is erased, is there any reason why reason why the storage should encapsulate any state beyond the values of the bits therein? – supercat Sep 11 '18 at 18:55
  • @supercat: Yes. Correctness. We are not dealing with bits. C++ is above that. – Lightness Races in Orbit Sep 11 '18 at 18:59
  • @LightnessRacesinOrbit: For non-PODS types whose storage is partially owned by the implementation, that's obvious. What situations are there where writing a region of storage using a PODS type would result in one defined behavior, and writing that same region of storage with a different PODS type that has the same bit pattern would result in a different defined behavior? – supercat Sep 11 '18 at 19:13
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/179846/discussion-between-supercat-and-lightness-races-in-orbit). – supercat Sep 11 '18 at 19:36
  • @supercat You've moved the goalposts by constraining the argument to POD types (though I suspect you mean trivial types) – Lightness Races in Orbit Sep 12 '18 at 08:34
  • @LightnessRacesinOrbit: A C++ implementation is allowed to claim ownership over some of the storage in certain types, but not others. Implementations are allowed to do anything they like, at any time, with storage they own, and are allowed to behave in arbitrary fashion if such storage is disturbed at any time. They are also allowed (even expected) to use the types of storage in to determine the meaning of information in the storage they own, and behave in arbitrary fashion if given incorrect information about such types. I'm sorry if I implied disagreement with any of the above. – supercat Sep 12 '18 at 15:13
  • @LightnessRacesinOrbit: On the other hand, there are some types with no defined semantics beyond those of a bunch of objects of other types fastened together with duct tape, and whose storage format is specified as a concatenation of the objects in question, separated by padding sufficient for alignment. Are there any situations where correct code generation would require that the compiler know the effective or dynamic type associated with such storage, and where "knowing that it doesn't know" wouldn't be sufficient? – supercat Sep 12 '18 at 15:33
  • @supercat I don't know, and it doesn't really matter, because "being able to generate code" is not the reason types exist. We want it to also all make sense. I haven't studied in sufficient detail your comments to be able to say whether your alternative makes sense, sorry. – Lightness Races in Orbit Sep 12 '18 at 15:50
5

Am I misreading the standard, or is this actually undefined behaviour?

None of those. The standard is not unclear but it could be clearer. The intent though is that the new object's destructor is called, as implied in [basic.life]p9.

[class.dtor]p12 isn't very accurate. I asked Core about it and Mike Miller (a very senior member) said:

I wouldn't say that it's a contradiction [[class.dtor]p12 vs [basic.life]p9], but clarification is certainly needed. The destructor description was written slightly naively, without taking into consideration that the original object occupying a bit of automatic storage might have been replaced by a different object occupying that same bit of automatic storage, but the intent was that if a constructor was invoked on that bit of automatic storage to create an object therein - i.e., if control flowed through that declaration - then the destructor will be invoked for the object presumed to occupy that bit of automatic storage when the block is exited - even it it's not the "same" object that was created by the constructor invocation.

I'll update this answer with the CWG issue as soon as it is published. So, your code does not have UB.

Rakete1111
  • 47,013
  • 16
  • 123
  • 162
  • Thank you! <3 This is *exactly* what I found confusing. After all the various replies and comments to this question, and our previous e-mail exchange, I was beginning to think someone should propose a clarification to [class.dtor]p12. So I'm very happy to hear this! :) Now I'll be able to sleep tonight after all. – knatten Sep 03 '18 at 20:21
  • 1
    @knatten To be fair, after some thought I found it completely unclear too (bordering a contradiction), so thanks for asking here :) – Rakete1111 Sep 03 '18 at 20:23
  • I still think [basic.life]p3 makes this evident. – Lightness Races in Orbit Sep 04 '18 at 10:18
  • @Light That paragraph doesn't say/imply that the destructor of the new object is called. – Rakete1111 Sep 04 '18 at 10:21
  • @Rakete1111 No, but it says that none of the rules for what happens to "objects" apply once said object's lifetime has ended, by which we can understand that there is no destructor call to the old, dead object (and, honestly, why on earth would there be?). The rest tells us that there is still a normal destructor call to what is _now_ under `a`. – Lightness Races in Orbit Sep 04 '18 at 10:21
  • @Light Exactly, I agree. But that would mean that no destructor is called at all. – Rakete1111 Sep 04 '18 at 10:23
  • @Rakete1111 Why's that? We have a new object now. The same old rules now apply to that. – Lightness Races in Orbit Sep 04 '18 at 11:13
  • BTW could you cite the actual named source please - the list is behind an auth wall so many people can't see who "a very senior member" actually is. – Lightness Races in Orbit Sep 04 '18 at 11:52
  • @Light I wasn't sure if I should publish his name, but ok. What old rules? [class.dtor]p12 doesn't mention objects with dynamic storage duration, which the new object has – Rakete1111 Sep 04 '18 at 12:59
  • @Rakete1111 Does it? Good point! Hmm, we may have a problem then – Lightness Races in Orbit Sep 04 '18 at 13:10
  • @Light `new` always gives you an object of dynamic storage duration :) (AFAIK) – Rakete1111 Sep 04 '18 at 13:14
  • @Rakete1111 Was this ever discussed further? As far as I can tell, the latest C++ standard (C++20) still has the contradiction in it mentioned above (i.e. what happens when you overwrite an automatic storage duration object with a dynamic storage object and the original destroyed object exits its scope). – NotAProgrammer Oct 09 '20 at 19:34
2

Too long for a comment.

Lightness' answer is correct and his link is the proper reference.

But let's examine terminology more precisely. There is

  • "Storage duration", concerning memory.
  • "Lifetime", concerning objects.
  • "Scope", concerning names.

For automatic variables all three coincide, which is why we often do not clearly distinguish: A "variable goes out of scope". That is: The name goes out of scope; if it is an object with automatic storage duration, the destructor is called, ending the lifetime of the named object; and finally the memory is released.

In your example only name scope and storage duration coincide — at any point during its existence the name a refers to valid memory — , while object lifetime is split between two distinct objects at the same memory location and with the same name a.

And no, I think you cannot understand "constructed" in 11.3 as "fully constructed and not destroyed" because the dtor will be called again (wrongly) if the object's lifetime was ended prematurely by a preceding explicit destructor call. In fact, that's one of the concerns with the concept of memory re-use: If construction of the new object fails with an exception the scope will be left and a destructor call will be attempted on an incomplete object, or on the old object which was deleted already.

I suppose you can imagine the automatically allocated, typed memory marked with a tag "to be destroyed" which is evaluated when the stack is unwound. The C++ runtime does not really track individual objects or their state beyond this simple concept. Since variable names are basically constant addresses it is convenient to think of "the name going out of scope" triggering the destructor call on the named object of the supposed type supposedly present at that location. If one of these suppositions is wrong all bets are off.

Peter - Reinstate Monica
  • 15,048
  • 4
  • 37
  • 62
  • Thank you for that clarification! I agree that this is probably the intention of the standard. Although, as can be seen in @Rakete1111's answer, I think the standard is unclear about this. I also agree with you about the "constructed" comment, I was just trying to understand what Lightness was saying, as I thought that's what he meant. – knatten Sep 03 '18 at 20:30
0

Imagine using placement new to create a struct B to the storage where the A a objects lives. In the end of the scope, the destructor of the struct A will be called (because the variable a of type A goes out of scope), even if an object of type B is in reallty living there right now.

As already cited:

"If a program ends the lifetime of an object of type T with static ([basic.stc.static]), thread ([basic.stc.thread]), or automatic ([basic.stc.auto]) storage duration and if T has a non-trivial destructor,39 the program must ensure that an object of the original type occupies that same storage location when the implicit destructor call takes place;"

So after putting B into the a storage, you need to destroy B and put an A there again, to not violate the rule above. This somehow not apply here directly, because you are putting an A to an A, but it shows the behavior. It shows, that this thinking is wrong:

So the destructor for the original A object is invoked implicitly when main exits.

There is no "original" object any longer. There is just an object currently alive in the storage of a. And thats it. And on the data currently sitting in a, a function is called, namely the destructor of A. Thats what the program compiles to. If it would magically kept track of all "original" objects you would somehow have a dynamic runtime behavior.

Additionally:

A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, if there is no explicit call to the destructor or if a delete-expression ([expr.delete]) is not used to release the storage, the destructor shall not be implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior.

Since the destructor of A is not trivial and has side effects, (i think) its undefined behavior. For build in types, this does not apply (hence you can use a char buffer as object buffer without reconstructing the chars back into the buffer after using it) since they have a trivial (no-op) destructor.

phön
  • 1,215
  • 8
  • 20
  • I know there is no original object any longer, this is in fact is why I posted this question in the first place. My reading of class.dtor¶11.3 is that the implementation should destroy the original object. That object no longer exists, so that reading is clearly wrong. It seems cwg agrees that the wording is imprecise and needs to be updated (see @Rakete1111's answer). – knatten Sep 04 '18 at 07:56
  • As for your 'Additionally' paragraph, I think what they're saying is that the implementation is not required to implicitly call the destructor *when you reuse the storage*. Other parts of the standard however says (or at least are trying to say) that the destructor will be called when the block in which `a` was created exits, and that this destructor will now destroy the new `A`. So I don't think there is any UB here. – knatten Sep 04 '18 at 07:59
  • @knatten Its guaranteed that one destructor call for a struct `A` is made at scope exit because a variable of type `A` goes out of scope. It is our responsibilty to ensure that a proper object of type `A` is living there in the moment this happens (otherwise its undefined behavior). I think we both agree on this. Now to the part with the destruction of the "original" `A` object. When you run the program, you even see just 1 destructor call but 2 constructor calls. You may get away with it because `A` is a super simple struct. – phön Sep 04 '18 at 09:12
  • @knatten ... Imagine having a struct with a thread member for example. You can not just copy a new object over it (and thats what placemenet new basically does). You have to call the destructor manually before reusing the storage. – phön Sep 04 '18 at 09:14
  • @curiousguy i dont understand what you mean – phön Sep 04 '18 at 10:02
  • @phön If a class doesn't provide assignment, or any way to change its value, what makes you think you have the right to try to change the value of an instance with destruction-construction? – curiousguy Sep 04 '18 at 10:05
  • @curiousguy Ah. It was just an example to "show" that you will not get away with it that easiliy when the class members are more complex than a trivial type. But to answer your question: Well, nothing forbids it? We can properly destruct the class (where the std::thread for example lives in), but the storage is still there. So we reuse it. Be aware of const and reference members tho – phön Sep 04 '18 at 10:14
  • Where is it guaranteed that one destructor call for a struct A is made at scope exit because a variable of type A goes out of scope? – knatten Sep 04 '18 at 11:49
  • @knatten It's the basics of how objects work in C++. Otherwise local variables wouldn't work. – Lightness Races in Orbit Sep 04 '18 at 11:52
  • I don't think the standard guarantees that the destructor is called because the variable goes out of scope. I think it guarantees that the destructor is called because the scope in which the object was created exits. – knatten Sep 04 '18 at 12:04
  • @phön We're discussing the wording in the standard, and whether it sufficiently describes what happens in practice. Showing what happens in practice doesn't really add anything to that discussion. – knatten Sep 04 '18 at 12:21
  • @knatten You said: "I think it guarantees that the destructor is called because the scope in which the object was created exits". And i provided an example that shows that this is clearly not the case using the implementation shown (all compilers is tested show this behavior). The behavior intended by the standard is surely the one you see here in practice, even if we can not pinpoint it directly in the wording – phön Sep 04 '18 at 12:27
  • Your example does not show that. Placement new does not give automatic storage duration. The only object with automatic storage duration, for which the rule about automatic destruction in class.dtor applies, is the one created on line 12. So the output from your example conforms to both your claim and my claim about what the standard says. – knatten Sep 04 '18 at 12:31
  • @knatten What about this: http://coliru.stacked-crooked.com/a/06f76dac548b7930 . I see the struggle to pinpoint the wording for "the variable is responsible for the automatic destruction". and i have to admit i never thought about this. its just the way it is. – phön Sep 04 '18 at 12:49
  • Yeah, that's exactly the reason for posting this question, that the standard is not sufficiently clear about this. We all agree what the *intention* of the standard is, and what actually happens in practice. And now it seems someone senior from Core also agrees it needs clarification, so then it might get fixed in the future! :) – knatten Sep 04 '18 at 19:03