Reputation: 940
I have two pointers to a base class, one points to an actual base object and the other to a derived object. I also have a nonmember function that is overloaded for the base and derived classes. I'd like to use polymorphism to downcast the pointers so that the correct overload is called.
The output of the code is
base downcast called
base
derived downcast called
base
however the desired output is
base
derived
Can someone explain the output, and what must be done to get the desired behavior?
#include <iostream>
using namespace std;
class base
{
public:
virtual base& downcast()
{
cout << "base downcast called" << endl;
return *this;
}
};
class derived: public base
{
public:
virtual derived& downcast()
{
cout << "derived downcast called" << endl;
return *this;
}
};
void foo(const base& a)
{
cout << "base" << endl;
}
void foo(const derived& a)
{
cout << "derived" << endl;
}
int main()
{
base* ptr1 = new(base);
base* ptr2 = new(derived);
foo(ptr1->downcast());
foo(ptr2->downcast());
return 0;
}
EDIT: added cout to the downcast functions to illustrate function override/polymorphism.
Upvotes: 0
Views: 199
Reputation: 275966
So we can do this in a somewhat generic way.
template<class...>struct types{};
template<std::size_t I>using index=std::integral_constant<std::size_t, I>;
template<class T, class types>
struct get_index_of_type;
template<class T, class...Ts>
struct get_index_of_type<T, types<T,Ts...>>:
index<0>
{};
template<class T, class U, class...Ts>
struct get_index_of_type<T, types<U,Ts...>>:
index<get_index_of_type<T, types<Ts...>>{}+1>
{};
template<class R, class Types>
struct dynamic_dispatch;
template<class R, class...Ts>
struct dynamic_dispatch<R, types<Ts...>>
{
using fptr = R(*)(void const* pf, void* t);
template<class F>
std::array<fptr, sizeof...(Ts)>
make_table() const {
return {{
+[](void const* pf, void* t)->R{
auto* pt = static_cast< std::remove_reference_t<Ts>* >(t);
auto* f = static_cast< std::remove_reference_t<F> const* >(pf);
return (*f)(static_cast<Ts&&>(*pt));
}...
}};
}
void const* pf = nullptr;
std::array<fptr, sizeof...(Ts)> table;
dynamic_dispatch( dynamic_dispatch&& )=default;
dynamic_dispatch( dynamic_dispatch const& )=default;
dynamic_dispatch& operator=( dynamic_dispatch&& )=default;
dynamic_dispatch& operator=( dynamic_dispatch const& )=default;
template<class F,
std::enable_if_t< !std::is_same<std::decay_t<F>, dynamic_dispatch>{}, int> =0
>
dynamic_dispatch( F&& f ):
pf(std::addressof(f)),
table( make_table<std::decay_t<F>>() )
{}
template<class T>
R operator()( T&& t ) const {
return table[get_index_of_type<T,types<Ts...>>{}]( pf, std::addressof(t) );
}
};
dynamic_dispatch<R, types<a,b,c>>
takes any callable that can be invoked with any of a
, b
or c
(exactly type, including l/r value and const required, so make your list verbose. No implicit casts are done; this can be fixed with more work).
Now, add a method in base
called apply
:
virtual void apply( dynamic_dispatch<void, types<base*, derived*>> f ) {
return f(this);
}
override it in derived:
virtual void apply( dynamic_dispatch<void, types<base*, derived*>> f ) override {
return f(this);
}
with the same body.
Now in main:
auto super_foo = [](auto* x) {
return foo(*x);
};
int main()
{
base* ptr1 = new(base);
base* ptr2 = new(derived);
ptr1->apply(super_foo);
ptr2->apply(super_foo);
}
For further reading, I type erased dynamic dispatch to a list of types on a function object. I created a view to this dispatch.
super_foo
is a single object representing the entire overload set of foo
, permitting it to be passed as one parameter.
This can also be done more conventionally with a visitor pattern:
struct visitor {
void invoke( base* ) const = 0;
void invoke( derived* ) const = 0;
};
then you implement a foo_visitor
:
struct foo_visitor:visitor {
void invoke( base* a ) const {return foo(*a);}
void invoke( derived* a ) const {return foo(*a);}
};
and we write an apply
that takes a visitor&
and does .invoke(this)
on it.
Note that this technique, especially if you make it smartly choose between the Ts
instead of requiring exact match, allows multiple dispatch polymorphism through recursion (or, you could change the dynamic_dispatch
to dynamic_dispatch< R, types... >
with multiple type packs).
Upvotes: 0
Reputation: 320777
You are basically trying to make run-time polymorphism to affect compile-time overload resolution. This is not possible for obvious reasons. Function overloading is a compile-time feature, meaning that overload resolution is performed at compile time based on static types of function arguments.
In your case the choice of which foo
to call is based on static typization: on static types of ptr1
and ptr2
and on static types of ptr1->downcast()
and ptr2->downcast()
return values. The latter are lvalues (references) of type base
in both cases. There's no polymorphism involved in choosing foo
. The compiler does not know (and does not care) that at run-time one of these base &
references will actually refer to a derived
object.
However, if you could call a.downcast()
from foo
, you would observe that rum-time polymorphism still works from inside foo
. (At this time such call is not possible due to downcast()
being non-const)
Upvotes: 4
Reputation: 40895
You need dynamic_cast. What you're trying to do won't work.
C++ allows covariant return values on overridden functions. Since derived
inherits from base
it qualifies. But if you call the base's version of the function you still get the type IT returns, not the derived class's version. To get that return type you must have already casted so that the compiler knows what type the function returns.
Upvotes: 1