c++stdlaunder

Constructing class or structures in an improper way using std::launder


Suppose you have a struct with elements which you don't want to (can't) initialize in the initialization phase, but is not default initializable. I figured out a way of using std::launder: Allocate a space for the structure, and then use the launder to assign a value at the right place. For example,

godbolt

#include <cassert>
#include <cstddef>
#include <iostream>
#include <new>

struct T {
    int t;
};

struct S {
    int x;
    T t;
    S() = delete;
    S(int x) : x(x) {}
};

int main() {
    alignas(S) std::byte storage[sizeof(S)];
    S s0(42);
    
    // 0. I believe that the commented line is dangerous, although it compiles well.
    // *std::launder(reinterpret_cast<S*>(storage)) = s0;
    
    S *ps = std::launder(reinterpret_cast<S*>(storage));
    ps->x = 42;
    ps->t = T{};

    { // 1. Is this safe?
        S &s1 = *ps;
        std::cout << s1.x << std::endl;
        s1.t.t = 24;
        assert(s1.x == 42 && s1.t.t == 24);
    }

    { // 2. Is this safe?
        S s2 = *ps;
        std::cout << s2.x << std::endl;
        assert(s2.x == 42 && s2.t.t == 24);
    }

    { // 3. Is this safe?
        S s3 = std::move(*ps);
        std::cout << s3.x << std::endl;
        assert(s3.x == 42 && s3.t.t == 24);
    }
}

The problem with this method is that it doesn't really 'construct' a structure, and therefore will not call a destructor.

  1. Is assigning a structure to a laundered object dangerous?
  2. Is referencing a laundered object safe?
  3. Is assigning another structure from a laundered object safe?
  4. Is moving a laundered object safe?

Thanks.


Solution

  • For std::launder to have defined behavior there has to actually be an object of the given type at the location pointed to. That is not the case in this instance, so the behavior of your program is undefined.

    Consider if S::t was a std::string instead of a simple int. It would be completely uninitialized and so assigning to it would likely cause it to copy data into what it thinks is its storage buffer but is actually a random, un-owned location in memory.

    Simply put, std::launder is the wrong tool for this job. You want placement-new:

    int main() {
        alignas(S) std::byte storage[sizeof(S)];
    
        S *ps = new(storage) S(42);
        ps->t = T{};
    }