views:

282

answers:

3

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?

+1  A: 

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&);

};
Charles Salvia
A: 

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 *

Chris Bednarski
The constructors should indeed be made explicit (question edited). However, in this case making them explicit does not eliminate the compile error.
ASV
+2  A: 

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!

Matthieu M.