Reputation: 1552
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
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
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
Reputation: 170064
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