Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Slicing and operator overloading in C++

Background Info

I've been programming in Java for a while, and I've only switched over to C++ just a few months ago, so I apologize if the answer is just something silly that I missed! Now that that's all been said, it is time for the issue at hand! I am developing a basic text-based game engine, and I recently ran into an interestingly specific and unlikely problem. I tried testing it out on a smaller scale in the program below, and decided to just show that (as opposed to my actual game code), as to not choke the screen, and make the issue less convoluted. The problem modeled below mirrors the problem with my actual code, just without the fluffy distractors.

The Problem

Essentially the problem is one of polymorphism. I want to overload the output operator "<<" to serve as a display function that is unique for each object in the hierarchy. The issue is that when I call this operator from a list which stores these hierarchy members, they lose their identity and call the output operator of the base class. Normally, one would solve this by replacing the operator overloads with a simple display method, marking the display method virtual, and moving on with their happy day. I don't particularly mind making that alteration to the code, but now I'm just plain curious. Is there a way to overload operators in a hierarchy that results in what I'm going for here?

The [Example] Code

#include <vector>
#include <iostream>
#include <string>

using namespace std;

class Polygon {
    friend ostream& operator<<(ostream& os, const Polygon& p);
public:

private:

};


class Rectangle : public Polygon {
    friend ostream& operator<<(ostream& os, const Rectangle& r);
public:

private:

};


class Square : public Rectangle {
    friend ostream& operator<<(ostream& os, const Square& s);
public:

private:

};

ostream& operator<<(ostream& os, const Polygon& p) {
    os << "Polygon!" << endl;
    return os;
}
ostream& operator<<(ostream& os, const Rectangle& r) {
    os << "Rectangle!" << endl;
    return os;
}
ostream& operator<<(ostream& os, const Square& s) {
    os << "Square!" << endl;
    return os;
}


int main() {
    vector<Polygon*> listOfPoly;
    listOfPoly.push_back(new Polygon());
    listOfPoly.push_back(new Rectangle());
    listOfPoly.push_back(new Square());

    for(Polygon* p : listOfPoly) {
        cout << *p;
    }
}

Output for [Example] Code

Polygon!
Polygon!
Polygon!

Desired output for [Example] Code

Polygon!
Rectangle!
Square!
like image 998
gen220 Avatar asked Nov 19 '14 19:11

gen220


1 Answers

Is there a way to overload operators in a hierarchy that results in what I'm going for here?

No.

The problem is, the operators aren't in your hierarchy. The friend keyword here just forward-declares a free function and gives it privileged access to the class. It doesn't make it a method, so it can't be virtual.


Note that operator overloading is just syntactic sugar. The expression

os << shape;

can either resolve to the free function (as you have here)

ostream& operator<< (ostream&, Polygon&);

or a member of the left-hand operand, such as

ostream& ostream::operator<<(Polygon&);

(obviously here the second case doesn't exist, because you'd have to modify std::ostream). What the syntax can't resolve to is a member of the right-hand operand.

So, you can have a free function operator (which is necessarily non-virtual), or a method on the left hand operand (which could be virtual), but not a method on the right operand.


The usual solution is to have a single overload for the top-level of the hierarchy which dispatches to a virtual method. So:

class Polygon {
public:
  virtual ostream& format(ostream&);
};

ostream& operator<<(ostream& os, const Polygon& p) {
    return p.format(os);
}

Now just implement Polygon::format, and override it in the derived classes.


As an aside, using friend carries a code smell anyway. In general it's considered better style to give your class a public interface complete enough that external code doesn't need privileged access to work with it.

Further digression for background info: multiple dispatch is a thing, and C++ overload resolution handles it fine when all the argument types are known statically. What isn't handled is discovering the dynamic type of each argument at runtime, and then trying to find the best overload (which, if you consider multiple class hierarchies, is obviously non-trivial).

If you replace your runtime-polymorphic list with a compile-time polymorphic tuple and iterate over that, your original overloading scheme would dispatch correctly.

like image 184
Useless Avatar answered Oct 16 '22 00:10

Useless