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 calledf
, possibly with different signatures, but with the same return typeR
; and - a
boost::variant<A1*, ..., An*>
objectv
(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:
- 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 typesA1, ..., An
that can be treated polymorphically (e.g.ENABLE_VARIANT_CALL(A1, ..., An)
) somewhere else in the code, especially on global scope; - 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 instancecall_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?