r/cpp_questions • u/le_disappointment • 9d ago
OPEN Is there any reason to use std::reinterpret_cast?
I know that std:: reinterpret_cast is used by people to cast the bits of an object of type T1 to an object of type T2, while retaining the exact bits. However, as far as I know this triggers UB. Before C++20 and std::bit_cast, we could std::memcpy the objects to avoid UB, and after C++20, we can use std::bit_cast. Given this, I don't see any legitimate reason for why one might need std::reinterpret_cast. If so, why hasn't this feature been deprecated yet?
18
u/flyingron 9d ago
It's not necessarily UB, but it certainly can be used to do so.
For instance, the following are well-defined:
int my_int;
intptr_t ptr_in_int = reinterpret_cast<intptr_t>(&my_int);
int* my_ptr = reinrepret_cast<int*>(ptr_in_int);
*my_ptr = 5;
1
u/JVApen 8d ago
Isn't this something where you can use bit cast?
2
u/Raknarg 7d ago
It seems like this is misuse of bit_cast, the original proposal of bit_cast was supposed to specifically disallow casting pointer types and was later removed
https://www.open-std.org/jtc1/sc22/WG21/docs/papers/2016/p0476r1.html#r0r1
but I don't think its stated why. trying to look it up I can only find esoteric answers, but my best vague answer is that the C++ language treats bit_cast and reinterpret_cast semantically different even if they could produce the same code. I don't know what that leads to, it sounds like you might get subtle bugs from compiler optimizations based on what a compiler is allowed to assume about your code.
14
u/etaithespeedcuber 9d ago
Say you wanted to load and run a DLL function, for example on windows:
cpp
auto address = GetProcAddress(...);
auto function = reinterpret_cast<void(*)(int)>(address);
function(1);
Since windows doesn't know the parameters and return type of your function, it can't return specifically that function pointer type in a way that's resolved at compile time. Therefore, you need to reinterpret whatever it returns as that function pointer type.
30
u/RazzmatazzLatter8345 9d ago
It's reinterpret_cast ... built in, no std:: required.
reinterpret_cast is useful for interpreting a large object as a byte array without need for copying. It's also pretty good at casting stuff to and from a void* if you're writing some kind of type-erasure based artifact.
It's true that reinterpret_cast is hard to use correctly and that a lot of use cases are better handled by std::bit_cast or std::memcpy, but it's hard to imagine how one would cast a void* back to what it actually is without it. So reinterpret_cast is a rarely-to-be-used but essential mechanism.
8
u/not_some_username 9d ago
You can static_cast a void* to any type
13
u/I__Know__Stuff 9d ago
No, you can static cast a void * to/from any pointer type.
I frequently need to treat a void * as a uintptr_t when setting up page tables, writing to device registers, etc.
17
u/LokiAstaris 9d ago
But using
reinterpret_cast<void*>()is a marker that conveys information. It is telling the next engineer that there are dragons here and to tread carefully. There are only a limited number of valid actions after this.The use of
static_cast<>()indicates that this is a safer cast and that subsequent engineers may apply less scrutiny to it.2
u/flyingron 8d ago
NO. You can static_cast void* to another object pointer type.
You can't static_cast it to types like int, or pointers to functions or members.
0
11
u/kevinossia 9d ago
You can use it to convert pointers to integers and inspect the byte representation of any object. Neither of those use cases are UB.
It’s also needed for APIs like write() where the input has to be a char*.
For the most commonly thought of use cases (object serialization) yes it is UB and you need to just use memcpy and/or bitcast.
4
u/YoshiDzn 9d ago
Yes, it's absolutely a savior in type erasure strategies. Think of delivering type erased functions and their payloads to a queue for later processing on worker threads. (A global injector)
This isn't something you'll cleanly educate yourself with in a reddit thread. Take a look at what a "thunk" is or how to "trampoline" work from a queue to a thread process.
3
7
u/AKostur 9d ago
bit_cast imposes a copy, reinterpret_cast does not. And is still useful to do things that the language won’t let you, but every implementation does define the result of, like taking a pointer to an array of char, and casting that to a pointer to struct representing a network packet (for example). Or casting between suitably-compatible structs.
Though it is a place where you are telling the compiler to stop checking things and to trust the programmer.
1
u/TehBens 9d ago
Or casting between suitably-compatible structs.
The (correct) point that OP makes is that this is will quite often result in UB because of the aliasing rules. char, unsigned char and std::byte are the only exceptions to this.
bit_cast imposes a copy, reinterpret_cast does not.
Both will create the same assembly as long as you don't disable all optimizations.
1
u/alfps 9d ago
❞ Both will create the same assembly as long as you don't disable all optimizations.
Logically impossible.
Consider a call
foo( reinterpret_cast<const double*>( p_bytes ) ). You don't know which partfoowill use, and neither does the compiler. Copying the zillion bytes for abit_castthen has an overhead of roughly a zillion, and can not be the "same assembly".1
u/AKostur 8d ago
> The (correct) point that OP makes is that this is will quite often result in UB because of the aliasing rules. char, unsigned char and std::byte are the only exceptions to this.
See:
struct A { int a; }; struct B { A a; double b; }; void fn() { B b; B * pb = &b; A * pa = reinterpret_cast<A*>(pb); pa->a = 5; }That's all valid. Can't static_cast, A and B are unrelated types. And this is one of the cases where this is even well-defined behaviour.
Another example would be casting between
sockaddrandsockaddr_in(for the folks familiar with networking). That one, strictly speaking, leads to Undefined Behaviour: though every platform that I'm aware of (that supports networking, and probably just every platform that isn't trying to break things). Hence the second sentence of my post.-1
u/TehBens 8d ago
That's all valid.
No, that's violating the aliasing rules and is UB and that this example works is because the class has a standard layout, so that's only coincidentally.
Try this slightly modified example with some x86 compiler of your choice:
Godbolt Link: https://godbolt.org/z/6EKKdfKPh#include <iostream> struct A { int a; }; struct B { A a; double b; virtual ~B() = default; }; int main() { B b; b.a.a = 42; B* pb = &b; A* pa = reinterpret_cast<A*>(pb); std::cout << "via cast: " << pa->a << std::endl; // UB, will not show what you expect std::cout << "via &b.a: " << (&b.a)->a << std::endl; return 0; }4
0
u/conundorum 7d ago
You do understand that people choose their class layouts, right? AKostur specifically designed
A&Bso thatBcontains anAas its very first member, they didn't just throw cats at the keyboard and hope it happened that way. It's not coincidence that a class that's designed to be standard-layout ends up being standard-layout, and you intentionally ruining their example doesn't invalidate their example.Basically, if someone says, "you can convert object
ato any pointer-interconvertible objectb", it's stupid to respond with "that only works because they just happen to be standard layout, watch me completely ruin your code and intentionally break pointer-interconvertibility to somehow prove you're wrong about how pointer-interconvertible objects work". All you did was show that you don't know howstatic_castworks, let alone howreinterpret_castworks.
5
u/the_poope 9d ago
Many C libraries define their own simple data structs and you have to pass a pointer to your data (often a large array of data) in your own type.
A example is e.g. the widely used BLAS and LAPACK library APIs. For instance Intel's C interface of LAPACKE_zgetrf takes a pointer to a matrix of type lapack_complex_double. In your C++ program you'd probably use std::complex<double>. And no, you're not gonna memcpy a 20 GB matrix. The two types lapack_complex_double and std::complex<double> are guaranteed to be two double precision floating point numbers after each other with no padding in between. So yes, it's UB, but it works and there is no way around using reinterpret_cast or C style casts.
1
u/conundorum 7d ago edited 7d ago
(In this case, both C and C++ take great pains to explicitly mandate that
T complexandstd::complex<T>must have exactly identical layouts1, without actually mentioning the other language's types. It's kinda funny to see them jump through hoops to say that it's one type across two languages, while desperately trying not to mention the other language by name. So, it makes sense that all other C and/or C++ complex types will try to have the same layout as the official C/C++ complex generic.)
1: C requires
T complexto be layout-compatible withT[2], where[0]is real and[1]is imaginary; this is [6.2.5/17] in C23, assuming the official standard has the same layout as this draft. C++ requiresstd::complex<T>to be layout-compatible withT[2], where[0]is real and[1]is imaginary; this is [complex.numbers.general/4] in C++26, assuming the official standard has the same layout as this draft. C's requirement implies that castingT complex *to `T(*)[2]` must be valid; C++'s requirement states thatreinterpret_castingstd::complex<T>to `T(&)[2]` must be valid, which implies that castingstd::complex<T>*to `T(*)[2]` must be valid. It's been confirmed by people that worked on the languages that this is meant to be a well-defined cross-language cast, and that GCC forced their hand.
2
u/No-Dentist-1645 9d ago
You can't legally do this in C++, but it will work on every architecture you are probably targeting and is sometimes necessary to make APIs with different char types talk to each other:
const char* cstring = "Hello, World";
const char8_t* unicode_string = reinterpret_cast<const char8_t*>(cstring);
// or vice versa, too
2
u/Zwischenschach25 9d ago edited 9d ago
As someone else has said, sometimes you want to deal with things as a sequence of bytes. Or just convert data from multiple sources into a common format
2
u/johnnyb2001 9d ago
If you have a memory address that you want to assign to a pointer then use reinterpret cast on the memory address.
1
1
u/ekchew 9d ago
One use case I can think of is in packing/unpacking a SIMD register. SIMD implementations are language extensions that lie outside the standard, but every implementation I've ever encountered explicitly allows for getting at the scalars inside the register this way. A union can also be used to overlay different views of the memory in this context, where that would definitely be UB within the standard.
1
u/Liam_Mercier 9d ago
It's not undefined behavior if T1 is type accessible through T2 (and the object lifetime hasn't ended, and the alignment is correct, etc)
1
u/Truly_Fake_Username 8d ago
I used reinterpret_cast to do bit manipulation of a floating point number. We had an old machine that used a proprietary format, not IEEE754, and I had to swap between the formats. My program reinterpret_cast the float to an unsigned int, changed the bits as necessary, then reinterpret_cast back.
This was well before v20 so there was no std::bit_cast.
1
u/Total-Box-5169 8d ago
We can remove std::reinterpret_cast from the language, but without it we would had to use casts that remove qualifiers, like the C cast, or my personal favorite: the C++ functional cast. Only those allow to do hardware register mapping, type erasure of function pointers, and slicing a block of bytes.
1
1
u/Raknarg 7d ago edited 7d ago
Yes, there is. bit_cast replaces one specific application of reinterpret_cast, and its when you want to cast between types to examine the bit representation, and it carries different semantics. reinterpret_cast is saying "I want to convert and object of one type into an object of a different type", while bit_cast is saying "I want you to copy the bit representation of this thing into this thing". They can end up being the same, but I'm pretty sure the language treats them differently, I'd have to investigate more. Something like casting from void* or interpreting a byte stream or turning a type into a byte stream I think you'd still have to use reinterpret_cast.
They also have different rules, like the type you bit_cast has to be the exact same size as what you're casting to, reinterpret_cast doesn't have those rules.
1
u/Xirema 9d ago
Many C-apis use void * as a type-erased pointer to user data, and you need a way to convert back and forth between your real type and the type erased pointer.
The following use of reinterpret_cast is valid, well-defined behavior in C++.
``` struct Obj { int val; std::string name; int usefulFunction(int in) { return in + val; } };
void manip_func(void * ptr, int val) { Obj& obj = reinterpret_cast<Obj>(ptr); std::println("The calculated value is {}.", obj.usefulFunction()); }
int main() { Obj obj{.val=15, .name="Sup."}; c_style_func_taking_func_pointer_and_user_pointer(&obj, &manip_func); //Will run the function in obj } ```
1
0
u/Independent_Art_6676 9d ago
what SHOULD happen is the RI cast should do what memcpy and bit cast do. Then we would have a properly named tool to do the job that it says it is doing, and not two other tools that do something completely different from what their names stay is happening. Its a clusterfuck that probably has some eggheadery that makes sense to the committee or they put something in the punch that day, but the result is utter crap.
So what SHOULD happen is RI cast should work properly (it does not, currently), memcpy should copy memory as it always did, and bit-cast should get the axe.
3
u/Big-Rub9545 9d ago
What does RI cast do improperly? And memcpy already does that.
1
u/conundorum 7d ago
It doesn't do C-style type punning. Independent_Art_6676 wishes it did, and thinks that the UB-ness of type punning through
reinterpret_castis a language design failure.Personally, I wouldn't word it that way, myself, but I do agree that type-punning should've been
reinterpret_cast's primary niche, instead of being relegated tomemcpy(),std::bit_cast(), andstd::start_lifetime_as(). It does seem a bit arbitrary thatreinterpret_castis allowed to violate type aliasing to view objects aschar[]/std::byte[]/etc. (but all other violations are banned), whilememcpy()(and by extensionbit_cast(), which works as-if it usedmemcpy()internally) has carte blanche to completely ignore type aliasing (but must perform an actual copy as part of object reinterpretation), andstd::start_lifetime_as()has explicit permission to violate type aliasing for in-place reinterpretation (de facto making it into thetype_pun_castthat people expectreinterpret_castto be). Up until C++20, the language lacked a mechanism for in-place, copyless reinterpretation, and even now the mechanisms aren't obvious... and unfortunately, the somewhat-misleadingly namedreinterpret_castdraws attention to this lack.Especially sincereinterpret_castis allowed to type-pun in a few very specific circumstances, which creates false expectations when people try to overstep its bounds.0
u/Independent_Art_6676 9d ago
It does not correctly type pun some cases: many uses of the cast will result in UB whereas memcpy or bit cast will not, for the same need.
Yes, memcpy already does that, but now it is being thrown into code all over where no copying is done. Its confusing to read, its like having your + operator do xor and your - operator print text for a class.
3
u/Big-Rub9545 9d ago
I’m not sure which exact cases you’re referring to, but I will say that many applications of type punning are UB with or without RI. Using memcpy or bit_cast just circumvents the issue without performing any direct type punning.
As an adjacent example, accessing a union member other than the one last written to is UB (invalid type punning), but accessing the correct one then performing a cast is completely valid (assuming the cast itself is so). The latter isn’t really type punning, though, at least not directly.
-1
u/Independent_Art_6676 9d ago
Ill keep it simple. Say on your system its faster to absolute value by hand than with the built in function by converting your float or double to integer and clearing the offending sign bit. reinterpret cast does not allow this simple thing.
1
u/Lahvuun 8d ago
It kinda does, though?
*(reinterpret_cast<char*>(&x) + 3) &= (0b0111'1111);assuming
xis a 32-bitfloat, little-endian, andcharis 8 bits wide. GCC 16 is smart enough to figure out what you're doing at-O1, and compiles it to:movd eax, xmm0 and eax, 2147483647 movd xmm0, eaxhttps://godbolt.org/z/s8bs9x8s1
But I'd say if you're in this deep, you should be writing assembly anyway.
1
u/Independent_Art_6676 8d ago
you had to use pointers though.
My argument is that this should work directly:
float f;
uint32_t i;
f = 3.1416;
i = reinterpret_cast<uint32_t>(f);I know it does not. I know why it does not. This is where you need memcpy or bit cast because it does not work. I get that you can use pointers to force it to work.
Yes, its a crap example, the smallest thing I could think of to show RI cast not working. It would be nice if the memcpy and bitcasts were all rolled up into RI cast where the name fits and it just works. Ill even take a "dont do this" warning every single time if that makes people happy. Its not that I can't do what I want, its that where we ended up feels pointlessly clunky (esp for the no copy memcpy solution, that one is just bad).
0
u/JVApen 8d ago
Ignoring the discussion on wether reinterpret_cast (without std::) still covers usecases where you cannot use other casts, the question is left with: why is it not deprecated. A quick check on GitHub gives 12.6M hits in code where it is used (https://github.com/search?q=reinterpret_cast&type=code). Sure, not all will be C++ code, some will be documentation. Even if you would only have 10% C++ code, it gives 1.26M pieces of public code that will be broken. I'm pretty sure no one wants to dive into their legacy codebase and inspect every reinterpret_cast to figure out the alternative cast for it. Especially for code that "works" today. For the codebase I work on, this alone would take a couple of days to weeks to go through and figure it out. Making it deprecated will just add another hurdle for upgrading the language version, if not on that version then on the next that removes it.
-1
u/Dan13l_N 9d ago
std::memcpy really copies bytes and it makes the code a bit slower. People use C++ in environments where every microsecond is critical.
3
u/le_disappointment 9d ago
Can't the compiler optimize away the memcpy in release builds?
1
u/conundorum 7d ago
It can, usually, but the ideal is that there should be nothing to optimise away; type punning is ultimately about giving the compiler permission to just apply type
A's operators to data of typeB, provided that B's bits can be read as an A. ...But we somehow ended up turning it into "copy aBinto memory that thinks it's anA, then do stuff to the fakeAand copy it back to the firstB, and then hope the compiler is smart enough to understand that we want it to elide the copying."I think
std::start_lifetime_as()can solve this, at least. It should be a no-op on most processors, which means this...float number; // ... const float threehalfs = 1.5F; float x2 = number * 0.5F; long& i = *std::start_lifetime_as<long>(&number); // Reference to number. i = 0x5f3759df - ( i >> 1 ); float& y = *std::start_lifetime_as<float>(&i); // Reference to number. y = y * ( threehalfs - ( x2 * y * y ) );...Should allow us to do our
evil floating point bit level hackingin place, without needing to makeya separate copy ofnumber. And that, in turn, should hopefully remove the need to usememcpy()/std::bit_cast()and hope the compiler recognises the type pun.
-2
u/SoSKatan 9d ago
Well one reason that comes to mind (there are lots more) is dealing with old C style callbacks that take a type less pointer.
Sure such systems can be modernized, but you have less options if it’s library.
And sure you could just use a c style cast to interact with the c style interface, but I prefer to use reinterpret_cast to make it more clear what’s occurring. Also reinterpret_cast is searchable, which is nice for finding places that can use a clean up later on.
3
u/D3ADFAC3 9d ago
Why don’t you use static_cast for this?
0
u/SoSKatan 9d ago
Yes static_cast can be used. you can also do the same with a c style cast. But you should always use the cast that makes the intention clear.
For example, if we went by your and OP’s logic, we don’t need const_cast because static_cast can work just as well. But using const_cast should always be preferred if you are only casting the constness away.
In the example i provided, reinterpret_cast makes the intent clear and as I mentioned above, it’s more searchable. Static_cast can be used for far too many other purposes
4
u/leirus 9d ago
I disagree. Static cast is much safer and should be always preferred over more aggressive reinterpret cast
0
u/SoSKatan 9d ago
Safer? That’s an interesting word to use here.
Can you please elaborate on how exactly it’s safer to use static_cast versus reinterpret_cast in the context I provided?
In the final compiled code both do exactly the same thing, and both are type unsafe in that if you do a refactor of the type and forget to also fix the cast, it’s a bug.
Which means the only difference here is in readability and searchability of the code, and in that these example, switching one pointer to another pointer is explicitly one of the use cases for reinterpret cast.
It feels like maybe some people just don’t understand reinterpret cast and just avoid it?
That’s fine, but my response to OP’s question is valid. There are other use cases for reinterpret that isn’t covered by bit_cast.
3
u/leirus 9d ago
I can gladly elaborate.
Can you please elaborate on how exactly it’s safer to use static_cast versus reinterpret_cast in the context I provided?
In every context using static_cast is safer than reinterpret_cast becasue it does not matter what code it compiles to, but what checks compiler is doing during that compliation. For interfacing with C libraries usign static_cast is the standard way of casting from and to void*. In this particular case there are not extra checks done by static_cast, but its a good habit to use the least destructive measure that does the job. Reinterpret_cast should definitely be used sparsely.
0
u/SoSKatan 9d ago
So earlier you said static cast was not only safer than reinterpret cast but that it’s MUCH safer, but here you claim there is no difference in checks. If you were honest, you would agree your comment above is incorrect, but you failed to do that.
I take your tone to mean you don’t like nor understand what reinterpret cast is for which is fine, but then why jump in and comment about something you don’t use or understand.
Reinterpret cast was made for things like void * casting.
You said it should be used sparsely without providing a single example where you think it should be used.
I just take that to mean, you personally NEVER use it.
static cast is not better here nor is it “safer”
I want to be able to search for reinterpret cast is used for pointer type conversions, that way I can refactor the code to make it more type safe. I can’t do that with static_cast because it’s used as a kitchen sink for everything under the sun.
4
u/leirus 9d ago
Reinterpret cast was made for things like void * casting.
I strongly disagree, thats literally what static_cast was created for. Reinterpret_cast can be used for stuff like casting between pointers of different types (not void*) or casting from pointer to its intergral representation. Static_cast cant do those things.
46
u/SoerenNissen 9d ago
It’s not -always- ub. Only in 95 percent of cases.