C++ / a working model

16 / 163   ·   C++11   ·   10 min

The Four Casts: Conversion Intent and Safety Preconditions

Keep this sentence

static_cast, dynamic_cast, const_cast, and reinterpret_cast each express a different conversion intent, but the keywords themselves do not guarantee runtime safety. Before choosing one, first establish numeric range, dynamic type, actual mutability, and the object's lifetime, alignment, and type-access rules.

In this lesson
  1. Numeric Conversions and Type Hierarchies
  2. Qualification Conversion and Low-Level Interpretation
  3. Make Dangerous Preconditions Reviewable
  4. Example
  5. Exercise

Numeric Conversions and Type Hierarchies

static_cast is used for clearly allowed numeric conversions, certain class-hierarchy conversions, and other conversions that can be described statically. It does not automatically check whether a value is out of range; a floating-to-integer conversion should first ensure the truncated value is representable, and a downcast must actually point to the corresponding derived object rather than rest on the programmer's guess.

dynamic_cast performs the required runtime check in a polymorphic class hierarchy and is appropriate for downcasts or cross-casts whose dynamic type is uncertain. The pointer form returns a null pointer on failure; the reference form throws std::bad_cast on failure. It still requires a valid input object in an appropriate lifetime and cannot be used to validate an arbitrary corrupted address.

Qualification Conversion and Low-Level Interpretation

const_cast changes const or volatile qualification; it does not change whether the actual object was originally modifiable. The example restores writable access from a read-only pointer to an ordinary int, so the write is legitimate; if the original object was defined as const, writing after the conversion is undefined behavior.

reinterpret_cast expresses a low-level or pointer-level reinterpretation, but it does not automatically create a target object, satisfy alignment, or lift type-access restrictions. Converting a float* to an int* and then reading is not a general way to inspect a bit pattern; memcpy is appropriate in suitable cases, and C++20 also provides bit_cast constrained by type and size.

Make Dangerous Preconditions Reviewable

C-style cast syntax is short, yet it may combine several conversion abilities, making it hard for a reader to see whether const was removed or a low-level reinterpretation occurred. New code should prefer named casts so the dangerous points become searchable; even better designs often need no cast at all, for example by using the correct interface type or a virtual function.

The example verifies successful and failed dynamic conversions, a numeric conversion known to be in a safe range, and a qualification conversion where the original object is truly non-const. All preconditions are visible in nearby code; incorrect conversions are not executed in order to "observe the result". An interview answer should also state preconditions and failure behavior, not merely list the four English names.

Pitfalls

  • static_cast from a base class to a derived class does not perform a runtime type check; the fact that the conversion compiles is not proof that the object type is correct.
  • A successful reinterpret_cast that produces some pointer value does not mean you may dereference through that type; lifetime, alignment, and type accessibility must each hold.

Run an example

Minimum C++11 · complete program · Download .cpp

#include <cassert>
#include <iostream>

struct Base { virtual ~Base() = default; };
struct Derived : Base { int value = 7; };
struct Other : Base {};

int main() {
    Derived object;
    Base* base = &object;
    Derived* derived = dynamic_cast<Derived*>(base);
    assert(derived != nullptr && derived->value == 7);
    assert(dynamic_cast<Other*>(base) == nullptr);
    int editable = 3;
    const int* read_only = &editable;
    *const_cast<int*>(read_only) = 4;
    const int whole = static_cast<int>(3.75);
    assert(editable == 4 && whole == 3);
    std::cout << derived->value << ' ' << editable << ' ' << whole << '\n';
}

Compile locally

g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread basics-casts.cpp -o example && ./example

Expected result

7 4 3

CHECK YOUR UNDERSTANDING

Close the answer. Explain it.

const int fixed = 5; const int* p = &fixed;. Can const_cast<int*>(p) be formed? Can you write 6 through the result? What happens if dynamic_cast<Derived&> fails instead?

Show a reference answer

The pointer with the qualification removed can be formed, but writing 6 is illegal because the actual object has been const since its definition, and modifying it is undefined behavior. A well-formed conversion expression and a well-formed later access are two different questions. The reference form of dynamic_cast has no null reference as a failure value, so a failed check throws std::bad_cast; catch that exception, or choose the pointer form when failure is allowed.

Check the sources

Drafts and official chapters change. The version mark is only the example’s minimum.

Back to the catalog