The correct way to do type punning in C++ - The second act
Last time, I wrote about type-punning in C++ and how C++20's std::bit_cast can help you. Today, I want to discuss a different reason for type-punning where std::bit_cast might not apply.
std::bit_cast and its limits
You probably remember the example from last month's post where I showed std::bit_cast. For your convenience, here is the code:
1 2 | |
The example above is perfect for a case where you want and can afford a copy of the data (the bits). But what if you're dealing with a larger structure in a constraint environment, where
- copying the data is too costly, and or
- you don't have enough RAM left to make the copy?
Suppose you encounter code like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
I assume you're dealing with some kind of message-processing system. The data most likely comes in from the wire due to constraints in RAM and processing time copying a large structure (128 uint32_ts are considered larger there). You want a zero-copy. This is why, despite knowing about std::bit_cast, you go back to the pleasures of UB-land and the casting dance. Of course, with the same consequences as I described last time.
Well, remember that I talked about different shaped and colored safety buoys? C++23 added another safety buoy for exactly such a situation.
C++23's std::start_lifetime_as
Meet std::start_lifetime_as below, which ships in <memory>.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
As you can see, the change is in A where in the previous example, the code was UB, I replaced the reinterpret_cast with a cast-looking std::start_lifetime_as. One noticeable difference is that while reinterepret_cast took a pointer as an argument for the destination type, std::start_lifetime_as takes just the type, but the function still returns a pointer of the destination type.
As memcpy in C++20 and later, std::start_lifetime_as is blessed by the standard to start the lifetime of an object. The difference to std::bit_cast is that std::start_lifetime_as doesn't copy the data. You can see this function like a placement new. You get a pointer of the destination type back. One major difference is that no constructor gets called. With that, std::start_lifetime_as is what all the folks in embedded and potential other domains were looking for over 30 years, a way to tell the compiler that a bit representation a is now used as an object b.
Andreas
