perencia
perencia

Reputation: 1552

function overloading and parameter inheritance

In the following code

#include <iostream>

using namespace std;

class A {

};

class B : public A {

};

class C {
public:
  void foo(const A& a) { cout << "A";}
  void foo(const B& b) { cout << "B";}
};
int main() {
    C c;
    const A& o = B();
    c.foo(o);
  
}

I'd like the result to be "B", instead it is "A". Is there any way that I could call void foo(const B& b) without resorting to dynamic_cast ?

EDIT: What I want to achieve is for the different functions to obtain different resources from C (i.e some member variables). I could do use virtual functions on A and B and do something like this

void foo(const A& o) {
  o.bar(this)
}

and then A or B would gather the resources from C, but I do not want for A or B to know about C.

Upvotes: 0

Views: 166

Answers (3)

Jarod42
Jarod42

Reputation: 217255

Easy way is indeed virtual methods in A:

struct A {
  virtual ~A() = default;
  virtual void foo(const C& c) { std::cout << "A" << c.n;}
};

struct B : public A {
  void foo(const C& c) override { std::cout << "B" << c.n;}
};

struct C {
  int n = 42;
  void foo(const A& a) { a.foo(*this); }
};

int main() {
    C c;
    const A& o = B();
    c.foo(o);
}

But indeed, then, A/B know C.

You might reverse dependency with visitor pattern (but then visitor should know the whole hierarchy):

struct A;
struct B;

struct IVisitor
{
    virtual ~IVisitor() = default;
    virtual void visit(A&) = 0;
    virtual void visit(B&) = 0;
};

struct A {
  virtual ~A() = default;
  virtual void accept(IVisitor& v) { v.visit(*this); }
};

struct B : public A {
  void accept(IVisitor& v) override { v.visit(*this); }
};

class C {
public:
  void foo(const A& a) { cout << "A";}
  void foo(const B& b) { cout << "B";}
};

struct Visitor : IVisitor
{
    C& c;
    void visit(A&) override { c.foo(a); }
    void visit(B&) override { c.foo(b); }
};

int main() {
    C c;
    const A& o = B();
    o.accept(Visitor{c});
}

Since C++17, std::variant might help for the dispatch, as it provide std::visit:

struct A {};
struct B {}; // inheritance no longer required.
             // If you keep inheritance,
             // virtual std::varaint<A*, B*> to_variant() { return this; }
             // might be useful

using A_or_B = std::variant<A, B>;

class C {
public:
  void foo(const A& a) { cout << "A";}
  void foo(const B& b) { cout << "B";}
};

int main()
{
    C c;
    const A_or_B o = B();
    std::visit([&c](auto& a_or_b) { c.foo(a_or_b); }, o);
}

Upvotes: 1

RoQuOTriX
RoQuOTriX

Reputation: 3001

Your goal is this:

What I want to achieve is for the different functions to obtain different resources from C (i.e some member variables).

This is a typical application of the visitor pattern without using dynamic_casts. Keep in mind that this only works with pointers:

#include <iostream>

using namespace std;

class A;
class B;

class C 
{
public:
    void foo(const A& a) { cout << "A";}
    void foo(const B& b) { cout << "B";}
};

struct Visitor
{
    Visitor(C& c) : _C{c} {}   

    void Visit(A* a)
    {
        _C.foo(*a);
    }

    void Visit(B* b)
    {
        _C.foo(*b);
    }

private:
    C& _C;
};

class A 
{
public:
    virtual void Accept(Visitor& visitor)
    {
        visitor.Visit(this);
    }
};

class B : public A 
{
public:
    void Accept(Visitor& visitor) override
    {
        visitor.Visit(this);
    }
};

int main() 
{
    C c;
    A* a = new B();
    Visitor v(c);
    a->Accept(v);
}

Upvotes: 1

In a comment you said

why it cannot know? The compiler knows that o is a subclass of B.

The phrasing here contains an inherent misunderstanding. o is not a subclass of B, but rather a reference.

As far as references are concerned, the C++ type system doesn't bother itself much with the circumstances of a reference's initialization. It operates under the simple assumption that a reference is bound to a valid object of a known type. And in your case, it is. It's bound to an A sub-object of a lifetime extended B.

After that, the id-expression o is always going to be an lvalue of type A const. That's how the type system works, and that's why overload resolution is always going to prefer the overload that print's A. All expressions have a static type.

To achieve a similar effects to what you are after, we employ design patterns around dynamic dispatch. Notable of those are the visitor pattern and the non-virtual interface pattern (AKA "template method" outside the C++osphere).

Upvotes: 2

Related Questions