How do you explain the differences among static_cast, reinterpret_cast, const_cast, and...

Quora Feeds

Active Member
Brian Bi

Why are they really needed?​
Good question! Actually, this is the key to understanding why C++ has four different casts.

In C there is only a single cast, but it performs many different conversions:
  1. Between two arithmetic types
  2. Between a pointer type and an integer type
  3. Between two pointer types
  4. Between a cv-qualified and cv-unqualified type
  5. A combination of (4) and either (1), (2), or (3)
C++ adds the following features that really change the game:
  • Inheritance
  • Templates
Why are these important? Well, inheritance changes the game because now there are three different things we might mean by a pointer cast. Say class D derives from class B. What should (D*)pb do, where pb has type B*? Here are three possibilities:
  1. Return a pointer to the same byte of memory, but just change the type of the pointer. (This is the same as what all pointer casts do in C, as explained above.)
  2. Check whether the B* really points to a B that is part of a D object. If so, return a pointer to the D object. If not, fail (maybe by returning a null pointer or throwing an exception.)
  3. Assume that the B* points to a B that is part of a D object; don't bother performing a check. Adjust the address of the pointer if necessary so that it will point to the D object.
It is conceivable that in C++ we might want to perform any of these three conversions. Therefore C++ gives us three different casts:reinterpret_cast<D*> for function (1), dynamic_cast<D*> for function (2), and static_cast<D*> for function (3).

Now, I also mentioned the fact that C++ has templates. The reason why this is relevant is that if you use C-style casts in C++, and either the source or target or both types are template parameters or depend on template parameters, then in general the kind of casting involved might depend on the template parameters. For example, say we write a function like this:

template <class T>
unsigned char* alias(T& x) {
return (unsigned char*)(&x);
}


So you can pass in any lvalue and you get a pointer to its first byte. All right! But hang on, what happens if I pass in a const lvalue? Then the C-style cast silently casts away the constness, and the function returns an unsigned char* which we can then use to modify the original object, causing undefined behaviour. So, you see, the C-style cast to pointer sometimes just changes the type of the object pointed to, but sometimes it just removes const (like if we pass in a const unsigned char lvalue) and sometimes it does both (like if we pass in a const int lvalue). Better to write

template <class T>
unsigned char* safe_alias(T& x) {
return reinterpret_cast<unsigned char*>(&x);
}


That way, if someone passes in a const lvalue, they'll get a compilation error, telling them---whoops! You almost casted away constness, which is super dangerous! Glad we caught that in time, eh?

And that's why we need const_cast as a separate cast from the other three casts: its function is entirely distinct from the other three casts' functions, you rarely need to cast away constness, and you almost always want the compiler to prevent you from accidentally casting away constness. If every static_cast, dynamic_cast, and reinterpret_cast had the power to cast away constness too, using them would become a lot more dangerous---especially when templates are involved! Now if someone really wants to get a char* to a const int object, they can call, e.g., safe_alias(const_cast<int&>(x)). Now it's explicit that constness is being cast away.

The fact that C++ has templates also forces us to explicitly perform implicit conversions sometimes! Consider the following in C:

int* pi;
void* pv = pi; // OK; implicit conversion
void* pv2 = (void*)pi; // OK but unnecessary
void f(void* pv);
f(pi); // OK; implicit conversion
f((void*)pi); // OK but unnecessary


But in C++ we can have something like this:

template <class T> void f(T* p);
// ...
int* pi;
f(pi); // OK; calls f<int>
f(static_cast<void*>(pi)); // OK; calls f<void>


Even though int* can be converted to void* without a cast, we might still need a cast anyway in order to force the argument to have the correct type! (That is, if we want to call f<void> and not f<int>.) In C this doesn't happen because you can't overload functions, let alone write function templates.

Boost provides an implicit_cast function template specifically designed to explicitly perform implicit conversions. Many people feel that this should become a part of standard C++. But for now all we have is static_cast. So to summarize approximately:
  • static_cast performs implicit conversions, the reverses of implicit standard conversions, and (possibly unsafe) base to derived conversions.
  • reinterpret_cast converts one pointer to another without changing the address, or converts between pointers and their numerical (integer) values.
  • const_cast only changes cv-qualification; all other casts cannot cast away constness.
  • dynamic_cast casts up and down class hierarchies only, always checking that the conversion requested is valid.
and the reason why there are four different casts in C++ is so that you can write a cast and be explicit about what kind of conversion you intend to perform; the compiler will never incorrectly "guess" what you meant; and other people reading your code will be able to tell what kind of conversions it does.

See Questions On Quora

Continue reading...
 
Top