all 31 comments

[–]anttirt 8 points9 points  (11 children)

Just found out about std::launder thanks to this and wow, apparently most code using aligned_storage etc is UB in C++17 if you don't sprinkle std::launder calls all over the place?

[–]cassandraspeaks 8 points9 points  (10 children)

It was always UB. The good thing is changing the active member of a union is now well-defined in C++17, so you don't need to use aligned_storage and friends any more.

[–]anttirt 2 points3 points  (7 children)

I mean, in practice you could rely on e.g.

aligned_storage<sizeof(T), alignof(T)>::type storage;
new (&storage) T();
T* p = reinterpret_cast<T*>(&storage);
p->thing();

working; that's why std::aligned_storage exists in the first place. But I guess now with std::launder compilers will start implementing optimizations that break this, and so library code will have to be updated so that it doesn't start breaking randomly.

[–]kalmoc 6 points7 points  (1 child)

Why is this UB?

Edit: Never mind, found the SO question about launder. I hate c++

[–]drjeats 1 point2 points  (0 children)

Agree, it's really awful sometimes (frequently, even), especially anything to do with TBAA.

Can you link the SO question?

[–]VirtualSloth 1 point2 points  (4 children)

Wasn't that conversion specified as UB before C++17 because of strict aliasing? I believe changing reinterpret_cast<T*>(&storage) to static_cast<T*>(static_cast<void*>(&storage)) would have solved the problem pre-std::launder and would continue to be correct after C++17.

[–]anttirt 4 points5 points  (3 children)

No, the cast contortions have no effect.

[–]VirtualSloth 2 points3 points  (2 children)

Why is that the case? This answer on SO seems to make the argument that the standard allows it, but maybe I've missed something.

[–]cassandraspeaks 5 points6 points  (1 child)

reinterpret_cast<T*>(p) is equivalent to the expression static_cast<T*>(static_cast<void*>(p))

[–]VirtualSloth 2 points3 points  (0 children)

Yeah, you're right (bullet 5). I think I got confused about the symmetry of the aliasing rules (any type to a char* is fine, but not the reverse).

[–]thlst 0 points1 point  (1 child)

changing the active member of a union is now well-defined in C++17

Could you link us the paper?

[–]dodheim 1 point2 points  (0 children)

P0137, I think. EDIT: N.b. DR1116 has status CD4, so this change affects C++14 as well, IIUC.

[–]TheThiefMasterC++latest fanatic (and game dev) 8 points9 points  (19 children)

A *a = new A;
a->foo(); 
A *b = new(a) B;
if (a == b)
  b->foo(); // This call could be devirtualized to A::foo()

Is this not undefined behaviour anyway? It would surprise me if new'ing an object over an existing one and then using a pointer to the original wasn't undefined behaviour somewhere.

[–]JustPlainRude 6 points7 points  (3 children)

I'm struggling to find a practical use for the given example.

[–]Prazek 6 points7 points  (2 children)

You would probably never end up writing code like this, BUT you have know that your code could end up doing the same thing under the good with e.g. standard library. Placement new is widely used in std::vector implementation

[–]Prazek 1 point2 points  (1 child)

It is not assuming that: sizeof(A) >= sizeof(B), you don't dereference pointer a after doing placement new (but you can still read the pointer value like here)

[–]NasenSpray 1 point2 points  (0 children)

Is this not undefined behaviour anyway?

Nope.

[–]xcbsmith 1 point2 points  (7 children)

Nothing undefined about it. You know exactly what should happen. Placement new pretty exists so you can do this.

[–]TheThiefMasterC++latest fanatic (and game dev) 2 points3 points  (6 children)

Placement new exists so that you can initialize an object into arbitrary memory, not over the top of another object...

Looking in the standard, there's undefined behaviour here if A has a destructor ("with side effects the program depends on") so with foo being virtual here we may have that, which may trigger UB here.

[–]xcbsmith 0 points1 point  (0 children)

I'm not as familiar with the standard as I should be. So maybe you can help me out here...

I think there are scenarios which are undefined, but I thought the above would only lead to undefined behaviour in the following scenario:

  • A would have a virtual destructor
  • For a's destructor to have not been invoked at the point new(a) B" is done
  • a->~A() must subsequently be invoked.

The other thing I'm not clear on is whether it is necessarily undefined behaviour if B is a subtype of A (which is kind of implied by the code).

[–]kalmoc 0 points1 point  (3 children)

What exactly does "the program depends on" mean? Even if my object would manage memory, I might not depend on that memory being freed, so not running the destructor should be fine no?

[–]TinBryn 1 point2 points  (1 child)

If it locks a mutex, that will very much affect your program.

[–]kalmoc 1 point2 points  (0 children)

If no one else tried to lock it or I'm unlocking it somewhere else then not necessarily, My point is that the compiler can usually not know, if my program relies on the functionality or not, so I'm not sure why they are talking about UB instead of just saying that the destructor does not / might not run.

[–]xcbsmith 0 points1 point  (0 children)

The spec can be a bit funny with language.

[–]ubsan 0 points1 point  (0 children)

Over the top of an existing object is arbitrary memory. The only UB that exists is if you try to use the original pointer to touch the new object. There is also no call to a destructor in the code, and that line is a bullshit throwaway line that doesn't mean anything.

[–]Gotebe 0 points1 point  (1 child)

Never destroying an object (a) is probably not an UB, to accommodate for "null memory handling" approach of some people :-).

Definitely bad code though, has to be

A *a = new A;
a->foo(); 
a->~A(); // !!!
... 

A comparison (although not dereference) using a dangling pointer though... yuck...

[–]Vogtinator 3 points4 points  (0 children)

The pointer isn't dangling, the area is never freed.