Ambiguous overload on template operators
-
06-07-2019 - |
Question
I am working on two wrapper classes that define real and complex data types. Each class defines overloaded constructors, as well as the four arithmetic operators +,-,*,/ and five assignment operators =,+= etc. In order to avoid repeating code, I was thinking of using template functions when the left- and right-hand-side arguments of an operator are of a different data type:
// real.h
class Real {
public:
explicit Real(const double& argument) {...}
explicit Real(int argument) {...}
...
friend const operator*(const Real&; const Real&);
template <class T> friend const Real operator*(const Real&, const T&);
template <class T> friend const Real operator*(const T&, cont Real&);
// Here, T is meant to be a template parameter for double and int
// Repeat for all other arithmetic and assignment operators
};
// complex.h
class Complex {
public:
explicit Complex(const Real& realPart) {...}
explicit Complex(const Real& realPart, const Real& imaginaryPart) {...}
// Overload for double and int data types
...
friend const operator*(const Complex&, const Complex&);
template <class T> friend const Complex operator*(const Complex&, const T&);
template <class T> friend const Complex operator*(const T&, cont Complex&);
// Here, T is is a template parameter for Real, double and int
...
};
The problem here is that code like:
//main.cpp
void main() {
Complex ac(2.0, 3.0);
Real br(2.0);
Complex cc = ac * br;
}
returns the compiler (gcc) error ambiguous overload for 'operator*' in 'ac * br', as the compiler cannot tell the difference between:
template <class T> friend const Complex operator*(const Complex&, const T&)
[with T = Real]template <class T> friend const Real operator*(const T&, cont Real&)
[with T = Complex]
Is there a way to specify that T cannot be a Complex in the template operator* definition in the class Real? Or do I have to do without templates and define each operator for every possible combination of argument data types? Or is there a way to redesign the code?
Solution
Ah, the problem of operators...
Boost created a nice library so that by providing a minimum of logic all the other variations are automagically added for you!
Take a look at Boost.Operators !
Now for your problem, actually as you noticed, you will have to define both flavors of the operators (int and double) rather than using a generic template. If there is a lot of logic in these operators (which I doubt), you can always have them call a common (templated) method.
template <typename T>
Complex complex_mult_impl(T const& lhs, Complex const& rhs) { ... } // Note (1)
// return type is not 'Complex const', see (2)
Complex operator*(int lhs, Complex const& rhs)
{
return complex_mult_impl(lhs,rhs);
}
But if you use Boost.operators you only provide Complex::operator*=(int) and Complex::operator*=(double) and the stand-alone versions will be automatically deduced :)
(1) You might use pass by-value here, if all arguments are built-ins. You might also want to consider Boost.CallTraits, which automatically chooses between by-value and by-ref depending if the argument is built-in or not. It is handy for templates.
(2) When returning arguments by value, it is non-sensical to qualify them as const
. The const
keyword only means something for references and pointers, here nothing prevents the user to instantiate a 'simple' Complex
... and you are fortunate it doesn't!
OTHER TIPS
You could make either the Real or Complex class have non-global multiplication operators.
class Real
{
........
template <class T> const Real operator*(const T&);
const Real operator*(const Real&);
};
Can you make Complex constructors explicit? This will mean that the implicit conversion from Real to Complex is not allowed and should disambiguate the operator *