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