ASV
ASV

Reputation: 45

Ambiguous overload on template operators

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:

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

Answers (4)

John P
John P

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

Matthieu M.
Matthieu M.

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

Chris Bednarski
Chris Bednarski

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

Charles Salvia
Charles Salvia

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

Related Questions