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:
A1, ..., An
, each of which has a method called f
, possibly with different signatures, but with the same return type R
; andboost::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:
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;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?
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.
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.
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.
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.
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.
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.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With