roro
roro

Reputation: 940

Trying to downcast with polymorphism, what's going wrong?

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

Answers (3)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

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

live example.

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

AnT stands with Russia
AnT stands with Russia

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

Edward Strange
Edward Strange

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

Related Questions