0

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.

10
  • Overloading does nothing hear. Look towards virtual functions. Commented Mar 31, 2021 at 6:48
  • Okay, firstly, the behavior is undefined because const A& o = B(); stores a reference to a destroyed object. Secondly, the type of o is only known to be an instance of A or some subclass. It cannot know that o actually refers to a B instance. Commented Mar 31, 2021 at 6:49
  • 4
    @paddy - Nothing undefined. This falls under the cases of reference lifetime extension. Commented Mar 31, 2021 at 6:52
  • @paddy why it cannot know? The compiler knows that o is a subclass of B. Commented Mar 31, 2021 at 6:55
  • And I'm guessing reference lifetime extension is what makes the OP want that c.foo(o); would print B? The fact the compiler must do the right thing (TM) to extend the temporary B's lifetime? Commented Mar 31, 2021 at 6:56

3 Answers 3

2

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).

Sign up to request clarification or add additional context in comments.

Comments

1

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);
}

1 Comment

You should make your visitor an interface, so A/B really doesn't know C (even though Visitor).
1

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);
}

1 Comment

Very interesting.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.