logoalt Hacker News

gavinrayyesterday at 1:46 PM5 repliesview on HN

I want to point out an implemented feature that people SHOULD be adopting but that I doubt will be picked up:

  P2590R2, Explicit lifetime management (PR106658)
This is for "std::start_lifetime_as<T>". If you have not heard of this before, it's the non-UB way to type-pun a pointer into a structured type.

Nearly all zero-copy code that deals with external I/O buffers looks something like:

  std::unique_ptr<char[]> buffer = stream->read();
  if (buffer[0] == FOO)
    processFoo(reinterpret_cast<Foo*>(buffer.get())); // undefined behavior
  else
    processBar(reinterpret_cast<Bar*>(buffer.get())); // undefined behaviour
With this merged, swap the reinterpret_cast for start_lifetime_as and you're no longer being naughty.

https://en.cppreference.com/cpp/memory/start_lifetime_as


Replies

jandrewrogersyesterday at 3:19 PM

There was already a legal way to achieve this that everyone should already have been using (laundering a pointer through a no-op memmove). Using reinterpret_cast here is a bug.

The "start_lifetime_as" facility does one additional thing beyond providing a tidy standard name for the memory laundering incantation. Semantically it doesn't touch the memory whereas the no-op memmove intrinsically does. In practice, this makes little difference, since the compiler could see that the memmove was a no-op and optimized accordingly.

show 2 replies
gpderettayesterday at 9:53 PM

Well, ignoring alignment restrictions, it depends on the implementation of read. If it is truly opaque, as far as the compiler is concerned, the kernel (or the network card or whatever) is truly constructing a Foo in that buffer, making the cast perfectly legitimate.

start_lifetime_as is useful when the buffer lifetime is transparent to the compiler and it can mess up aliasing assumptions.

amlutoyesterday at 3:45 PM

The cppreference description seems questionable to me:

> Implicitly creates a complete object of type T (whose address is p) and objects nested within it. The value of each created object obj of TriviallyCopyable type U is determined in the same manner as for a call to std::bit_cast<U>(E) except that the storage is not actually accessed, where E is the lvalue of type U denoting obj. Otherwise, the values of such created objects are unspecified.

So T is the complete new object. It contains subobjects, and one of those subobjects has type U. U is initialized as if by bit_cast, and I presume they meant to say that bit_cast casted from the bits already present at the address in question. Since “obj” is mentioned without any definition of any sort, I’ll assume it means something at the correct address.

But what’s E? The page says “E is the lvalue of type U denoting obj,” but obj probably has type char or a similar type, and if it already had type U, there would be no need for bit_cast.

groundzeros2015yesterday at 1:50 PM

You’re allowed to type pun char buffers.

show 2 replies
throw834948398yesterday at 2:22 PM

Your code is not only naughty, it’s also incorrect due to alignment issues.