2.12.  Conversions

[ fromfile: conversions.xml id: conversions ]

A constructor that can be called with a single argument (of a different type) is a conversion constructor because it defines a conversion from the argument type to the constructor's class type.

Example 2.19. src/ctor/conversion/fraction.cpp

class Fraction {
public:
    Fraction(int n)                  1
       : m_Numer(n), m_Denom(1) {}
    Fraction(int n, int d )
       : m_Numer(n), m_Denom(d) {}
    Fraction times(const Fraction& other) {
       return Fraction(m_Numer * other.m_Numer, m_Denom * other.m_Denom);
    }
private:
    int m_Numer, m_Denom;
};
int main() {
    int i;
    Fraction frac(8);                2
    Fraction frac2 = 5;              3
    frac = 9;                        4
    frac = (Fraction) 7;             5
    frac = Fraction(6);              6
    frac = static_cast<Fraction>(6); 7
    frac = frac2.times(19);          8
    return 0;
}

1

Single argument ctor defines a conversion from int.

2

Conversion constructor call.

3

Copy init (calls conversion ctor too).

4

Conversion followed by assignment.

5

C-style typecast (deprecated).

6

Explicit temporary, also a C++ typecast.

7

Preferred ANSI style typecast.

8

Implicit call to the conversion constructor.


In the main() function of Example 2.19, the Fraction variable frac is initialized with a single int. The matching constructor is the one-parameter version. Effectively, it converts the integer 8 to the fraction 8/1.

The prototype for a conversion constructor typically looks like this:

ClassA::ClassA(const ClassB& bobj);

The conversion constructor for ClassA is automatically called when an object of that class is required, and when such an object can be created by that constructor from the value of ClassB that was supplied as an initializer or assigned value.

For example, if frac is a properly initialized Fraction as defined in Example 2.19, you can write the statement

 frac = frac.times(19);

Because 19 is not a Fraction object (as required by the times() function definition), the compiler checks to see whether it can be converted to a Fraction. Because you have a conversion constructor, this is indeed possible.

Fraction::operator=() is not defined, so the compiler uses a default assignment operator that it supplied:

  Fraction& operator=(const Fraction& fobj); 

This assignment operator performs a memberwise assignment, from each data member of fobj to the corresponding member of the host object.

So, that statement calls three Fraction member functions:

  1. Fraction::operator=() to perform the assignment.

  2. Fraction::times() to perform the multiplication.

  3. Fraction::Fraction(19) to convert 19 from int to Fraction.

The temporary Fraction object returned by the times() function exists just long enough to complete the assignment and is then automatically destroyed.

You can simplify the class definition for Fraction by eliminating the one-parameter constructor and providing a default value for the second parameter of the two-parameter constructor. Because it can be called with only one argument, it satisfies the definition of conversion constructor.

Example 2.20. src/ctor/conversion/fraction.h

class Fraction {
public:
    Fraction(int n, int d = 1)
            : m_Numer(n), m_Denom(d) {}
    Fraction times(const Fraction& other) {
       return Fraction(m_Numer* other.m_Numer, m_Denom* other.m_Denom);
    }
            
private:
    int m_Numer, m_Denom;
};


Ordinarily, any constructor that can be called with a single argument of a different type is a conversion constructor that has the implicit mechanisms discussed above. If the implicit mechanisms are not appropriate for some reason, it is possible to suppress them. The keyword explicit prevents the compiler from automatically using that constructor for implicit conversions. [32]



[32] Section 19.8.4 informally discusses an example.