1

I've read a few Q/A about std::launder (What is the purpose of std::launder?, Does this really break strict-aliasing rules?, std::launder reachability rules), but am still unclear whether I am using it correctly in my specific use-case. The following is what I am trying to achieve:

struct Base {
    ...
};

struct Derived : Base {
    ...
};

// Declare a buffer
alignas(std::max_align_t) char buffer[2000];

// Populate the buffer using placement-new
*reinterpret_cast<size_t*>(&buffer[0]) = sizeof(Derived);
Derived *d = new (&buffer[2 * sizeof(std::max_align_t)]) Derived();
*reinterpret_cast<Base**>(&buffer[sizeof(std::max_align_t)]) = static_cast<Base*>(d);

// Extract values from the buffer
size_t s = *reinterpret_cast<size_t*  >(&buffer[0]);
Base  *b = *reinterpret_cast<Base**   >(&buffer[sizeof(std::max_align_t)]);
       d = *reinterpret_cast<Derived**>(&buffer[sizeof(std::max_align_t) * 2]);

My inclination is that the following is the appropriate way to std::launder the above statements to ensure they all result in defined behavior per the standard:

// Populate the buffer using placement-new
*std::launder(reinterpret_cast<size_t*>(&buffer[0])) = sizeof(Derived);
Derived *d = new (&buffer[2 * sizeof(std::max_align_t)]) Derived();
*std::launder(reinterpret_cast<Base**>(&buffer[sizeof(std::max_align_t)])) = static_cast<Base*>(d);

// Extract values from the buffer
size_t s = *std::launder(reinterpret_cast<size_t*  >(&buffer[0]));
Base  *b = *std::launder(reinterpret_cast<Base**   >(&buffer[sizeof(std::max_align_t)]));
       d = *std::launder(reinterpret_cast<Derived**>(&buffer[sizeof(std::max_align_t) * 2]));

Is the above the appropriate usage of std::launder? Do I have to placement-new the size_t and Base* members prior to assigning to them? Do I have to std::launder the size_t and Base* members at all?

Jeff G
  • 4,470
  • 2
  • 41
  • 76
  • IMO the the highest ranked answer in the first link has a very good example https://stackoverflow.com/a/39382728/6752050 – 273K Feb 01 '23 at 02:14
  • That post shows an instance where they use placement-new to create an integer. Is that placement-new required in order to result in defined behavior? It isn't clear to me from the example. I think the above is legal and results in defined behavior, but couldn't determine that from the examples provided, nor the language in the standard. – Jeff G Feb 01 '23 at 02:17
  • 1
    No, `new` is not required. It's just an example of the code, where a compiler can miss a changed value of an object. See [std::launder](https://en.cppreference.com/w/cpp/utility/launder). *Provenance **fence** with respect to p.* – 273K Feb 01 '23 at 02:21
  • 2
    _"Do I have to placement-new the size_t and Base* members"_ yes, technically everything has to be newed prior C++20. Practically, no. – Passer By Feb 01 '23 at 02:27
  • @JeffG: Is there a reason why you don't do this in a way that isn't littered with a bunch of `reinterpret_cast`s? Like maybe making a template struct that has a size, pointer, and a `Derived` within it instead of manufacturing them piecemeal like this?? – Nicol Bolas Feb 01 '23 at 02:30
  • @NicolBolas I thought about making a structure, but it would have to be a template in order to support any derived type. I am trying to make a statically-allocated template collection, that can store derived types created by calling placement-new with perfect-forwarding. Since I don't know which of the derived types is being used, there is no struct type I could utilize to store the `size_t`, `Base*`, and `Derived`, because it wouldn't be standard-layout. I could make the header (`size_t`, `Base*`) a struct, which would make it more readable; that is a good suggestion. – Jeff G Feb 01 '23 at 02:34
  • 2
    @JeffG: Take note of the second paragraph of the answer in the duplicate: "*But object lifetime is not what `std::launder` is about. `std::launder` can't be used to start the lifetime of objects.*" – Nicol Bolas Feb 01 '23 at 02:43

0 Answers0