In C++ some types are related to each other. When two types are related, we can use an object or value of one type where an operand of the related type is expected. Two types are related if there is a conversion between them.
As an example, consider the following expression, which initializes ival
to 6:
int ival = 3.541 + 3; // the compiler might warn about loss of precision
The operands of the addition are values of two different types: 3.541
has type double
, and 3
is an int
. Rather than attempt to add values of the two different types, C++ defines a set of conversions to transform the operands to a common type. These conversions are carried out automatically without programmer intervention—and sometimes without programmer knowledge. For that reason, they are referred to as implicit conversions.
The implicit conversions among the arithmetic types are defined to preserve precision, if possible. Most often, if an expression has both integral and floatingpoint operands, the integer is converted to floating-point. In this case, 3
is converted to double
, floating-point addition is done, and the result is a double
.
The initialization happens next. In an initialization, the type of the object we are initializing dominates. The initializer is converted to the object’s type. In this case, the double
result of the addition is converted to int
and used to initialize ival
. Converting a double
to an int
truncates the double
’s value, discarding the decimal portion. In this expression, the value 6
is assigned to ival
.
The compiler automatically converts operands in the following circumstances:
• In most expressions, values of integral types smaller than
int
are first promoted to an appropriate larger integral type.
• In conditions, non
bool
expressions are converted tobool
.
• In initializations, the initializer is converted to the type of the variable; in assignments, the right-hand operand is converted to the type of the left-hand.
• In arithmetic and relational expressions with operands of mixed types, the types are converted to a common type.
• As we’ll see in Chapter 6, conversions also happen during function calls.
The arithmetic conversions, which we introduced in § 2.1.2 (p. 35), convert one arithmetic type to another. The rules define a hierarchy of type conversions in which operands to an operator are converted to the widest type. For example, if one operand is of type long double
, then the other operand is converted to type long double
regardless of what the second type is. More generally, in expressions that mix floating-point and integral values, the integral value is converted to an appropriate floating-point type.
The integral promotions convert the small integral types to a larger integral type. The types bool
, char
, signed char
, unsigned char
, short
, and unsigned short
are promoted to int
if all possible values of that type fit in an int
. Otherwise, the value is promoted to unsigned int
. As we’ve seen many times, a bool
that is false
promotes to 0 and true
to 1.
The larger char
types (wchar_t
, char16_t
, and char32_t
) are promoted to the smallest type of int
, unsigned int
, long
, unsigned long
, long long
, or unsigned long long
in which all possible values of that character type fit.
If the operands of an operator have differing types, those operands are ordinarily converted to a common type. If any operand is an unsigned
type, the type to which the operands are converted depends on the relative sizes of the integral types on the machine.
As usual, integral promotions happen first. If the resulting type(s) match, no further conversion is needed. If both (possibly promoted) operands have the same signedness, then the operand with the smaller type is converted to the larger type.
When the signedness differs and the type of the unsigned operand is the same as or larger than that of the signed operand, the signed operand is converted to unsigned. For example, given an unsigned int
and an int
, the int
is converted to unsigned int
. It is worth noting that if the int
has a negative value, the result will be converted as described in § 2.1.2 (p. 35), with the same results.
The remaining case is when the signed operand has a larger type than the unsigned operand. In this case, the result is machine dependent. If all values in the unsigned type fit in the larger type, then the unsigned operand is converted to the signed type. If the values don’t fit, then the signed operand is converted to the unsigned type. For example, if the operands are long
and unsigned int
, and int
and long
have the same size, the long
will be converted to unsigned int
. If the long
type has more bits, then the unsigned int
will be converted to long
.
One way to understand the arithmetic conversions is to study lots of examples:
bool flag; char cval;
short sval; unsigned short usval;
int ival; unsigned int uival;
long lval; unsigned long ulval;
float fval; double dval;
3.14159L + 'a'; // 'a' promoted to int, then that int converted to long double
dval + ival; // ival converted to double
dval + fval; // fval converted to double
ival = dval; // dval converted (by truncation) to int
flag = dval; // if dval is 0, then flag is false, otherwise true
cval + fval; // cval promoted to int, then that int converted to float
sval + cval; // sval and cval promoted to int
cval + lval; // cval converted to long
ival + ulval; // ival converted to unsigned long
usval + ival; // promotion depends on the size of unsigned short and int
uival + lval; // conversion depends on the size of unsigned int and long
In the first addition, the character constant lowercase 'a'
has type char
, which is a numeric value (§ 2.1.1, p. 32). What that value is depends on the machine’s character set. On our machine, 'a'
has the numeric value 97. When we add 'a'
to a long double
, the char
value is promoted to int
, and then that int
value is converted to a long double
. The converted value is added to the literal. The other interesting cases are the last two expressions involving unsigned values. The type of the result in these expressions is machine dependent.
Exercises Section 4.11.1
Exercise 4.34: Given the variable definitions in this section, explain what conversions take place in the following expressions:
(a)
if (fval)
(b)
dval = fval + ival;
(c)
dval + ival * cval;
Remember that you may need to consider the associativity of the operators.
char cval; int ival; unsigned int ui;
float fval; double dval;identify the implicit type conversions, if any, taking place:
(a)
cval = 'a' + 3;
(b)
fval = ui - ival * 1.0;
(c)
dval = ui * fval;
(d)
cval = ival + fval + dval;
In addition to the arithmetic conversions, there are several additional kinds of implicit conversions. These include:
Array to Pointer Conversions: In most expressions, when we use an array, the array is automatically converted to a pointer to the first element in that array:
int ia[10]; // array of ten ints
int* ip = ia; // convert ia to a pointer to the first element
This conversion is not performed when an array is used with decltype
or as the operand of the address-of (&
), sizeof
, or typeid
(which we’ll cover in § 19.2.2 (p. 826)) operators. The conversion is also omitted when we initialize a reference to an array (§ 3.5.1, p. 114). As we’ll see in § 6.7 (p. 247), a similar pointer conversion happens when we use a function type in an expression.
Pointer Conversions: There are several other pointer conversions: A constant integral value of 0 and the literal nullptr
can be converted to any pointer type; a pointer to any nonconst
type can be converted to void*
, and a pointer to any type can be converted to a const void*
. We’ll see in § 15.2.2 (p. 597) that there is an additional pointer conversion that applies to types related by inheritance.
Conversions to bool:
There is an automatic conversion from arithmetic or pointer types to bool
. If the pointer or arithmetic value is zero, the conversion yields false;
any other value yields true
:
char *cp = get_string();
if (cp) /* ... */ // true if the pointer cp is not zero
while (*cp) /* ... */ // true if *cp is not the null character
Conversion to const:
We can convert a pointer to a nonconst
type to a pointer to the corresponding const
type, and similarly for references. That is, if T
is a type, we can convert a pointer or a reference to T
into a pointer or reference to const T
, respectively (§ 2.4.1, p. 61, and § 2.4.2, p. 62):
int i;
const int &j = i; // convert a nonconst to a reference to const int
const int *p = &i; // convert address of a nonconst to the address of a const
int &r = j, *q = p; // error: conversion from const to nonconst not allowed
The reverse conversion—removing a low-level const
—does not exist.
Conversions Defined by Class Types: Class types can define conversions that the compiler will apply automatically. The compiler will apply only one class-type conversion at a time. In § 7.5.4 (p. 295) we’ll see an example of when multiple conversions might be required, and will be rejected.
Our programs have already used class-type conversions: We use a class-type conversion when we use a C-style character string where a library string
is expected (§ 3.5.5, p. 124) and when we read from an istream
in a condition:
string s, t = "a value"; // character string literal converted to type string
while (cin >> s) // while condition converts cin to bool
The condition (cin >> s
) reads cin
and yields cin
as its result. Conditions expect a value of type bool
, but this condition tests a value of type istream
. The IO library defines a conversion from istream
to bool
. That conversion is used (automatically) to convert cin
to bool
. The resulting bool
value depends on the state of the stream. If the last read succeeded, then the conversion yields true
. If the last attempt failed, then the conversion to bool
yields false
.
Sometimes we want to explicitly force an object to be converted to a different type. For example, we might want to use floating-point division in the following code:
int i, j;
double slope = i/j;
To do so, we’d need a way to explicitly convert i
and/or j
to double
. We use a cast to request an explicit conversion.
A named cast has the following form:
cast-name<type>(expression);
where type is the target type of the conversion, and expression is the value to be cast. If type is a reference, then the result is an lvalue. The cast-name may be one of static_cast
, dynamic_cast
, const_cast
, and reinterpret_cast
. We’ll cover dynamic_cast
, which supports the run-time type identification, in § 19.2 (p. 825). The cast-name determines what kind of conversion is performed.
static_cast
Any well-defined type conversion, other than those involving low-level const
, can be requested using a static_cast
. For example, we can force our expression to use floating-point division by casting one of the operands to double
:
// cast used to force floating-point division
double slope = static_cast<double>(j) / i;
A static_cast
is often useful when a larger arithmetic type is assigned to a smaller type. The cast informs both the reader of the program and the compiler that we are aware of and are not concerned about the potential loss of precision. Compilers often generate a warning for assignments of a larger arithmetic type to a smaller type. When we do an explicit cast, the warning message is turned off.
A static_cast
is also useful to perform a conversion that the compiler will not generate automatically. For example, we can use a static_cast
to retrieve a pointer value that was stored in a void*
pointer (§ 2.3.2, p. 56):
void* p = &d; // ok: address of any nonconst object can be stored in a void*
// ok: converts void* back to the original pointer type
double *dp = static_cast<double*>(p);
When we store a pointer in a void*
and then use a static_cast
to cast the pointer back to its original type, we are guaranteed that the pointer value is preserved. That is, the result of the cast will be equal to the original address value. However, we must be certain that the type to which we cast the pointer is the actual type of that pointer; if the types do not match, the result is undefined.
const_cast
A const_cast
changes only a low-level (§ 2.4.3, p. 63) const
in its operand:
const char *pc;
char *p = const_cast<char*>(pc); // ok: but writing through p is undefined
Conventionally we say that a cast that converts a const
object to a nonconst
type “casts away the const
.” Once we have cast away the const
of an object, the compiler will no longer prevent us from writing to that object. If the object was originally not a const
, using a cast to obtain write access is legal. However, using a const_cast
in order to write to a const
object is undefined.
Only a const_cast
may be used to change the const
ness of an expression. Trying to change whether an expression is const
with any of the other forms of named cast is a compile-time error. Similarly, we cannot use a const_cast
to change the type of an expression:
const char *cp;
// error: static_cast can't cast away const
char *q = static_cast<char*>(cp);
static_cast<string>(cp); // ok: converts string literal to string
const_cast<string>(cp); // error: const_cast only changes constness
A const_cast
is most useful in the context of overloaded functions, which we’ll describe in § 6.4 (p. 232).
reinterpret_cast
A reinterpret_cast
generally performs a low-level reinterpretation of the bit pattern of its operands. As an example, given the following cast
int *ip;
char *pc = reinterpret_cast<char*>(ip);
we must never forget that the actual object addressed by pc
is an int
, not a character. Any use of pc
that assumes it’s an ordinary character pointer is likely to fail at run time. For example:
string str(pc);
is likely to result in bizarre run-time behavior.
The use of pc
to initialize str
is a good example of why reinterpret_cast
is dangerous. The problem is that types are changed, yet there are no warnings or errors from the compiler. When we initialized pc
with the address of an int
, there is no error or warning from the compiler because we explicitly said the conversion was okay. Any subsequent use of pc
will assume that the value it holds is a char*
. The compiler has no way of knowing that it actually holds a pointer to an int
. Thus, the initialization of str
with pc
is absolutely correct—albeit in this case meaningless or worse! Tracking down the cause of this sort of problem can prove extremely difficult, especially if the cast of ip
to pc
occurs in a file separate from the one in which pc
is used to initialize a string
.
A
reinterpret_cast
is inherently machine dependent. Safely usingreinterpret_cast
requires completely understanding the types involved as well as the details of how the compiler implements the cast.
In early versions of C++, an explicit cast took one of the following two forms:
type (expr); // function-style cast notation
(type) expr; // C-language-style cast notation
Casts interfere with normal type checking (§ 2.2.2, p. 46). As a result, we strongly recommend that programmers avoid casts. This advice is particularly applicable to
reinterpret_cast
s. Such casts are always hazardous. Aconst_cast
can be useful in the context of overloaded functions, which we’ll cover in § 6.4 (p. 232). Other uses ofconst_cast
often indicate a design flaw. The other casts,static_cast
anddynamic_cast
, should be needed infrequently. Every time you write a cast, you should think hard about whether you can achieve the same result in a different way. If the cast is unavoidable, errors can be mitigated by limiting the scope in which the cast value is used and by documenting all assumptions about the types involved.
Depending on the types involved, an old-style cast has the same behavior as a const_cast
, a static_cast
, or a reinterpret_cast
. When we use an old-style cast where a static_cast
or a const_cast
would be legal, the old-style cast does the same conversion as the respective named cast. If neither cast is legal, then an old-style cast performs a reinterpret_cast
. For example:
char *pc = (char*) ip; // ip is a pointer to int
has the same effect as using a reinterpret_cast
.
Old-style casts are less visible than are named casts. Because they are easily overlooked, it is more difficult to track down a rogue cast.
Exercises Section 4.11.3
Exercise 4.36: Assuming
i
is anint
andd
is adouble
write the expressioni
*=d
so that it does integral, rather than floating-point, multiplication.Exercise 4.37: Rewrite each of the following old-style casts to use a named cast:
int i; double d; const string *ps; char *pc; void *pv;
(a)
pv = (void*)ps;
(b)
i = int(*pc);
(c)
pv = &d;
(d)
pc = (char*) pv;
double slope = static_cast<double>(j/i);