14

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?

Community
  • 1
  • 1
Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • 1
    Sounds like a job for type erasure. Is that acceptable? – Kerrek SB Jan 11 '13 at 23:07
  • @KerrekSB: indeed, i tried pretty much everythin i know, but with no success so far – Andy Prowl Jan 11 '13 at 23:09
  • Somehow it looks to me like it wouldn't be possible, but I'd love to see otherwise. – user541686 Jan 11 '13 at 23:10
  • Are the number and types of the function parameters fixed? – Kerrek SB Jan 11 '13 at 23:10
  • @Mehrdad: i agree with the feeling. i've been trying quite hard, but maybe i'm missing some key technique – Andy Prowl Jan 11 '13 at 23:11
  • @KerrekSB: no, they are not fixed (it is mentioned in the problem definition) – Andy Prowl Jan 11 '13 at 23:11
  • it is really a shame that template members are not supported on local types, that would make it possible – Andy Prowl Jan 11 '13 at 23:12
  • This seems pretty straightforward but I'm not going to try since no doubt Kerrek will beat me to it. – Seth Carnegie Jan 11 '13 at 23:14
  • 2
    Well, C++ is statically typed, so you can't really decide on the number and type of arguments *dynamically*... – Kerrek SB Jan 11 '13 at 23:14
  • 1
    @SethCarnegie: No no, go ahead. I'm not too sure right now. I could do it for a fixed function signature, but I don't think that's what's desired. – Kerrek SB Jan 11 '13 at 23:14
  • I can't help but wonder if what you really need is just argument-dependent lookup, not actually run-time dynamic dispatch. It almost seems like you're trying to emulate ADL. – user541686 Jan 11 '13 at 23:15
  • @KerrekSB: as I mention in the text, the compiler should reject calls whose arguments are not compatible with *all* functions – Andy Prowl Jan 11 '13 at 23:16
  • @KerrekSB: just consider my macro at the end of the question text: if local templates were allowed, that would work even for different signatures (provided the arguments are compatible with *all* those signatures) – Andy Prowl Jan 11 '13 at 23:16
  • But that wouldn't be *dynamic*. And if you want the call to be compatible with *all* functions, doesn't that require them to have the same number of arguments?! – Kerrek SB Jan 11 '13 at 23:17
  • @KerrekSB: it would be dynamic. compatibility would be checked at compile-time, dispatch would be done at run-time – Andy Prowl Jan 11 '13 at 23:18
  • My blurred visualization tells me about a (tuple?) + variadic templates recursion to do the signature checking part. variant to do the dynamic dispatch. but not sure. – oblitum Jan 11 '13 at 23:20
  • @KerrekSB: what i want is: **if** the call is compatible with the signature of all functions, then compile and dispatch the call at run-time; if the call is **not** compatible with all of them (for instance, if they have a different number of arguments) then do not compile. so to be more precise: yes *in order to compile* they have to have the same number of arguments – Andy Prowl Jan 11 '13 at 23:21
  • @chico: feel free to try that out. the hardest problem is in the locality of the definition (i do not want to have separate global declarations, otherwise it's quite easy). i have tried boost::variant a lot for the dynamic dispatch, in fact my closest solution is based on that; but i can't make it work (see my failed attempt at the end of the question text, that's as far as i could go) – Andy Prowl Jan 11 '13 at 23:28
  • I'm somehow feeling that you want something that doesn't make sense. Here's a thought experiment: Suppose we have two functions `R f1(float)` and `R f2(int)`, and constants `int A = INT_MAX` and `float B = 0.5`. Now imagine you ask the user to input two characters like `"A1"` or `"B2"`, and you want to call the respective function with the respective constant. If you write this by hand, you have four different function calls, and two of those cause value-distorting conversions (namely `f1(A)` and `f2(B)`) while two don't. You are somehow asking for a way to generate all this code automatically – Kerrek SB Jan 11 '13 at 23:34
  • @KerrekSB: not sure I am following, maybe i'm missing one step in your analogy. in my scenario, functions `f1` and `f2` are both called `f` and are members of two unrelated classes `A1` and `A2`. i do believe what I am looking for makes sense, i admit however that i might be ambiguous in communicating it – Andy Prowl Jan 11 '13 at 23:38
  • @KerrekSB: just please focus on my macro (failed) attempt at the end of the question text: if local templates were allowed, that macro would do what i am asking. i am looking for something that does the equivalent thing – Andy Prowl Jan 11 '13 at 23:39
  • @KerrekSB: i apologize, my macro text contained a mistake, i edited the question. sorry about that (not sure if that changes things) – Andy Prowl Jan 11 '13 at 23:41
  • Well, you're looking for a magic dynamic container that can hold both `f1` and `f2` and that you can call with `A` or `B`. But the example goes to show that in the most general sense this boils down to generating separate code for all the possible combinations. It's essentially a lot of static dispatch along with a big switch for which function you want. But you don't get around the big switch, if you see what I mean. – Kerrek SB Jan 11 '13 at 23:41
  • The situation would be entirely different if you could fix the number of the function parameters and a common type for each, such that all the individual types could be obtained losslessly from the common type (e.g. `double` in the example above). Then this would be straight-forward with a simple type-erasing wrapper. – Kerrek SB Jan 11 '13 at 23:43
  • @KerrekSB: yes, i think i understand, and indeed i used `boost::variant` and `static_visitor` to get the "big switch" done. and since the content of each "case" of this hypothetical switch has the same form, it can be templated (as i did in my macro). in theory, that's something the compiler could do - it does not break the type system. but it's hard to achieve without separate global declarations. i shall repeat myself, if local templates were allowed that would be possible, and this is not a *conceptual* obstacle – Andy Prowl Jan 11 '13 at 23:44
  • @KerrekSB: I agree, fixing the number of arguments would make it easy (i did that already). i want my `call_on_variant` to be able to invoke functions with any signature (i could accept compromises on the return type though, but that does not help much) – Andy Prowl Jan 11 '13 at 23:46
  • What about an ordinary, free template then, and a type-deducing helper? – Kerrek SB Jan 11 '13 at 23:54
  • @KerrekSB: yes, i tried splitting type resolution and call dispatch, but didn't work. actually I am thinking of a possible solution I haven't considered yet, because i was tacitly focusing on free functions in my exercies rather than methods (which explains why my macro contained a typo) and that makes it harder - i'll try something out with methods, i think it could work – Andy Prowl Jan 11 '13 at 23:58
  • ok, i tried something new and failed again – Andy Prowl Jan 12 '13 at 00:07
  • This looks sort of like what adobe::poly<> does (in a rather different way). Slides here: http://stlab.adobe.com/wiki/images/c/c9/Boost_poly.pdf various other links can be found here: http://stlab.adobe.com/wiki/index.php/Papers_and_Presentations – user673679 Jan 12 '13 at 00:47
  • @user673679: a very interesting reference, thank you! i've been thinking about this kind of stuff on my own quite some time. and it's just true, every time you come up with what seems like a good idea, somebody else had it before you :) – Andy Prowl Jan 12 '13 at 00:52

4 Answers4

7

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.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
4

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.)

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • this incredibly resembles what I've been trying hard the whole day :) and I have the code for tuple unpacking, or at least i had it - i'm afraid i deleted everything. but i would like to see a self-containing example – Andy Prowl Jan 12 '13 at 00:02
  • I've done it once to write a `printf` that takes a tuple, and from what I recall I'm not keen on reliving that. I'm pretty sure someone here has done that before. – Kerrek SB Jan 12 '13 at 00:03
  • @AndyProwl: It has to be coded into the tuple unpack code. [Here is one version](http://stackoverflow.com/a/7858971/596781), and [here is another](http://stackoverflow.com/a/1547118/596781). Ah, I see, the name of the function cannot be templated, because that's outside the language. – Kerrek SB Jan 12 '13 at 00:10
  • actually I feel there is a problem hidden somewhere: `call_on_variant` should also accept a function name, and passing that one to the visitor is the hard part. this is because the visitor knows *which* member function (of which class) to invoke only when the call operator is being invoked, meaning that you should pass just a *name*, and you can do that only with macros – Andy Prowl Jan 12 '13 at 00:11
  • @AndyProwl: Yeah, you're right. You can't make the function *name* a C++ entity: Since the types are unrelated, it's a lexical coincidence that the functions have the same name, but you can't exploit that from within the language. – Kerrek SB Jan 12 '13 at 00:12
  • indeed, that's why i was trying hard with macros. and i want my macro to be local, not in some separate global scope. i almost made it, but template members are not allowed on local classes, which is unfortunate. – Andy Prowl Jan 12 '13 at 00:14
  • i am wondering whether i could try and steal some of Boost.Variant's machinery and inline it inside the macro rather than having a call operator invoked for determining the dynamic type of the object – Andy Prowl Jan 12 '13 at 00:15
  • So I a problem is that the function name is limited to inside the macro, and templates are limited to outside the macro. If you can break one of those (probably the former) then you can do this I think. – Seth Carnegie Jan 12 '13 at 00:17
  • @SethCarnegie: indeed. i believe the macro should contain the machinery of variant visitation without relying on a `static_visitor` object. if that could be inlined inside the macro somehow, it could work – Andy Prowl Jan 12 '13 at 00:19
  • @AndyProwl: Or, if you were just willing to allow *two* non-local lines of code for each function name, you could put the dispatching code in a macro at file scope, separate from the invocation code. – Kerrek SB Jan 12 '13 at 00:22
  • @KerrekSB: yes, I have such a solution already, but even those few lines started to bother me :) (i know, i'm picky) – Andy Prowl Jan 12 '13 at 00:23
  • Shouldn't references be handled with `std::ref`? – GManNickG Jan 12 '13 at 01:05
  • @GManNickG: I think a tuple of references is fine. It's what you get from `std::tie`, too. – Kerrek SB Jan 12 '13 at 01:07
  • @GManNickG: `forward_as_tuple` could solve that, it forwards the arguments perfectly. however, the problem there is more substantial - can't have generic code in local scope, and cannot export a function *name* to generic code – Andy Prowl Jan 12 '13 at 01:14
4

Unfortunately, this cannot be done in C++ (yet - see the conclusions). Follows a proof.

CONSIDERATION 1: [on the need of templates]

In order to determine the correct member function Ai::f to be invoked at run-time when the expression call_on_variant(v, f, ...) is met (or any equivalent form of it), it is necessary, given the variant object v, to retrieve the type Ai of the value being held by v. Doing so necessarily requires the definition of at least one (class or function) template.

The reason for this is that no matter how this is done, what is needed is to iterate over all the types the variant can hold (the type list is exposed as boost::variant<...>::types, check whether the variant is holding a value of that type (through boost::get<>), and (if so) retrieve that value as the pointer through which the member function invocation must be performed (internally, this is also what boost::apply_visitor<> does).

For each single type in the list, this can be done this way:

using types = boost::variant<A1*, ..., An*>::types;
mpl::at_c<types, I>::type* ppObj = (get<mpl::at_c<types, I>::type>(&var));
if (ppObj != NULL)
{
    (*ppObj)->f(...);
}

Where I is a compile-time constant. Unfortunately, C++ does not allow for a static for idiom that would allow a sequence of such snippets to be generated by the compiler based on a compile-time for loop. Instead, template meta-programming techniques must be used, such as:

mpl::for_each<types>(F());

where F is a functor with a template call operator. Directly or indirectly, at least one class or function template needs to be defined, since the lack of static for forces the programmer to code the routine that must be repeated for each type generically.

CONSIDERATION 2: [on the need of locality]

One of the constraints for the desired solution (requirement 1 of the section "CONSTRAINTS" in the question's text) is that it shall not be necessary to add global declarations or any other declaration at any other scope than the one where the function call is being done. Therefore, no matter whether macro expansion or template meta-programming is involved, what needs to be done must be done in the place where the function call occurs.

This is problematic, because "CONSIDERATION 1" above has proved that it is needed to define at least one template to carry out the task. The problem is that C++ does not allow templates to be defined at local scope. This is true of class templates and function templates, and there is no way to overcome this restriction. Per §14/2:

"A template-declaration can appear only as a namespace scope or class scope declaration"

Thus, the generic routines we have to define in order to do the job must be defined elsewhere than at call site, and must be instantiated at call-site with proper arguments.

CONSIDERATION 3: [on function names]

Since the call_on_variant() macro (or any equivalent construct) must be able to handle any possible function f, the name of f must be passed in as an argument to our template-based, type resolving machinery. It is important to stress that only the name of the function shall be passed, because the particular function Ai::f that needs to be invoked must be determined by the template machinery.

However, names cannot be template arguments, because they do not belong to the type system.

CONCLUSION:

The combination of the three considerations above proves that this problem cannot be solved in C++ as of today. It requires either the possibility of using names as template arguments or the possibility of defining local templates. While the first thing is undesirable at least, the second one might make sense, but it is not being taken into consideration by the standardization committee. However, one exception is likely to be admitted.

FUTURE OPPORTUNITIES:

Generic lambdas, which are being strongly pushed to get into the next C++ standard, are in fact local classes with a template call operator.

Thus, even though the macro I posted at the end of the question's text will still not work, an alternative approach seems viable (with some tweaking required for handling return types):

// Helper template for type resolution
template<typename F, typename V>
struct extractor
{
    extractor(F f, V& v) : _f(f), _v(v) { }

    template<typename T>
    void operator () (T pObj)
    {
        T* ppObj = get<T>(&_v));
        if (ppObj != NULL)
        {
            _f(*ppObj);
            return;
        }
    }

    F _f;
    V& _v;
};

// v is an object of type boost::variant<A1*, ..., An*>;
// f is the name of the function to be invoked;
// The remaining arguments are the call arguments.
#define call_on_variant(v, f, ...) \
    using types = decltype(v)::types; \
    auto lam = [&] (auto pObj) \
    { \
        (*pObj)->f(__VA_ARGS__); \
    }; \
    extractor<decltype(lam), decltype(v)>(); \
    mpl::for_each<types>(ex);

FINAL REMARKS:

This is an interesting case of type-safe call that is (sadly) not supported by C++. This paper by Mat Marcus, Jaakko Jarvi, and Sean Parent seems to show that dynamic polymorphism on unrelated types is crucial to achieve an important (in my opinion, fundamental and unavoidable) paradigm shift in programming.

Community
  • 1
  • 1
Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • +1 well stated, this sums up the problem nicely, and I'm afraid you're right, the problem can't be solved with the constraints you made. – Seth Carnegie Jan 12 '13 at 16:02
  • @SethCarnegie: thank you, indeed it seems i'm asking too much. hope we'll have polymorphic lambdas soon enough – Andy Prowl Jan 12 '13 at 16:05
0

I once solved this by simulating .NET delegates:

template<typename T>
class Delegate
{
    //static_assert(false, "T must be a function type");
};

template<typename ReturnType>
class Delegate<ReturnType()>
{
private:
    class HelperBase
    {
    public:
        HelperBase()
        {
        }

        virtual ~HelperBase()
        {
        }

        virtual ReturnType operator()() const = 0;
        virtual bool operator==(const HelperBase& hb) const = 0;
        virtual HelperBase* Clone() const = 0;
    };

    template<typename Class>
    class Helper : public HelperBase
    {
    private:
        Class* m_pObject;
        ReturnType(Class::*m_pMethod)();

    public:
        Helper(Class* pObject, ReturnType(Class::*pMethod)()) : m_pObject(pObject), m_pMethod(pMethod)
        {
        }

        virtual ~Helper()
        {
        }

        virtual ReturnType operator()() const
        {
            return (m_pObject->*m_pMethod)();
        }

        virtual bool operator==(const HelperBase& hb) const
        {
            const Helper& h = static_cast<const Helper&>(hb);
            return m_pObject == h.m_pObject && m_pMethod == h.m_pMethod;
        }

        virtual HelperBase* Clone() const
        {
            return new Helper(*this);
        }
    };

    HelperBase* m_pHelperBase;

public:
    template<typename Class>
    Delegate(Class* pObject, ReturnType(Class::*pMethod)())
    {
        m_pHelperBase = new Helper<Class>(pObject, pMethod);
    }

    Delegate(const Delegate& d)
    {
        m_pHelperBase = d.m_pHelperBase->Clone();
    }

    Delegate(Delegate&& d)
    {
        m_pHelperBase = d.m_pHelperBase;
        d.m_pHelperBase = nullptr;
    }

    ~Delegate()
    {
        delete m_pHelperBase;
    }

    Delegate& operator=(const Delegate& d)
    {
        if (this != &d)
        {
            delete m_pHelperBase;
            m_pHelperBase = d.m_pHelperBase->Clone();
        }

        return *this;
    }

    Delegate& operator=(Delegate&& d)
    {
        if (this != &d)
        {
            delete m_pHelperBase;
            m_pHelperBase = d.m_pHelperBase;
            d.m_pHelperBase = nullptr;
        }

        return *this;
    }

    ReturnType operator()() const
    {
        (*m_pHelperBase)();
    }

    bool operator==(const Delegate& d) const
    {
        return *m_pHelperBase == *d.m_pHelperBase;
    }

    bool operator!=(const Delegate& d) const
    {
        return !(*this == d);
    }
};

You can use it much like .NET delegates:

class A
{
public:
    void M() { ... }
};

class B
{
public:
    void M() { ... }
};

A a;
B b;

Delegate<void()> d = Delegate<void()>(&a, &A::M);
d(); // calls A::M

d = Delegate<void()>(&b, &B::M);
d(); // calls B::M

This works with methods that have no arguments. If you can use C++11, you can modify it to use variadic templates to handle any number of parameters. Without C++11, you need to add more Delegate specializations to handle specific numbers of parameters:

template<typename ReturnType, typename Arg1>
class Delegate<ReturnType(Arg1)>
{
    ...
};

template<typename ReturnType, typename Arg1, typename Arg2>
class Delegate<ReturnType(Arg1, Arg2)>
{
    ...
};

With this Delegate class you can also emulate .NET events, which are based on delegates.

user1610015
  • 6,561
  • 2
  • 15
  • 18
  • thank you for sharing your code, which is very interesting :) however, this is regular type erasure that `std::function` and `std::bind` already support. what i am trying to achieve is something different (and, sadly, impossible as of today). see my answer for an explanation, and maybe try re-reading the question for better understanding what I am looking for. but thank you again for sharing your thoughts and code, I appreciate it – Andy Prowl Jan 12 '13 at 11:10