3

I have the following struct inheritance:

struct Base
{
  Base(int a, int b)
    : m_a(a), m_b(b)
  {
    // empty
  }

  int m_a;
  int m_b;
};

struct ChildOne: public Base
{
  ChildOne(int a, int b)
    : Base(a, b)
  {
    // empty
  }
};

struct ChildTwo: public Base
{
  ChildTwo(int a, int b)
    : Base(a, b)
  {
    // empty
  }
};

If I want to have an overloaded function, one that handles the case when a ChildOne struct is passed, and one for ChildTwo, how would I implement that?

What I'm currently trying: In hpp:

class MyClass
{
  void foo(Base s);
}

In cpp:

void Myclass::foo(ChildOne s)
{
  //Do things specific for ChildOne
}

void MyClass::foo(ChildTwo s)
{
  //Do things specific for ChildTwo
}

If I understand inheritance correctly, ChildOne and ChildTwo are both Base. I should be able to pass ChildOne to this foo function because ChildOne IS a Base. But this doesn't compile for me, saying it can't find a find a matching prototype in MyClass (but it does recognize foo(Base) as a potential candidate). What am I misunderstanding about overloading functions and inheritance?

note: The reason I have a base class with empty children inheriting it was so I could have a std::queue<Base> that would only hold ChildOne or ChildTwo structs, no Base structs. This works beautifully. It's calling an overloaded function to handle the front item of the queue (whether it's a ChildOne or ChildTwo) that's the problem.

cjameston
  • 199
  • 3
  • 11
  • Note that it doesn't matter if you have "struct inheritance" or "class inheritance". It is all the same in C++. – juanchopanza Aug 25 '15 at 22:50
  • 2
    "so I could have a `std::queue` that would only hold `ChildOne` or `ChildTwo` structs, no `Base` structs"... except that you are falling victim to **object slicing**. When you want polymorphism, you need to use a pointer or reference, not pass by value. – Ben Voigt Aug 25 '15 at 23:04

2 Answers2

4

C++ doesn't take inheritance into account in cases like matching function definition with existing declaration. You have declared void foo(Base s); and only that should be defined. But since you need overloads, you should have two declarations instead:

void foo(ChildOne s);
void foo(ChildTwo s);

Additionally, you can have void foo(T s); overload, where T is a pointer or a reference to Base or const Base. This overload will be called when passing Base or derived classes different to ChildOne and ChildTwo.

EDIT:

After reading the note part which I skipped over, there's no way of making the above work with elements of std::queue<Base>, which are all Bases. If you try to push in ChildOne or ChildTwo, the object gets sliced.

If you had std::queue<Base*> or std::queue<std::shared_ptr<Base>> which support polymorphism, *myQueue.front() would still try to match the overload taking Base as an argument. Nonetheless, you should take a look at polymorphism, because I think that's what should be used here. I hope this example will make you interested if you're not already.

#include <iostream>
#include <queue>
using std::cout;
using std::endl;

struct Base
{
    Base(int a, int b) : m_a(a), m_b(b) {}
    virtual ~Base() = default;

    virtual void doSomething() = 0; // pure virtual (must be defined by deriving classes)

    int m_a;
    int m_b;
};

struct ChildOne : public Base
{
    ChildOne(int a, int b) : Base(a, b) {}

    virtual void doSomething() override
    {
        cout << "ChildOne doing something" << endl;
    }
};

struct ChildTwo : public Base
{
    ChildTwo(int a, int b) : Base(a, b) {}

    virtual void doSomething() override
    {
        cout << "ChildTwo doing something" << endl;
    }
};

class MyClass
{
public:
    void foo(Base *b)
    {
        b->doSomething(); // polymorphism will take care of calling the right function
    }
};

int main()
{
    MyClass c;
    std::queue<Base*> queue;
    queue.push(new ChildOne(1, 2));
    queue.push(new ChildTwo(1, 2));
    queue.push(new ChildTwo(1, 2));
    queue.push(new ChildOne(1, 2));

    while(!queue.empty())
    {
        c.foo(queue.front());
        delete queue.front();
        queue.pop();
    }
}

Output (Coliru):

ChildOne doing something
ChildTwo doing something
ChildTwo doing something
ChildOne doing something
Community
  • 1
  • 1
LogicStuff
  • 19,397
  • 6
  • 54
  • 74
  • @Neil Kirk In practice, I would of course. But I chose to not complicate things and have that one `delete` in place. – LogicStuff Aug 26 '15 at 00:43
  • @LogicStuff Thanks for the heads up about slicing, I had never heard of that before. I could have sworn this was a thing you could do in C++, but maybe that was a different language. Thank you for your reply, I'll try playing around with your example a bit. – cjameston Aug 26 '15 at 17:16
0

If you want to have one member function for ChildOne and ChildTwo you also have to declare zwo memberfunctions (one for childOne anf one for ChildTwo).

As far as functio overloading is concerned, it is completely irrelevant that e.g. ChildOne is derived from Base. Function declaration and definition have to match exactly.

MikeMB
  • 20,029
  • 9
  • 57
  • 102