1

I've run into something I don't understand in regards to a class I've built and how it interacts with built-in types through operator overloading in C++. As an example of my problem below is a (incomplete) complex number class:

class complex {
public:
  complex(double real=0., double imag=0.) : _real(real), _imag(imag) {;}
  ~complex() {;}
  complex& operator=(const complex &rhs) {
    if(this!=&rhs) {
      this->_real = rhs._real;
      this->_imag = rhs._imag;
    }
    return *this; 
  }
  complex& operator*=(const complex &rhs) {
    this->_real = this->_real * rhs._real + this->_imag * rhs._imag;
    this->_imag = this->_real * rhs._imag + this->_imag * rhs._real;
    return *this;
  }
  const complex operator*(const complex &other) const {
    complex result = *this;
    result *= other;
    return result;
  }
protected:
  double _real;
  double _imag;
};

When I call this code with the following main:

int main(void)
{
  complex a(1.,0.);
  complex b;
  b = a * 2.;
  b = 2. * a;
  return 0;
}

I get the complier error for the second to last line in "main.cpp":

error: no match for ‘operator*’ in ‘2.0e+0 * a’

but no error for the line before. If I cast the "2.0" in the offending line to a complex then all is good. So my question is, how/why does the compiler know to cast the double to a complex number in the first line but (appears to ) want to use the double version of the operator* in the second line?

If I could derive a class, say Real, that derives off of double and adds something like:

const complex operator*(const double &other)

then I think this would work but I know I can't do that (built-in types can't be used as base-classes).

Thanks!


@MikeSeymore had a nice fix. Add a non-member function. What I ended up with was:

complex operator*(double lhs, complex const &rhs) {
  complex result(lhs,0.);
  return result*=rhs;
}

and all is good with the world. Thanks Mike.

BTW: A discussion of non-class overloading Operator overloading outside class

Community
  • 1
  • 1
boneheadgeek
  • 765
  • 1
  • 7
  • 9
  • 2
    Don’t you get tons of annoying warnings for `{;}` in your code? If not, kick up the warning level a notch. – Konrad Rudolph Jun 27 '14 at 14:25
  • 2
    You can remove the copy-assignment operator; it's doing exactly what the implicitly generated one would. – Mike Seymour Jun 27 '14 at 14:27
  • @KonradRudolph: Actually (and this might not be the case anymore) a few compilers I've used in the past actually would have problems if there was no code in the body. This has become a habit that may not be necessary anymore. – boneheadgeek Jun 27 '14 at 14:28
  • 1
    As an aside, you shouldn't define special members - destructor, copy constructor, copy and move assignment - when the implicit definitions do the right thing. Let the compiler do its job. – Casey Jun 27 '14 at 15:19

1 Answers1

6

Since operator* is a member function, conversions can only be applied to its right-hand operand. The left-hand operand has to be of type complex.

The simplest fix is to make it a non-member:

complex operator*(complex lhs, complex const & rhs) {return lhs *= rhs;}

If performance is important, then you might like to provide specialisations for double, rather than relying on the implicit conversion, to avoid unnecessary multiplications by zero:

// This one can be a member or a friend, according to taste
complex operator*(double rhs) const {return complex(_real * rhs, _imag * rhs);}

// This one can't be a member
complex operator*(double lhs, complex const & rhs) {return rhs * lhs;}

In general, you shouldn't return const values: that inhibits move semantics.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • `operator*` shouldn't mutate its operands. `*=` should be just `*`. – David G Jun 27 '14 at 14:32
  • 1
    @0x499602D2: It's mutating a copy of its left-hand operand to get the value it needs to return. The definition of `*` can't call `*` itself - that leads to recursive death. – Mike Seymour Jun 27 '14 at 14:33
  • Okay, I mistakenly thought `lhs` was a reference. – David G Jun 27 '14 at 14:36
  • @MikeSeymour: Got it. Didn't occur to me to create a non-member function. Thanks. Note that the first option you provided didn't work for me but the last did (mostly, i'll post what I ended up using in edits). – boneheadgeek Jun 27 '14 at 14:39
  • 1
    @boneheadgeek, The first is how you'd normally overload operators with compound versions that take two `YourClass`es. – chris Jun 27 '14 at 14:51