The inclusion of the operator float conversion operator creates problems for the Fraction class. Consider the following statement:
a = b + 1234; // Error: ambiguous
// a = (float)b + 1234;
// a = b + Fraction( 1234 );
The compiler could either convert b to a floating-point number and then add that together with the integer, or it could convert 1234 to a Fraction and then add the two Fraction objects. That is, the compiler could add the two values as built-in types, or it could add them as objects. The compiler has no basis for choosing one conversion over the other, so it generates an error.
There are a few ways to resolve this ambiguity. One solution is to use an ordinary member function to perform addition, instead of overloading the + operator. For example:
class Fraction
{
friend Fraction add( const Fraction &first,
const Fraction &second );
};
Since this function does not look like the + operator, there is no confusion between adding two values as Fraction objects or adding them as built-in types.
Another solution is to remove one of the implicit conversions. You could remove the implicit conversion from an integer to a Fraction by getting rid of the single-argument constructor. This requires that you rewrite the previous statement as follows:
a = b + Fraction( 1234, 1 );
If you wanted to add them as built-in types, you would have to write the following:
a = Fraction( b + 1234, 1 );
Or you could remove the implicit conversion from a Fraction to a floating-point number, by changing the conversion operator into an ordinary member function. For example:
class Fraction
{
public:
float cvtToFloat() const;
// ...
};
This leaves only one interpretation for the following statement:
a = b + 1234; // a = b + Fraction( 1234 );
If you wanted to add the two values as built-in types, you would have to write the following:
a = b.cvtToFloat() + 1234;
If you wish to retain the convenience of both of these implicit conversions, as well as use operator overloading, you must explicitly define all three versions of the operator+ function:
class Fraction
{
friend Fraction operator+( const Fraction &first,
const Fraction &second );
friend Fraction operator+( long first,
const Fraction &second );
friend Fraction operator+( const Fraction &first,
long second );
// ...
};
If all three functions are defined, the compiler doesn't have to perform any conversions to resolve expressions that mix Fraction objects and integers. The compiler simply calls the function that matches each possibility. This solution requires more work when writing the class, but it makes the class more usable.
As this example illustrates, you must use care if you define both overloaded operators and implicit conversions.