Casts — four mechanisms instead of one
Most languages have one cast syntax. C++ has four, and that is not pedantry: a C-style (T)x can hide a safe numeric conversion, an unsafe downcast, a const strip, or a byte reinterpretation — and the compiler cannot tell. Named casts split that space into four classes of operations with different guarantees: each cast allows a specific set, and violating it is a compile error.
C++ also distinguishes three sources of conversion: implicit (inserted by the compiler — standard conversions, integer promotion, narrowing), user-defined (via converting constructor or operator T()), and explicit (the four named casts). The mechanisms overlap: a hierarchy downcast can be implicit (upcast), static-checked, or runtime-checked (dynamic). The full map lives in the layers below.
Topic map
- Type conversion — the family map: implicit/user-defined/explicit, lossy vs lossless.
- Implicit conversions — integer promotion, narrowing, conversion sequences.
- User-defined conversions — converting ctor,
operator T(),explicit. - Explicit casts — the four named casts; what a C-style cast really does.
- Hierarchy casts — upcast, downcast, crosscast; static_cast vs dynamic_cast.
- Dynamic typing and RTTI —
dynamic_cast,typeid,type_info,-fno-rtti. - Pointer conversion —
void*,reinterpret_cast, strict aliasing,bit_cast. - Const-correctness —
const_cast, when safe, when UB,mutable.
Common traps
| Mistake | Consequence |
|---|---|
C-style (T)x in new code | Can silently strip const or reinterpret — runtime bug |
static_cast<Derived*>(base) without a type check | UB on use if the object is not a Derived |
dynamic_cast on a non-polymorphic type | Compile error — no vtable, no RTTI |
Writing through a const_cast of a truly-const object | UB regardless of how many casts you applied |
Reading through reinterpret_cast of an incompatible type | Strict aliasing violation — UB under optimization |
dynamic_cast<T&> without try/catch | std::bad_cast on failure — unhandled exception |
Polymorphic delete without virtual ~Base() | UB; the Derived destructor never runs |
Single-argument constructor without explicit | Silent implicit conversions in surprising places |
int s = -1; if (s < unsigned(1)) | signed → unsigned yields a huge value; comparison lies |
int x{3.14} expecting truncation | Compile error — narrowing forbidden by brace-init |
Interview relevance
Casts come up in interviews because they touch the C++ object model, RTTI, undefined behavior, and strict aliasing directly. The interviewer checks understanding of the guarantees, not syntax.
Typical checks:
- How
static_castdiffers from a C-style cast — C-style tries multiple mechanisms and can silently stripconstor reinterpret. - When
dynamic_castreturnsnullptrand when it throws — pointers vs references. dynamic_caston a non-polymorphic class — compile error, and why.- Strict aliasing — why reading through
reinterpret_castof an incompatible type is UB. std::bit_cast— the safe alternative; requirements (sizeof+ trivially-copyable).const_cast— the only way to stripconst; UB on writes to a truly-const object.- Implicit user-defined conversion and the "at most one" rule in a chain.
- Why
explicitbelongs on a single-argument constructor.
Common wrong answer: "I just use (Type)value everywhere — it's shorter and does the same thing." That is exactly the opening for a discussion of what (T)x actually does and where it breaks.