Data types are essential to computer programs, determining how much memory to allocate for a value and how to interpret its bits. For example, 2 and 2.0 are mathematically the same, but their computer representations are very different. The former is typically half as long as the latter and is represented in simple binary, while the latter is represented in the IEEE 754 format. Among the many ways computer scientists classify programming languages are as strongly or weakly typed. Aside from type deduction, programmers must specify a variable's type and a function's return type. Even expressions have a type derived from their data and operations. Every value in a strongly typed language has a data type, and the language rigorously enforces type compatibility. Two types are compatible if a program can combine them without modification. Weakly typed languages permissively allow most combinations, attempting automatic type conversions as needed.
Strong- and weak-typing have their relative advantages and disadvantages. Strong typing can catch some programming errors at compile time, while weak typing may result in runtime errors or inappropriate automatic type conversions. Alternatively, there are often cases where converting a value represented by one data type into a value represented by a different type is necessary. Some strongly typed languages limit or prevent type conversions, leading to awkward or convoluted programs. Although C++ leans toward strong typing, it does perform some automatic type conversions and allows programmers to perform other conversions explicitly.
There are often cases where converting a value represented by one data type into a value represented by a different type is necessary. What the conversions mean and how C++ implements them depends on the data types. For example, the types int
and double
are different kinds of numbers, and converting them is a meaningful operation. Many languages automatically convert an int
to a double
but require programmers to explicitly perform the opposite conversion due to the potential loss of precision. Other type conversions are also possible and often necessary. For example, 2, '2,' and "2" represent an integer, a character, and a string, respectively. Weakly typed languages generally convert one type to another automatically, but more strongly typed languages require explicit programmer intervention.
C++ further allows converting between objects and fundamental types with conversion constructors and operators. Programmers can also use these functions to convert between objects instantiated from different classes.
Chapter 9 introduced conversion constructors, which we review here with a more recent example.
fraction(int n = 0, int d = 1); |
fraction f; |
fraction f(5); |
fraction f(2, 3); |
(a) | (b) | (c) | (d) |
Conversion constructors are not the only conversion mechanism C++ programmers can use. C++ also allows programmers to overload the casting operator. The fraction class doesn't need a conversion operator, so we base the following examples on the Time class, which has three integer member variables: hours, minutes, and seconds. The first step in the Time add function is converting two Time objects into integers that represent the time stored in each object as seconds.
Programmers can create an overloaded conversion operator that converts an instance of Time into an integer and replace the expressions calculating i1 and i2 in the previous example.
Based on the overloaded conversion operator, it's possible to rewrite the Time add function. We also take this opportunity to change the add function into the overloaded operator+
, which is demonstrated both as a member and as a friend
. Unfortunately, neither is the final version of operator+ (see "Conversion Conflicts" in the red highlighted box below).
Member | friend |
---|---|
Time Time::operator+(Time t2) { int i1 = (int)*this; int i2 = (int)t2; int s = i1 + i2; int h = s / 3600; s %= 3600; return Time(h, s / 60, s % 60); } |
Time operator+(Time t1, Time t2) { int i1 = (int)t1; int i2 = (int)t2; int s = i1 + i2; int h = s / 3600; s %= 3600; return Time(h, s / 60, s % 60); } |
(a) | |
int i1 = int(*this); int i2 = int(t2); |
int i1 = int(t1); int i2 = int(t2); |
(b) | |
int i1 = *this; int i2 = t2; |
int i1 = t1; int i2 = t2; |
(c) |
this
pointer, dereferenced, to access the bound or calling object's member variables. The conversion constructor and operator conflict (detailed below), so the function extracts the hours, minutes, and seconds from the sum before calling the three-argument constructor.
Programs sometimes need to convert an object from one class type to another or between a fundamental type and an object. Various situations may dictate when programmers use a constructor or an operator to perform the conversion. In some ways, we can consider one technique the opposite of the other - think of constructors pulling data into an object and operators pushing it out.
Object To Object | Integer To Object |
---|---|
class foo { public: foo(bar b); // bar → foo }; |
class foo { public: foo(int i); // int → foo }; |
(a) | (b) |
Object To Object | Object To Integer |
---|---|
class bar { public: operator foo(); // bar → foo }; |
class bar { public: operator int(); // bar → int }; |
(a) | (b) |
class Time { public: Time(int t); // (a) int(); // (b) friend operator+(Time, Time); }; |
class fraction { private: fraction(int n = 0, int d = 0); // (a) double() { return (double) numerator / denominator; } // (b) double operator+(fraction, fraction); }; |
T + 30
F + 5