Placement new and assignment of class with const member
Asked Answered
D

1

13

Why is that undefined behaviour?

struct s
{
    const int id; // <-- const member

    s(int id):
        id(id)
    {}

    s& operator =(const s& m) {
        return *new(this) s(m); // <-- undefined behavior?
    }
};

(Quote from the standard would be nice).

This question arose from this answer.

Davinadavine answered 24/11, 2017 at 12:45 Comment(16)
const int id; says that the value of id will never change. And then you change it?Evanston
It is strange to want to mutate something that you deliberately made immutable.Ola
@BoPersson: Another view is that I create a new object on the same location.Davinadavine
I've been reading the c++17 standard for 30min now and I still cannot find a set of rules forbidding the double construction of an object.Smaltite
I distinctively remember this being legal. @BoPersson const only applies to the lifetime of the object.Rogers
@PasserBy is it not legal only if you somehow call the destructor of the object between the two calls to its constructor? I really fail to find it mentioned in the standard. It must be hidden somewhere.Smaltite
@Smaltite Destructors not being called isn't undefined behaviour. Calling a destructor on an invalid object is.Rogers
@Smaltite If the destructor is trivial (like in this case), then it's legal to not call it. timsong-cpp.github.io/cppwp/basic.life#5.sentence-1Trenatrenail
@Trenatrenail that's an answer!Smaltite
@Smaltite I don't think so. It says UB won't arise from destructors not being called, which is very partial at best.Rogers
@PasserBy what? I've created a chat room, there seems to be a misunderstanding somewhereSmaltite
@Smaltite I don't know if I'm actually right though if this applies even if an object within an object is const, because I don't know if timsong-cpp.github.io/cppwp/basic.life#10 seems to apply.Trenatrenail
@Trenatrenail [basic.life]/10 is about const objects, not object with const members, is it not?Smaltite
@Smaltite That's basically it, I don't know :( It sure seems like the former thoughTrenatrenail
Reading [basic.life]/9, it says explicitly it's ok?Smaltite
@Smaltite A const member is a const object, is it not?Anora
R
20

There is nothing that makes the shown code snippet inherently UB. However, it is almost certain UB will follow immediately under any normal usage.

From [basic.life]/8 (emphasis mine)

If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:

  • the storage for the new object exactly overlays the storage location which the original object occupied, and

  • the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and

  • the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type, and

  • the original object was a most derived object of type T and the new object is a most derived object of type T (that is, they are not base class subobjects).

Since there is a const member in s, using the original variable after a call to operator= will be UB.

s var{42};
var = s{420};         // OK
do_something(var.id); // UB! Reuses s through original name
do_something(std::launder(&var)->id);  // OK, this is what launder is used for
Rogers answered 24/11, 2017 at 15:7 Comment(5)
So, do I understand correctly that there is no legal way to access the members prior C++17?Davinadavine
@Davinadavine Technically, there is. But I highly recommend against it. auto& ref = (var = s{420});. And then use refRogers
OK, I get it. Thanks.Davinadavine
@Davinadavine Technically, under a strict reading on the std, on common implementations, using a pointer object was always guaranteed to work after that const changing trick. Still is, as long as a pointer contains only an address (number). However, this is pedantic and certainly NOT intended, and the people whose work is to interpret the std would not support it. No compiler will go out of its way to support that pedantic reading.Anora
This answer may not be correct anymore, since the quoted sentence was relaxed in C++20: github.com/cplusplus/draft/commit/…Violation

© 2022 - 2024 — McMap. All rights reserved.