reinterpret_cast vs std::bit_cast: C++ Undefined Behavior
In C++, converting the raw bit representation of an object from one
type to another—known as type punning—has historically been prone to
bugs and undefined behavior. While developers traditionally turned to
reinterpret_cast for this purpose, doing so often violates
C++ strict aliasing rules and alignment requirements, resulting in
undefined behavior. With C++20, std::bit_cast was
introduced to provide a standard-compliant, safe, and
constexpr-capable mechanism to reinterpret the binary
representation of a value without invoking undefined behavior.
The Problem with reinterpret_cast
reinterpret_cast does not construct a new object or copy
bits; instead, it reinterprets the existing pointer or reference type at
compile time. When used for type punning (e.g., casting a pointer of one
type to a pointer of an unrelated type and dereferencing it), it
frequently triggers undefined behavior in two primary ways:
- Strict Aliasing Violations: The C++ standard
mandates that two pointers of unrelated types cannot point to the same
memory location, with narrow exceptions (such as
char*,unsigned char*, orstd::byte*). If you cast afloat*to anint*and dereference it, you violate strict aliasing. Compilers optimize code based on the assumption that unrelated pointers do not alias each other; violating this assumption leads to unexpected optimizations, misordered memory reads, or corrupted program state. - Alignment Faults: Different types have different
alignment requirements. If you use
reinterpret_castto convert a pointer to a type that has stricter alignment constraints than the source address guarantees, dereferencing the resulting pointer causes undefined behavior, which can trigger hardware-level exceptions on architectures that strictly enforce alignment.
While reinterpret_cast is valid for converting integer
types to pointer types, or converting between unrelated pointer types
provided the pointer is cast back to the original type before
dereferencing, it is not a safe tool for reading the bits of one type as
another.
How std::bit_cast Solves Undefined Behavior
Defined in the <bit> header in C++20,
std::bit_cast is explicitly designed for bit-level type
reinterpretation. Instead of reinterpreting memory addresses through
pointer manipulation, std::bit_cast creates a new object of
the destination type and copies the bit representation of the source
object into it.
#include <bit>
#include <cstdint>
float f = 1.0f;
// Safe, well-defined bit reinterpretation
auto i = std::bit_cast<std::uint32_t>(f); std::bit_cast eliminates undefined behavior through
strict compile-time checks:
- Size Matching: The source and destination types
must be of the exact same size
(
sizeof(To) == sizeof(From)). Mismatched sizes result in a compilation error. - Trivially Copyable: Both the source and destination
types must be trivially copyable
(
std::is_trivially_copyable_v<T>). This prevents reinterpreting types that manage dynamic resources, have custom copy constructors, or contain virtual methods. - Value-Based Semantics: Because
std::bit_castacts like a standard-compliantstd::memcpyon the value level rather than aliasing memory via references or pointers, it does not violate the strict aliasing rule. - Compile-Time Execution: Unlike
std::memcpy,std::bit_castisconstexpr-qualified, allowing safe bit-level manipulation during compilation without generating runtime overhead or risking undefined behavior.
Summary
The distinction regarding undefined behavior is clear: dereferencing
a pointer converted via reinterpret_cast to perform type
punning breaks strict aliasing and alignment rules, leading to undefined
behavior. In contrast, std::bit_cast provides well-defined
value-copy semantics enforced by compile-time constraints, making it the
correct choice for reinterpreting bit representations in modern C++.