green diod
green diod

Reputation: 1499

"Polymorphic" non-member functions/operators, do I need extra overloadings?

I want to override parent class overloaded operators but I'd like to avoid boilerplate code rewriting all non-member operators for inherited class. Is it possible at all?

In the following example, I overloaded virtual Foo & Foo::operator+=(Foo const &) and based a free function Foo & operator+(Foo, Foo const &) out of it. In Bar, I overrode Bar & Bar::operator+=(Foo const &) override. What I want is the free function to call the overriden function when I state Bar + Foo and I expect Foo as a result. I know that overloading again Bar operator+(Bar, Foo const &) solves for that particular situation but I'd like to avoid explicitly do that if possible (think about all the other operators). And then there's also Foo + Bar that I want to return Bar.

#include <iostream>

class Foo {
public:
  Foo(unsigned int bottles=11) : bottles(bottles) {} // This is odd on purpose

  virtual void display(std::ostream & out) const {
    out << bottles << " bottles";
  }

  virtual Foo & operator+=(Foo const &);

protected:
  unsigned int bottles;
};

std::ostream & operator<<(std::ostream & out, Foo const & f) {
  f.display(out);
  return out;
}

Foo & Foo::operator+=(Foo const &f) {
  bottles += f.bottles;
  return *this;
}

Foo const operator+(Foo f, Foo const & g) {
  return f += g;
}

class Bar : public Foo {
public:
  Bar(unsigned int bottles=0) : Foo(bottles) { enforce(); }
  Bar(Foo const & f) : Foo(f) { enforce(); }

  void display(std::ostream & out) const override {
    out << bottles << " manageable bottles";
  }

  Bar & operator+=(Foo const &) override;

private:
  void enforce() { bottles /= 2; bottles *=2; }
};

Bar & Bar::operator+=(Foo const &f) {
  Foo::operator+=(f);
  enforce();
  return *this;
}



int main () {
  std::cout << "----- Foo + Foo -----" << std::endl;
  Foo bar;
  Foo becue(2);
  std::cout << bar << " + " << becue << " -> (+) "
    << bar + becue << std::endl;

  std::cout << "----- Bar + Bar -----" << std::endl;
  Bar crazy(bar);
  Bar horse(5);
  std::cout << crazy << " + " << horse << " -> (+) "
    <<  crazy + horse << std::endl;

  std::cout << "----- Bar + Foo -----" << std::endl;
  std::cout << crazy << " + " << bar << " -> (+) "
    <<  crazy + bar << std::endl;

  std::cout << "----- Foo + Bar -----" << std::endl;
  std::cout << bar << " + " << horse << " -> (+) "
    <<  bar + horse << std::endl;

  return 0;
}

I expect manageable bottles as a result each time manageable bottles are involved.

Upvotes: 2

Views: 138

Answers (1)

Jack
Jack

Reputation: 133629

The problem derives from object slicing that occurs when invoking

Foo const operator+(Foo f, Foo const & g) {
  return f += g;
}

Here, f is passed by value, which means that any additional information of subtypes of Foo are discarded. So the compiler just sees a Foo and is not able to call the polymorphic operator.

To prevent slicing you are forced to pass a pointer or a reference, but this would imply that you need an l-value as first operand and you can't use const because you are calling operator+= on it.

So you could have

Foo const operator+(Foo& f, Foo const & g) {
  return f += g;
}

and it would work for your specific situation like:

Foo bar;
Bar crazy(bar);
std::cout <<  crazy + bar << std::endl;

Because crazy is an l-value but you won't be able to do Bar(5) + horse nor Foo(5) + horse.

Upvotes: 1

Related Questions