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_t
s 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++17 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 for over 30 years, a way to tell the compiler that a bit representation a
is now used as an object b
.
Andreas