David Feurle David Feurle - 1 month ago 17
C++ Question

Dynamic dispatch in C++ - better syntax

I want to have a dynamic_call functionality in C++. It should trigger overload resolution and call the most specific function on the target class (Visitor) depending on the dynamic type of the argument. It should replace the visitor pattern and should work like the dynamic keyword in C#.
I pasted what I got so far below. I want to not have to declare the generic lambda on caller side but on the implementation of dynamic call to make it easier to use. Is this possible?

#include <iostream>

struct Base { virtual ~Base() = default; };
class A : public Base {};
class B : public Base {};

template <class... Ts>
class dynamic_call {
public:
template <class F, class Arg>
static void call(F& func, Arg& a) {
call_impl<F, Arg, Ts...>(func, a);
}
private:
template <class F, class Arg>
static void call_impl(F& /*func*/, Arg& /*a*/) {
//end of recursion => nothing more to be done
}
template <class F, class Arg, class T, class... R>
static void call_impl(F& func, Arg& a) {
T* t = dynamic_cast<T*>(&a);
if(t) {
func(*t);
}
call_impl<F, Arg, R...>(func, a);
}
};

using namespace std;
struct Visitor {
void Visit(A&) { cout << "visited for a" << endl; }
void Visit(B&) { cout << "visited for b" << endl; }
};

int main(int /*argc*/, char */*argv*/[])
{
Visitor v;
auto func = [&v](auto& a) { v.Visit(a); };

A a;
dynamic_call<A, B>::call(func, a);

B b;
dynamic_call<A, B>::call(func, b);

{
Base& base(a);
dynamic_call<A, B>::call(func, base);
}
{
Base& base(b);
dynamic_call<A, B>::call(func, base);
}

return 0;
}


I want to call it like this without the need to add the generic lambda.

dynamic_call<A,B>::call(v, a);

Answer

Here are some ideas, I am not sure what your requirements are, so they might not fit:

  1. Change Visit into operator(). Then the call syntax reduces to dynamic_call<A,B>::call(v, a); as you required. Of course that is only possible if the interface of the visitor may be changed.

  2. Change func(*t) in call_impl to func.Visit(*t). Then again the caller can use dynamic_call<A,B>::call(v, a); and no change to the interface of the visitor is necessary. However every visitor used with dynamic_call now needs to define Visit as visitor method. I think operator() is cleaner and follows the usual patterns, e.g. for Predicates in the standard library more.

I don't particularly like either of these because the caller always has to know the possible overloads available in the visitor and has to remember using dynamic_call. So I propose to solve everything in the visitor struct:

  1. struct Visitor {
        void Visit(A&) {
            cout << "visited for a" << endl;
        }
        void Visit(B&) {
            cout << "visited for b" << endl;
        }
        void Visit(Base& x) {
            dynamic_call<A,B>::call([this](auto& x){Visit(x);}, x);
        }
    };
    

    This can be called with v.Visit(a), v.Visit(b) and v.Visit(base). This way the user of Visitor does not need to know anything about the varying behavior for different derived classes.

  2. If you do not want to modify Visitor, then you can just add the overload via inheritance:

    struct DynamicVisitor : Visitor {
        void Visit(Base& x) {
            dynamic_call<A,B>::call([this](auto& x){Visit(x);}, x);
        }
    };
    

The points can be combined, for example into:

struct Visitor {
    void operator()(A&) {
        cout << "visited for a" << endl;
    }
    void operator()(B&) {
        cout << "visited for b" << endl;
    }
    void operator()(Base& x) {
        dynamic_call<A,B>::call(*this, x);
    }
};

Used as v(a), v(b) and v(base).

Comments