Ch2.13: Casting
Implicit casting and type promotion
The compiler automatically converts objects to a common type when evaluating expressions. This is called implicit casting or type promotion.
#include <cstddef>
#include <cstdint>
::std::uint8_t a{10u};
::std::uint8_t b{20u};
auto sum{a + b}; // both promoted to int before addition
// sum has type int, not ::std::uint8_t
Small integer types (char, unsigned char, short, unsigned short) are promoted to int (or unsigned int) for arithmetic. When mixing types, the compiler promotes to a common type to perform the computation safely.
#include <cstddef>
double pi{3.14};
::std::size_t n{2zu};
auto result{pi * n}; // n promoted to double, result is double
Explicit casting overview
When you need a specific conversion, use explicit casts. Modern C++ provides distinct operators for different intents:
static_cast, const_cast, reinterpret_cast, and ::std::bit_cast (C++20).
⚠️ But remember: if you can avoid casting altogether, that is usually the best choice. Design your code so that types match naturally.
static_cast
Use static_cast for well-defined, compile-time-checked conversions (e.g., numeric types).
#include <cstddef>
double pi{3.14159};
::std::size_t n{static_cast<::std::size_t>(pi)}; // converts double to size_t
const_cast
Use const_cast to add or remove const qualifiers.
#include <cstddef>
::std::size_t const x{42zu};
auto& y{const_cast<::std::size_t&>(x)}; // removes const
⚠️ Writing through y is undefined behavior if x was truly const.
reinterpret_cast
reinterpret_cast reinterprets the bit pattern of an object.
⚠️ It is usually wrong and should be avoided.
The only common exception is casting to char* or unsigned char* to inspect raw bytes.
Do not use signed char* for type punning.
Even with char* or unsigned char*, you must consider the strict aliasing rule.
See:
What is the Strict Aliasing Rule and Why do we care?
.
We will discuss this further in the future pointer section.
#include <cstdint>
::std::uint32_t u{0xDEADBEEFu};
auto p{reinterpret_cast<unsigned char const*>(&u)}; // raw byte view
If you want to treat one type as another (for example, a float64_t as a uint64_t),
you should not use reinterpret_cast.
Instead, use ::std::bit_cast.
::std::bit_cast
::std::bit_cast safely reinterprets bits when source and destination types have the same size and are trivially copyable.
#include <bit>
#include <cstdint>
double d{3.14};
::std::uint64_t bits{::std::bit_cast<::std::uint64_t>(d)}; // safely copies bit pattern
dynamic_cast
dynamic_cast is used with polymorphism to safely cast between base and derived classes.
In practice it is slow, complex, and often banned in performance-critical code.
Herb Sutter has proposed a ::std::down_cast for the future,
which would be much cheaper when it doesn’t cross virtual inheritance links or DLL boundaries.
For now: avoid dynamic_cast. Prefer designs that do not require it.
You are free to skip this section — we list it here only to show that there is such a thing as dynamic_cast.
We will discuss it properly in the future when we cover pointers and polymorphism.
Here is a very simple example just to show the syntax:
struct Base { virtual ~Base() = default; };
struct Derived : Base { };
Derived d;
Base* b = ::std::addressof(d);
Derived* pd = dynamic_cast<Derived*>(b); // safe downcast
if(pd) {
print("dynamic_cast succeeded\n");
}
This works because b actually points to a Derived.
If b pointed to some other subclass, dynamic_cast would return nullptr.
C-style casts
A C-style cast looks like (type)expression.
It is dangerous because it tries too much:
the compiler will attempt a const_cast, then a static_cast, and finally a reinterpret_cast if needed.
This makes it unclear which conversion is actually happening.
#include <cstddef>
double pi{3.14159};
::std::size_t n{(::std::size_t)pi}; // C-style cast
⚠️ Avoid C-style casts. They hide intent and can silently perform unsafe conversions. Always prefer the modern cast operators.
Key takeaways
- Implicit casting promotes small integers to int and finds common types in mixed expressions.
- Use
static_castfor clear, compile-time-checked conversions. - Use
const_castonly to change qualifiers when the underlying object is truly non-const. reinterpret_castis usually wrong; only considerchar*/unsigned char*for byte inspection, and mind strict aliasing.- Use
::std::bit_castfor safe, explicit bit reinterpretation. dynamic_castexists, but is slow and often banned. Syntax shown above for awareness only.- Avoid C-style casts; they try too many conversions (const_cast → static_cast → reinterpret_cast) and hide intent.
- If you can avoid casting altogether, that is best — design your code so types match naturally.