0

I'd like to store a child object in a container of its parent type, and then call a function overload based on the type of child in the container. Is that possible?

#include <vector>

class Parent { public: };

class A : public Parent { public: };
class B : public Parent { public: };
class C : public Parent { public: };

class Hander
{
public:
    static void handle(A & a) {}
    static void handle(B & b) {}
    static void handle(C & c) {}
};

int main()
{
    A test1;
    Hander::handle(test1); // compiles and calls the correct overload

    Parent test2 = A();
    Hander::handle(test2); // doesn't compile

    Parent * test3 = new A();
    Hander::handle(*test3); // doesn't compile

    Parent children1[] = { A(), B(), C() };
    for (int i = 0; i < 3; ++i)
        Hander::handle(children1[i]); // doesn't compile

    std::vector<Parent*> children2 = { new A(), new B(), new C() };

    for (int i = 0; i < 3; ++i)
        Hander::handle(*children2[i]); // doesn't compile
}
Jim V
  • 1,131
  • 12
  • 22
  • 4
    read up on polymorphism and object slicing. – NathanOliver Mar 01 '16 at 15:49
  • 2
    **First:** I think `Parent test2 = A()` doesn't do what you think. In *C++* polymorphism exists for pointer and references only. `test2` has type `Parent` and is constructed from `A`, but it is not of type `A` anymore. **Second:** Overloaded functions are chosen from compiler. If you want to call the right function, you have to check manually which type it is. But you may interested in [*virtual functions*](http://stackoverflow.com/q/2391679/4967497). With virtual function you can make `handle` a member of `A`, `B` and `C` and it will always call the right function automatically. – JojOatXGME Mar 01 '16 at 15:55
  • @JojOatXGME I am aware of the ability to manually check the type (using a smart pointer cast), but I was under the impression that is an anti-pattern. Is this correct? – Jim V Mar 01 '16 at 15:59
  • As I understand, a virtual method tells the compiler to favor the overload farthest down the inheritance hierarchy in exchange for a slight performance hit. Is this correct or is there more to it? – Jim V Mar 01 '16 at 16:00
  • "With virtual function you can make handle a member of A, B and C and it will always call the right function automatically." So A, B, and C would implement the handle method? – Jim V Mar 01 '16 at 16:03
  • 1
    @JimV Yes, I think it could be explained like this. Non-virtual member functions are just functions with an implicit parameter `this`. This means that you have the same rules as for overloading. But virtual functions are choosen dynamically. If `A` has a virtual function, it stores a reference to the function in a *function table*. If you override this function in `B : A`, objects of `B` stores a referene to it's own implementation in it's function table at the same index. This way the compiler just call the function referenced in the table and get the right one every time. – JojOatXGME Mar 01 '16 at 16:10

1 Answers1

1

No, it is not possible.

The function which is called is chosen at compile-time. Lets say you have code like this:

Base &o = getSomeObject();
handle(o);

The compiler doesn't know the real type of o. It only knows that it is some subtype of Base or Base itself. This mean it will search for a function which acceppts objects of type Base.

You could implement a check for the type yourself or use a map to store possible functions:

Base &o = getSomeObject();
functionMap[typeid(o)](o);

But typeid does only work this whay if Base is a polymorphic type. This mean it must have at least one virtual function. This brings us to the next section:

But you could use virtual functions.

Virtual functions are non-static member functions of classes which can be overridden. The right function is resolved at runtime. The following code would output Subt instead of Base:

class Base {
public: virtual std::string f() {return "Base"}
};
class Subt : public Base {
public: virtual std::string f() {return "Subt"}
};

int main() {
    Subt s;
    Base &b = s;
    std::cout << b.f() << std::endl;
}

You can omit virtual in the definition of Subt. The function f() is already defined as virtual in it's base class.

Classes with at least one virtual function (also called polymorphic types) are storing a reference to a virtual function table (also called vtable). This table is used to get the right function at runtime.

The problem in your question could be solved like this:

class Parent {
public:
    virtual void handle() = 0;
};

class A : public Parent {
public:
    void handle() override { /* do something for instances of A */ }
};
class B : public Parent {
public:
    void handle() override { /* do something for instances of B */ }
};
class C : public Parent {
public:
    void handle() override { /* do something for instances of C */ }
};

int main()
{
    std::vector<std::unique_ptr<Parent>> children = {
            std::make_unique<A>(),
            std::make_unique<B>(),
            std::make_unique<C>()};

    for (const auto &child : children)
        child->handle();
}

Note about compatibility: The keywords auto and override are only available in C++11 and above. The range-based for loop and std::unique_ptr is also available since C++11. The function std::make_unique is available since C++14. But virtual function can be used with older versions, too.

Another hint:

Polymorphism does only work with references and pointers. The following would call Base::f() and not Subt::f():

Subt s;
Base b = s;
std::cout << b.f() << std::endl;

In this example b will just contain a object of type Base instead of Subt. The object is just created at Base b = s;. It may copy some information from s but it isn't s anymore. It is a new object of type Base.

JojOatXGME
  • 3,023
  • 2
  • 25
  • 41
  • Thanks so much for the in-depth answer! This explains everything I'm looking for and more. And yes, I always use managed pointers unless I'm providing a bare-bones example for SO. – Jim V Mar 02 '16 at 20:00