Skip to content

Wiki

C++ Type Conversion

About 24 min read

Existing English translations were reused. 2 current source blocks remain in Simplified Chinese, without triggering paid translation.

1.What problem does this section solve?

C++ It is common to encounter scenarios where "one type is used as another type", for example:

  • Convert double to int.
  • Convert enum class to an integer.
  • Safely convert a base class pointer to a derived class pointer.
  • Temporarily remove the const qualifier to accommodate legacy interfaces.
  • In low-level code, convert among pointers, integers, and byte views.

C-style cast syntax (目标类型)表达式 can do many things, but that is exactly the problem: it is too "all-purpose," and readers of the code can hardly tell at a glance whether this conversion is an ordinary numeric conversion, an inheritance hierarchy conversion, removing const , or a dangerous low-level reinterpretation.

C++ provides four more explicit type conversion operators, letting the conversion intent be written in the code.

2.implicit conversion and explicit conversion

Type conversion can be divided into implicit conversion and explicit conversion.

2.1.Implicit conversion

An implicit conversion is a conversion performed automatically by the compiler; the conversion operation does not need to be written in the code. For example:

int count = 10;
double result = count;

Here count is int,而 result is double。The compiler automatically converts the integer 10 to the floating-point number 10.0. This is implicit conversion.

Implicit conversions are convenient to write, but some conversions may cause data loss:

double price = 19.8;
int whole_price = price;

This code usually compiles, but whole_price yields 19 and the fractional part is discarded. Because the conversion is not explicitly written, it is also easy to overlook when reading the code.

2.2.explicit conversion

An explicit conversion is a conversion that the programmer explicitly requests in the code. For example:

double price = 19.8;
int whole_price = static_cast<int>(price);

Here, the static_cast<int> explicitly indicates: the programmer knows that price will be converted to int and also accepts that the fractional part will be discarded.

The difference between the two can be simply understood as:

  • Implicit conversion: the compiler automatically converts based on context.
  • Explicit conversion: the programmer explicitly writes in the code what type to convert to.

Implicit conversions are not necessarily unsafe, and explicit conversions do not necessarily mean safety. The main value of writing out a conversion explicitly is to make the conversion intent clearer and to have the compiler perform checking according to the specified conversion method.

The following described static_cast、dynamic_cast、const_cast and reinterpret_cast are all explicit type conversions provided by C++.

3.What is this feature?

C++ The four explicit type conversions are:

Conversion method Primary use Safety level
static_cast<T>(expr) Conventional type conversion, such as numeric, enum, and parent-child class upcasting Commonly used, compile-time checking
dynamic_cast<T>(expr) Safe downcasting of polymorphic types Runtime checking, returns nullptr on failure or throws an exception
const_cast<T>(expr) Add or remove const / volatile qualifiers Use very sparingly
reinterpret_cast<T>(expr) Low-level binary reinterpretation, such as converting between pointers and integers Most dangerous, use sparingly

Principle: Avoid explicit casts whenever possible; when a cast is necessary, prefer the conversion with the narrowest semantics that best conveys the intent.

4.C++ Standard Edition

C++98 already provides these four conversion forms; in actual engineering, they are still recommended to replace C-style casts.

5.Required header files

Type conversion itself does not require additional header files. The libraries used in the example code require corresponding header files, for example:

#include <cstdint>
#include <iostream>

Run result: This code block does not contain the main function; it is a syntax, interface, or multi-file example and cannot be run independently, so there is no terminal output.

6.Basic syntax

目标类型 value = static_cast<目标类型>(表达式);
目标类型 value = dynamic_cast<目标类型>(表达式);
目标类型 value = const_cast<目标类型>(表达式);
目标类型 value = reinterpret_cast<目标类型>(表达式);

Run result: This code block does not contain the main function; it is a syntax, interface, or multi-file example and cannot be run independently, so there is no terminal output.

7.Example code

7.1.Example 1: static_cast Handle regular conversion

static_cast Suitable for explicit, conventional conversions that can be checked at compile time. For example, numeric conversions,enum class to integer, a base class pointer pointing to a derived class object, etc.

#include <iostream>

enum class Status
{
    ok = 200,
    not_found = 404
};

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    double score = 89.7;
    int integer_score = static_cast<int>(score);

    Status status = Status::not_found;
    int status_code = static_cast<int>(status);

    std::cout << "integer_score = " << integer_score << "\n";
    std::cout << "status_code = " << status_code << "\n";

    // 返回 0 表示程序正常结束。
    return 0;
}

Running result:

integer_score = 89
status_code = 404

7.2.Example 2: dynamic_cast Safely determine the actual object type

dynamic_cast Commonly used for polymorphic base classes. When downcasting, it checks the object's true type at runtime.

#include <iostream>

struct Animal
{
    virtual ~Animal() = default;
};

struct Cat : Animal
{
    void meow() const
    {
        std::cout << "cat: meow\n";
    }
};

struct Dog : Animal
{
};

void try_meow(Animal* animal)
{
    Cat* cat = dynamic_cast<Cat*>(animal);

    if (cat != nullptr)
    {
        cat->meow();
    }
    else
    {
        std::cout << "not a cat\n";
    }
}

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    Cat cat;
    Dog dog;

    try_meow(&cat);
    try_meow(&dog);

    // 返回 0 表示程序正常结束。
    return 0;
}

Running result:

cat: meow
not a cat

Note:dynamic_cast When used on a pointer, a failed cast yields nullptr; when used on a reference, a failed cast throws std::bad_cast. It requires the base class to have at least one virtual function, usually a virtual destructor.

7.3.Example 3:const_cast Only change the const qualifier

const_cast can only be added or removed, const / volatile qualification, cannot convert int to double, nor can two unrelated types be converted to each other.

#include <iostream>

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    int value = 10;
    const int& readonly_ref = value;

    int& writable_ref = const_cast<int&>(readonly_ref);
    writable_ref = 20;

    std::cout << "value = " << value << "\n";

    // 返回 0 表示程序正常结束。
    return 0;
}

Running result:

value = 20

In this example,value itself is an ordinary int variable, not const int.

int value = 10;
const int& readonly_ref = value;

这两行代码的意思是:readonly_ref 是 value 的一个“只读引用”。它并没有创建一个新的变量,而是给 value 起了一个只能读取、不能修改的别名。

In other words:

readonly_ref

Essentially, it still points to the original value.

Next:

int& writable_ref = const_cast<int&>(readonly_ref);

This line of code uses const_cast to remove the readonly_ref qualifier on const making it a modifiable reference again. Since it still refers to the original value so:

writable_ref = 20;

Essentially, it is modifying:

value = 20;

Therefore, the final output is:

value = 20

It should be noted that this example can be modified successfully because the original object value itself is not const; we are only temporarily referencing it in the read-only way of const int&.

If the original object itself is const, forcing modification with const_cast is undefined behavior, for example:

const int value = 10;
const int& readonly_ref = value;

int& writable_ref = const_cast<int&>(readonly_ref);
writable_ref = 20; // 未定义行为

So it can be simply remembered:

const_castIt just changes the "read-only view" back to a "writable view"; it does not copy into a new variable, nor does it truly and safely turn an inherently constant value into an ordinary variable.

7.4.Example 4:reinterpret_cast Low-level reinterpretation

reinterpret_cast means "interpret this binary data as another type." It bypasses many type system protections and should usually only appear in very low-level code.

For example, converting a pointer to an integer-form address, and then converting this integer address back to a pointer.

reinterpret_castIt can be used to perform some relatively "low-level" type conversions, such as converting a pointer to an integer-form address, and then converting that integer address back to a pointer.

It should be noted that what is converted here is theaddressnot the value stored in the variable.

#include <cstdint>
#include <iomanip>
#include <iostream>

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    int value = 42;
    int* p = &value;

    std::uintptr_t raw = reinterpret_cast<std::uintptr_t>(p);
    int* again = reinterpret_cast<int*>(raw);

    std::cout << "value = " << value << "\n";
    std::cout << "p = " << p << "\n";

    std::cout << "raw = " << raw << "\n";
    std::cout << "raw(hex) = 0x" << std::hex << raw << std::dec << "\n";

    std::cout << "again = " << again << "\n";
    std::cout << "*again = " << *again << "\n";

    std::cout << std::boolalpha;
    std::cout << "same pointer = " << (p == again) << "\n";

    // 返回 0 表示程序正常结束。
    return 0;
}

Example run result:

value = 42
p = 0x7ffd60a576d4
raw = 140726224910036
raw(hex) = 0x7ffd60a576d4
again = 0x7ffd60a576d4
*again = 42
same pointer = true

Among them, the specific address value may differ each time it runs, so your run result may not be exactly the same as the above.

In this code:

int value = 42;
int* p = &value;

value is an ordinary int variable, and the value stored in it is 42.

p is a int* pointer that stores the address of value rather than the value of value.

That is to say,p what is stored inside is not:

42

Instead, it is an address like this:

0x7ffd60a576d4

Next:

std::uintptr_t raw = reinterpret_cast<std::uintptr_t>(p);

This line of code converts the pointer p to an integer.

std::uintptr_t is an unsigned integer type, and its purpose is to hold a pointer address as safely as possible.

So if:

p = 0x7ffd60a576d4

Then raw stores the integer form corresponding to this address.

For example:

raw = 140726224910036

This is just the decimal representation of the same address.

The following line of code:

std::cout << "raw(hex) = 0x" << std::hex << raw << std::dec << "\n";

is to print raw in hexadecimal. This way, you can see that the hexadecimal form of raw and the address printed by pointer p are basically the same.

Then:

int* again = reinterpret_cast<int*>(raw);

This line of code converts the address in integer form raw back to a int* pointer.

In other words:

p     指向 value
again 也指向 value

Therefore:

std::cout << "*again = " << *again << "\n";

Here, *again is accessing the variable pointed to by again.

Because again is redirected back to the original value,so:

*again

Equivalent to:

value

Therefore, the output result is:

*again = 42

Finally:

std::cout << "same pointer = " << (p == again) << "\n";

This line of code compares whether the addresses stored in the two pointers p and again are the same.

Since they all point to value the result is:

same pointer = true

The entire process can be understood as:

value 是一个变量,值是 42

p 保存 value 的地址

raw 把这个地址用整数形式保存下来

again 又把这个整数地址转换回指针

所以 again 重新指向 value

So the key point of this code is not to convert 42 to an integer, but to convert the value'saddressto an integer.

Simple memory:

int* p = &value;

Indicates:

拿到 value 的地址
std::uintptr_t raw = reinterpret_cast<std::uintptr_t>(p);

Indicates:

把地址当成整数保存
int* again = reinterpret_cast<int*>(raw);

Indicates:

把整数地址再变回指针
*again

Indicates:

通过这个地址找到原来的 value

One-sentence summary:

reinterpret_cast 在这个例子里转换的是“地址的表示形式”,不是变量里面的值。value 的值是 42,而 raw 保存的是 value 的地址。

8.How to Choose Among the Four Conversions

Requirement Recommended写法 Description
double cast to int static_cast<int>(x) Explicitly acknowledges possible loss of fractional part
enum class cast to integer static_cast<int>(e) Enum classes are not implicitly converted to integers
Base class pointer to derived class pointer, and the actual type is uncertain dynamic_cast<Derived*>(p) Conversion failure can be checked with nullptr
remove const adapt to legacy interface const_cast<T*>(p) Provided that the legacy interface does not modify the actual const object
Pointer-to-integer conversion and low-level byte interpretation reinterpret_cast<T>(x) Very low-level; avoid if possible

9.Type Conversion Comparison: Does C-style casting increase runtime overhead?

static_cast<T>(expr)Although angle brackets are used, making it look somewhat like a template, it is not a template but rather a C++ built-in type conversion operator in the language. Here, T indicates the target type to be converted to.

Compared with a functionally equivalent C-style cast, C++ explicit type conversion usually does not incur additional runtime overhead:

Conversion method Typical runtime overhead
static_cast No additional overhead is introduced by using this form
dynamic_cast The object's actual type needs to be checked at runtime, which may incur time and program size overhead
const_cast Usually only changes the way the compiler performs type checking, with no runtime operation
reinterpret_cast Usually only changes the way the compiler interprets the data, with no additional conversion step

For example, the following two expressions represent the same numerical conversion:

double value = 3.14;

int a = (int)value;
int b = static_cast<int>(value);

Compilers typically generate identical or equivalent machine instructions for them.static_castThe advantage of is not running faster, but that the conversion intent is clearer and stricter compile-time checks can be obtained.

However, it is necessary to distinguish between "the overhead of conversion syntax" and "the overhead of the conversion operation itself." For example, converting a floating-point number to an integer may require executing actual processor instructions:

double value = 3.14;
int result = static_cast<int>(value);

The runtime overhead that may arise here comes from the operation of converting double to int", not from static_cast syntax. Even if changed to an implicit conversion or C-style cast, this conversion operation still exists.

Therefore, simply remember: choosing an explicit type conversion of C++ is mainly to make type checking stricter and code intent clearer; except for cases requiring runtime checks of dynamic_cast, it is usually not slower than a C-style cast just because of a different syntax.

10.Why C-style casts are not recommended

C-style casts are short to write:

int n = (int)3.14;

Run result: This code block does not contain the main function; it is a syntax, interface, or multi-file example and cannot be run independently, so there is no terminal output.

It is more recommended to write it as:

int n = static_cast<int>(3.14);

Run result: This code block does not contain the main function; it is a syntax, interface, or multi-file example and cannot be run independently, so there is no terminal output.

A C-style cast may combine multiple conversion capabilities behind the scenes, potentially acting like static_cast, or like const_cast,even approaching reinterpret_cast. The lower-level the code and the more complex the types, the greater the risk of this “not making it clear.”

11.Common Errors

11.1.Use static_cast to do an unsafe downcast

Animal* animal = new Dog;
Cat* cat = static_cast<Cat*>(animal);  // 危险:编译可能通过,但真实对象不是 Cat

Run result: This code block does not contain the main function; it is a syntax, interface, or multi-file example and cannot be run independently, so there is no terminal output.

If the actual type is uncertain, use dynamic_cast.

11.2.Modify the actual const object

const int value = 10;
int& ref = const_cast<int&>(value);
ref = 20;  // 未定义行为

Run result: This code block does not contain the main function; it is a syntax, interface, or multi-file example and cannot be run independently, so there is no terminal output.

11.3.Misuse reinterpret_cast

double d = 3.14;
int* p = reinterpret_cast<int*>(&d);  // 危险:把 double 对象当 int 对象访问

Run result: This code block does not contain the main function; it is a syntax, interface, or multi-file example and cannot be run independently, so there is no terminal output.

12.Usage Recommendations

  1. For ordinary conversions, prefer static_cast.
  2. For polymorphic downcasts, prefer dynamic_cast and check for failure.
  3. const_castUse only to adapt to legacy interfaces; do not use it to modify the real const object.
  4. reinterpret_castPlace only in very low-level, clearly bounded code, and encapsulate it centrally.
  5. Do not use C-style casts just to save a few keystrokes.

13.Summary

  • static_cast: Regular conversion, the most commonly used.
  • dynamic_cast: Polymorphic type-safe downcast.
  • const_cast: Only changes const / volatile qualifiers.
  • reinterpret_cast: Low-level reinterpretation, highest risk.

One-sentence takeaway: Avoid it if possible; when it must be used, state the conversion intent clearly.