Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to achieve dynamic polymorphism (run-time call dispatch) on unrelated types?

GOAL:

I would like to achieve type-safe dynamic polymorphism (i.e. run-time dispatch of a function call) on unrelated types - i.e. on types which do not have a common base class. It seems to me that this is achievable, or at least theoretically sound. I will try to define my problem more formally.

PROBLEM DEFINITION:

Given the following:

  • two or more unrelated types A1, ..., An, each of which has a method called f, possibly with different signatures, but with the same return type R; and
  • a boost::variant<A1*, ..., An*> object v (or whatever other type of variant) which can and must assume at any time one value of any of those types;

My goal is to write instructions conceptually equivalent to v.f(arg_1, ..., arg_m); that would get dispatched at run-time to function Ai::f if the actual type of the value contained in v is Ai. If the call arguments are not compatible with the formal parameters of each function Ai, the compiler should raise an error.

Of course I do not need to stick to the syntax v.f(arg_1, ..., arg_m): for instance, something like call(v, f, ...) is also acceptable.

I tried to achieve this in C++, but so far I have failed to come up with a good solution (I do have a bunch of bad ones). Below I clarify what I mean by "good solution".

CONSTRAINTS:

A good solution is anything that lets me mimic the v.f(...) idiom, e.g. call_on_variant(v, f, ...);, and satisfies the following constraints:

  1. does not require any sort of separate declaration for each function f that must be called this way (e.g. ENABLE_CALL_ON_VARIANT(f)) or for any list of unrelated types A1, ..., An that can be treated polymorphically (e.g. ENABLE_VARIANT_CALL(A1, ..., An)) somewhere else in the code, especially on global scope;
  2. does not require to explicitly name the types of the input arguments when doing the call (e.g. call_on_variant<int, double, string>(v, f, ...)). Naming the return type is OK, so for instance call_on_variant<void>(v, f, ...) is acceptable.

Follows a demonstrative example that hopefully clarifies my wish and requirements.

EXAMPLE:

struct A1 { void f(int, double, string) { cout << "A"; } };
struct A2 { void f(int, double, string) { cout << "B"; } };
struct A3 { void f(int, double, string) { cout << "C"; } };

using V = boost::variant<A1, A2, A3>;

// Do not want anything like the following here:
// ENABLE_VARIANT_CALL(foo, <whatever>)

int main()
{
    A a;
    B b;
    C c;

    V v = &a;
    call_on_variant(v, f, 42, 3.14, "hello");

    // Do not want anything like the following here:
    // call_on_variant<int, double, string>(v, f, 42, 3.14, "hello");

    V v = &b;
    call_on_variant(v, f, 42, 3.14, "hello");

    V v = &c;
    call_on_variant(v, f, 42, 3.14, "hello");
}

The output of this program should be: ABC.

BEST (FAILED) ATTEMPT:

The closest I got to the desired solution is this macro:

#define call_on_variant(R, v, f, ...) \
[&] () -> R { \
    struct caller : public boost::static_visitor<void> \
    { \
        template<typename T> \
        R operator () (T* pObj) \
        { \
            pObj->f(__VA_ARGS__); \
        } \
    }; \
    caller c; \
    return v.apply_visitor(c); \
}();

Which would work perfectly, if only template members were allowed in local classes (see this question). Does anybody have an idea how to fix this, or suggest an alternative approach?

like image 804
Andy Prowl Avatar asked Jan 11 '13 23:01

Andy Prowl


People also ask

What is dynamic polymorphism explain dynamic method dispatch on the basis of following example?

Dynamic polymorphism is a process or mechanism in which a call to an overridden method is to resolve at runtime rather than compile-time. It is also known as runtime polymorphism or dynamic method dispatch. We can achieve dynamic polymorphism by using the method overriding.

How does dynamic method dispatch help in implementing polymorphism?

Method overriding is one of the ways in which Java supports Runtime Polymorphism. Dynamic method dispatch is the mechanism by which a call to an overridden method is resolved at run time, rather than compile time.

How dynamic method dispatch is achieved in Java elaborate it?

Dynamic method dispatch is the mechanism in which a call to an overridden method is resolved at run time instead of compile time. This is an important concept because of how Java implements run-time polymorphism.

Which dispatching method is used to implement subtype polymorphism?

There are two forms of dispatch, static and dynamic . The former means that a call to a method is resolved at compile time and the latter means that is resolved at run time. Dynamic dispatch is the mechanism that allows polymorphic operations.


2 Answers

Some time has passed, C++14 is being finalized, and compilers are adding support for new features, like generic lambdas.

Generic lambdas, together with the machinery shown below, allow achieving the desired (dynamic) polymorphism with unrelated classes:

#include <boost/variant.hpp>

template<typename R, typename F>
class delegating_visitor : public boost::static_visitor<R>
{
public:
    delegating_visitor(F&& f) : _f(std::forward<F>(f)) { }
    template<typename T>
    R operator () (T x) { return _f(x); }
private:
    F _f;
};

template<typename R, typename F>
auto make_visitor(F&& f)
{
    using visitor_type = delegating_visitor<R, std::remove_reference_t<F>>;
    return visitor_type(std::forward<F>(f));
}

template<typename R, typename V, typename F>
auto vcall(V&& vt, F&& f)
{
    auto v = make_visitor<R>(std::forward<F>(f));
    return vt.apply_visitor(v);
}

#define call_on_variant(val, fxn_expr) \
    vcall<int>(val, [] (auto x) { return x-> fxn_expr; });

Let's put this into practice. Supposing to have the following two unrelated classes:

#include <iostream>
#include <string>

struct A
{
    int foo(int i, double d, std::string s) const
    { 
        std::cout << "A::foo(" << i << ", " << d << ", " << s << ")"; 
        return 1; 
    }
};

struct B
{
    int foo(int i, double d, std::string s) const
    { 
        std::cout << "B::foo(" << i << ", " << d << ", " << s << ")"; 
        return 2;
    }
};

It is possible to invoke foo() polymorphically this way:

int main()
{
    A a;
    B b;

    boost::variant<A*, B*> v = &a;
    auto res1 = call_on_variant(v, foo(42, 3.14, "Hello"));
    std::cout << std::endl<< res1 << std::endl;

    v = &b;
    auto res2 = call_on_variant(v, foo(1337, 6.28, "World"));
    std::cout << std::endl<< res2 << std::endl;
}

And the output is, as expected:

A::foo(42, 3.14, Hello)
1
B::foo(1337, 6.28, World)
2

The program has been tested on VC12 with November 2013's CTP. Unfortunately, I do not know of any online compiler that supports generic lambdas, so I cannot post a live example.

like image 67
Andy Prowl Avatar answered Oct 23 '22 13:10

Andy Prowl


OK, here's a wild shot:

template <typename R, typename ...Args>
struct visitor : boost::static_visitor<R>
{
    template <typename T>
    R operator()(T & x)
    { 
        return tuple_unpack(x, t);   // this needs a bit of code
    }

    visitor(Args const &... args) : t(args...) { }

private:
    std::tuple<Args...> t;
};

template <typename R, typename Var, typename ...Args>
R call_on_variant(Var & var, Args const &... args)
{
    return boost::apply_visitor(visitor<R, Args...>(args...), var);
}

Usage:

R result = call_on_variant<R>(my_var, 12, "Hello", true);

I've hidden a certain amount of work you need for calling a function by unpacking a tuple, but I believe this has been done elsewhere on SO.

Also, if you need to store references rather than copies of the arguments, this can possibly be done, but needs more care. (You can have a tuple of references. But you have to think about whether you also want to allow temporary objects.)

like image 38
Kerrek SB Avatar answered Oct 23 '22 14:10

Kerrek SB