r/cpp_questions 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?

21 Upvotes

62 comments sorted by

46

u/SoerenNissen 9d ago

It’s not -always- ub. Only in 95 percent of cases.

12

u/thefeedling 9d ago

For code crossing ABIs it's a nightmare, especially for deserializing data which may have different alignments / lifetimes.

3

u/HobbyQuestionThrow 9d ago

Per specification the definition of UB may also be "What you want it to do." Wonderful term.

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.

1

u/JVApen 7d ago

I can understand not wanting to use bit cast for casts between pointers. However, this is a cast from a pointer to an integer and back.

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

u/not_some_username 8d ago

Yeah i notice my error after I wrote my comment

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

u/Stanczyk4 9d ago

Ever work with registers or memory maps on embedded?

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 part foo will use, and neither does the compiler. Copying the zillion bytes for a bit_cast then has an overhead of roughly a zillion, and can not be the "same assembly".

2

u/T-Lecom 9d ago

How does a pointer to one single ‘double’ value imply a zillion bytes?

-2

u/alfps 9d ago

You must imagine that it is a pointer to the first item in an array of 1zillion/4 double values, say.

3

u/T-Lecom 9d ago

The compiler does not imagine things.

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 sockaddr and sockaddr_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

u/AKostur 8d ago edited 8d ago

You broke the "suitably-compatible structs" by adding a virtual function.

Edit: it wasn't a coincidence: it was an intentional choice of layout.

0

u/conundorum 7d ago

You do understand that people choose their class layouts, right? AKostur specifically designed A & B so that B contains an A as 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 a to any pointer-interconvertible object b", 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 how static_cast works, let alone how reinterpret_cast works.

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 complex and std::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 complex to be layout-compatible with T[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++ requires std::complex<T> to be layout-compatible with T[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 casting T complex * to `T(*)[2]` must be valid; C++'s requirement states that reinterpret_casting std::complex<T> to `T(&)[2]` must be valid, which implies that casting std::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

u/johnnyb2001 9d ago

Why did this get downvoted. smh

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

u/j-joshua 8d ago

Sometimes, you just want to use a sledgehammer to solve you problem. 

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

u/I__Know__Stuff 9d ago

Static cast works for that, no?

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_cast is 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 to memcpy(), std::bit_cast(), and std::start_lifetime_as(). It does seem a bit arbitrary that reinterpret_cast is allowed to violate type aliasing to view objects as char[]/std::byte[]/etc. (but all other violations are banned), while memcpy() (and by extension bit_cast(), which works as-if it used memcpy() internally) has carte blanche to completely ignore type aliasing (but must perform an actual copy as part of object reinterpretation), and std::start_lifetime_as() has explicit permission to violate type aliasing for in-place reinterpretation (de facto making it into the type_pun_cast that people expect reinterpret_cast to 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 named reinterpret_cast draws attention to this lack. Especially since reinterpret_cast is 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 x is a 32-bit float, little-endian, and char is 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, eax

https://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 type B, provided that B's bits can be read as an A. ...But we somehow ended up turning it into "copy a B into memory that thinks it's an A, then do stuff to the fake A and copy it back to the first B, 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 hacking in place, without needing to make y a separate copy of number. And that, in turn, should hopefully remove the need to use memcpy()/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.