A from-scratch guide to every kind of type conversion in C++: implicit conversions, the four named casts (
static_cast,dynamic_cast,const_cast,reinterpret_cast), C-style and functional casts,std::bit_cast, smart-pointer casts, and the subtle pointer-adjustment rules that trip up even experienced engineers. Written for someone comfortable with intermediate C++ who wants to never be confused by a cast again.
You know C++ (classes, inheritance, pointers, references, templates at a basic
level) but you're fuzzy on which cast to use when, why dynamic_cast needs a
vtable, why reinterpret_cast<Derived*>(base) is a silent bug under multiple
inheritance, or what std::bit_cast buys you over a memcpy. By the end you will:
- Understand the conversion model of C++: what happens implicitly, and why casts are the explicit override of those rules.
- Know exactly what each of the four named casts can and cannot do — with the compiler's-eye view of what machine-level work (if any) each performs.
- Read and reason about pointer adjustment under single, multiple, and
virtual inheritance (the
this-pointer offset problem). - Use
dynamic_castcorrectly (and know when RTTI is the wrong tool). - Use
std::bit_cast/std::start_lifetime_asfor well-defined type punning instead of undefined-behavior tricks. - Cast smart pointers (
static_pointer_cast,dynamic_pointer_cast, …) safely. - Recognize the handful of casts that are undefined behavior and avoid them.
Read 00–05 in order the first time — the named casts build on the conversion
model. After that, dip into whatever you need.
| # | File | Topic |
|---|---|---|
| 00 | 00-why-casting.md | The conversion model; why C casts are dangerous; the 4 named casts overview |
| 01 | 01-implicit-conversions.md | Standard conversions, integral/float promotions, user-defined conversions, explicit |
| 02 | 02-static-cast.md | static_cast — the workhorse: numeric, up/down-cast, void*, enums, std::move |
| 03 | 03-dynamic-cast.md | dynamic_cast, RTTI, vtables, cross-casts, typeid, the reference-throws rule |
| 04 | 04-const-cast.md | const_cast — adding/removing const/volatile, when it's UB |
| 05 | 05-reinterpret-cast.md | reinterpret_cast — bit reinterpretation, strict aliasing, the danger zone |
| 06 | 06-c-and-functional-casts.md | C-style (T)x, functional T(x), what they actually expand to |
| 07 | 07-pointer-adjustment.md | The this-offset problem: multiple & virtual inheritance, why casts move pointers |
| 08 | 08-bit-cast-and-punning.md | std::bit_cast, memcpy, unions, strict aliasing, start_lifetime_as (C++23) |
| 09 | 09-smart-pointer-casts.md | static_pointer_cast, dynamic_pointer_cast, const_pointer_cast, reinterpret_pointer_cast |
| 10 | 10-narrowing-and-numeric.md | Narrowing, {}-init, signed/unsigned, float↔int, std::in_range, gsl::narrow |
| 11 | 11-patterns-and-pitfalls.md | Decision guide, real bugs, anti-patterns, code review checklist |
| 12 | 12-cheatsheet.md | One-page quick reference / decision tree |
Runnable examples live in examples/ with a build_all.sh script.
+----------------------------------------------------------------------------+
| PREFER NO CAST. A cast is you telling the compiler "trust me, I know |
| more than you." Every cast is a place a future bug can hide. |
| |
| WHEN YOU MUST CAST, use the NAMED cast that expresses the LEAST power: |
| |
| static_cast related types, numeric, up/down-cast (no RTTI) |
| dynamic_cast safe polymorphic down/cross-cast (checked at runtime)|
| const_cast ONLY add/remove const / volatile |
| reinterpret_cast raw bit reinterpretation (last resort, often UB) |
| |
| NEVER use a C-style cast (T)x in C++: it silently picks the most |
| dangerous interpretation, including reinterpret_cast + const_cast. |
+----------------------------------------------------------------------------+
C-style cast (T)expr Named casts (C++)
------------------------ ----------------------------------
ONE syntax that tries, in order: FOUR syntaxes, each does ONE job:
1. const_cast const_cast<T> -> constness
2. static_cast static_cast<T> -> conversions
3. static_cast + const_cast dynamic_cast<T> -> checked poly
4. reinterpret_cast reinterpret_cast<T> -> raw bits
5. reinterpret_cast + const_cast
Problem: (T)x is UNSEARCHABLE and picks silently. If a refactor changes
the type of x, a (T)x that was a harmless static_cast can BECOME a
reinterpret_cast with ZERO warning. Named casts fail to compile instead.
Each named cast is:
- Searchable — you can
grepforreinterpret_castto audit the scary ones. - Intent-revealing — the reader knows what class of conversion is happening.
- Minimally powerful — the compiler rejects misuse instead of silently doing something more dangerous.
- Intermediate C++: classes, inheritance, virtual functions, references,
const, basic templates and smart pointers. - A C++17 compiler for most chapters; C++20/23 for
std::bit_cast,std::start_lifetime_as,std::in_range.
g++ -std=c++23 -Wall -Wextra -Wold-style-cast file.cpp # GCC
clang++ -std=c++23 -Wall -Wextra -Wold-style-cast file.cpp # Clang-Wold-style-cast warns on every C-style cast — turn it on and keep your code
free of them.
Start with 00-why-casting.md.