Where I can find an excellently understandable article on C++ type conversion covering all of its types (promotion, implicit/explicit, etc.)?
I\'ve been learning C++ fo
(Props to Crazy Eddie for a first answer, but I feel it can be made clearer)
Type conversion can happen for two main reasons. One is because you wrote an explicit expression, such as static_cast
. Another reason is that you used an expression at a place where the compiler needed another type, so it will insert the conversion for you. E.g. 2.5 + 1
will result in an implicit cast from 1 (an integer) to 1.0 (a double).
There are only a limited number of explicit forms. First off, C++ has 4 named versions: static_cast
, dynamic_cast
, reinterpret_cast
and const_cast
. C++ also supports the C-style cast (Type) Expression
. Finally, there is a "constructor-style" cast Type(Expression)
.
The 4 named forms are documented in any good introductory text. The C-style cast expands to a static_cast
, const_cast
or reinterpret_cast
, and the "constructor-style" cast is a shorthand for a static_cast
. However, due to parsing problems, the "constructor-style" cast requires a singe identifier for the name of the type; unsigned int(-5)
or const float(5)
are not legal.
It's much harder to enumerate all the contexts in which an implicit conversion can happen. Since C++ is a typesafe OO language, there are many situations in which you have an object A in a context where you'd need a type B. Examples are the built-in operators, calling a function, or catching an exception by value.
In all cases, implicit and explicit, the compiler will try to find a conversion sequence. A conversion sequence is a series of steps that gets you from type A to type B. The exact conversion sequence chosen by the compiler depends on the type of cast. A dynamic_cast
is used to do a checked Base-to-Derived conversion, so the steps are to check whether Derived inherits from Base, via which intermediate class(es). const_cast
can remove both const
and volatile
. In the case of a static_cast
, the possible steps are the most complex. It will do conversion between the built-in arithmetic types; it will convert Base pointers to Derived pointers and vice versa, it will consider class constructors (of the destination type) and class cast operators (of the source type), and it will add const
and volatile
. Obviously, quite a few of these step are orthogonal: an arithmetic type is never a pointer or class type. Also, the compiler will use each step at most once.
As we noted earlier, some type conversions are explicit and others are implicit. This matters to static_cast
because it uses user-defined functions in the conversion sequence. Some of the conversion steps consiered by the compiler can be marked as explicit
(In C++03, only constructors can). The compiler will skip (no error) any explicit
conversion function for implicit conversion sequences. Of course, if there are no alternatives left, the compiler will still give an error.
Integer types such as char
and short
can be converted to "greater" types such as int
and long
, and smaller floating-point types can similarly be converted into greater types. Signed and unsigned integer types can be converted into each other. Integer and floating-point types can be changed into each other.
Since C++ is an OO language, there are a number of casts where the relation between Base and Derived matters. Here it is very important to understand the difference between actual objects, pointers, and references (especially if you're coming from .Net or Java). First, the actual objects. They have precisely one type, and you can convert them to any base type (ignoring private base classes for the moment). The conversion creates a new object of base type. We call this "slicing"; the derived parts are sliced off.
Another type of conversion exists when you have pointers to objects. You can always convert a Derived*
to a Base*
, because inside every Derived object there is a Base subobject. C++ will automatically apply the correct offset of Base with Derived to your pointer. This conversion will give you a new pointer, but not a new object. The new pointer will point to the existing sub-object. Therefore, the cast will never slice off the Derived part of your object.
The conversion the other way is trickier. In general, not every Base*
will point to Base sub-object inside a Derived object. Base objects may also exist in other places. Therefore, it is possible that the conversion should fail. C++ gives you two options here. Either you tell the compiler that you're certain that you're pointing to a subobject inside a Derived via a static_cast
, or you ask the compiler to check with dynamic_cast
. In the latter case, the result will be nullptr
if baseptr
doesn't actually point to a Derived object.
For references to Base and Derived, the same applies except for dynamic_cast
: it will throw std::bad_cast
instead of returning a null pointer. (There are no such things as null references).
There are two ways to define user conversions: via the source type and via the destination type. The first way involves defining a member operator DestinatonType() const
in the source type. Note that it doesn't have an explicit return type (it's always DestinatonType
), and that it's const
. Conversions should never change the source object. A class may define several types to which it can be converted, simply by adding multiple operators.
The second type of conversion, via the destination type, relies on user-defined constructors. A constructor T::T
which can be called with one argument of type U
can be used to convert a U
object into a T object. It doesn't matter if that constructor has additional default arguments, nor does it matter if the U argument is passed by value or by reference. However, as noted before, if T::T(U)
is explicit
, then it will not be considered in implicit conversion sequences.
it is possible that multiple conversion sequences between two types are possible, as a result of user-defined conversion sequences. Since these are essentially function calls (to user-defined operators or constructors), the conversion sequence is chosen via overload resolution of the different function calls.