c++pointerslanguage-lawyerstrict-aliasingstdlaunder

std::launder when there are two objects in the same memory location


I was doing some reading on std::launder, and I thought of a scenario that the standard didn't seem to address.

cppreference.com defines std::launder(T *p) like this:

Formally, given

  • the pointer p represents the address A of a byte in memory
  • an object x is located at the address A
  • x is within its lifetime
  • the type of x is the same as T, ignoring cv-qualifiers at every level
  • every byte that would be reachable through the result is reachable through p . . .

Then std::launder(p) returns a value of type T* that points to the object x. Otherwise, the behavior is undefined.

My reading of this is that "if p points at a memory location where there lives a T, then std::launder(p) returns a pointer to that T".

The examples I saw talked about std::launder's utility in situations where one object has perished and another has begun its lifetime in a memory location, and you just need to tell the compiler about it. But if I'm reading it correctly, std::launder can be used for more than that.

struct S {
    int i;

    S(int i, int j) : i(i), j(j) {}

private:
    int j;
};

int main() {
    S s(10, 20);
    int *pi = reinterpret_cast<int*>(&s);
    int *pi_good = std::launder(pi);
}

In this example, pi is a int* that points at a memory location where there lives an int, but pointers don't just point to memory locations; they point to objects. And since reinterpret_cast doesn't change the value of the pointer (because S is not pointer-interconvertible with int), pi still points at the S object. So dereferencing pi is undefined behavior. This is where std::launder comes in, and it gives us a pointer to the int that actually lives there.

Now this extends the scope of std::launder beyond the original case I mentioned earlier, where one object has died and another has taken its place because in this situation, we have two separate objects living in the same memory location at the same time, and we can use std::launder to switch from one to the other and back*.

(This would also mean that std::launder(reinterpret_cast<T*>(std::rand())) can sometimes be well defined.)

Now, a natural next question would be: If two objects of the same type live in the same memory location at the same time, and you launder a pointer to that memory location, which one does the new pointer point to?

#include <new>

struct Holder {
    // unsigned char required to provide storage
    unsigned char storage[4];

    // make it a non-trivial type
    ~Holder() {};

    template <typename T>
    void hold() {
        ::new(static_cast<void *>(storage)) T;
    }
};

int main() {
    Holder h;
    h.hold<Holder>();
    Holder *ph = &h;
    [[maybe_unused]] Holder *ph_laundered = std::launder(ph);
}

In this example, both the Holder and the held object live in the same memory location at the same time and have the same type.

*The last bullet in the definition requires that an S* cannot access any bytes that the int* could not, which we can accomplish while maintaining S's non-pointer-interconvertibility with int by changing S to class S { int i; }; where i is private.


Solution

  • There are never supposed to be two different objects of similar type at the same address with overlapping lifetimes (Per [intro.object]p10). So std::launder is not defined to tell you 'which one' it points to.

    This is a standard defect, CWG2744.