Reputation: 45
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?
Upvotes: 2
Views: 2722
Reputation: 1581
I have a few ideas, but this one appears to work. I got past the error you were getting in any case.
class Real;
class Complex;
template<class L, class R> using NotComplex =
std::enable_if_t<!std::is_base_of_v<L, Complex>, R>;
//...
template <class T>
friend const auto operator*(const Real&, const T&)
-> NotComplex<T, Real>;
Alternatively, you might want to look at std::is_arithmetic
or other ways to say "a Real or scalar type" or "not a Complex".
Check out this page on std::conjunction
to see an example where they enable a template if a type is in a list of types.
Upvotes: 0
Reputation: 300439
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!
Upvotes: 2
Reputation: 3434
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 *
Upvotes: 0
Reputation: 53339
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&);
};
Upvotes: 1