如何在不相关的类型上实现动态多态(运行时调用调度)?

And*_*owl 14 c++ polymorphism runtime boost-variant c++11

目标:

我想在不相关的类型上实现类型安全的动态多态(即函数调用的运行时调度)- 即在没有公共基类的类型上.在我看来,这是可以实现的,或者至少在理论上是合理的.我会尝试更正式地定义我的问题.

问题定义:

鉴于以下内容:

  • 两个或多个不相关的类型A1, ..., An,每个类型都有一个被调用的方法f,可能具有不同的签名,但具有相同的返回类型 R ; 和
  • 一个boost::variant<A1*, ..., An*>对象v(或任何其他类型的变体),它可以并且必须在任何时候假设任何这些类型的一个值;

我的目标是写指令概念上等同于v.f(arg_1, ..., arg_m);将获得在运行时调度的功能Ai::f,如果实际类型包含的价值vAi.如果调用参数与每个函数的形式参数不兼容Ai,编译器应该引发错误.

当然我不需要坚持语法v.f(arg_1, ..., arg_m):例如,类似的东西call(v, f, ...)也是可以接受的.

我试图在C++中实现这一点,但到目前为止我还没有找到一个好的解决方案(我确实有很多坏的解决方案).下面我通过"好的解决方案"澄清我的意思.

约束:

一个好的解决方案是让我模仿v.f(...)成语的任何东西,例如call_on_variant(v, f, ...);,并满足以下约束:

  1. 不需要为每个必须以这种方式调用的函数(例如)或任何可以多态处理(例如)代码中其他地方的不相关类型列表的单独声明,特别是在全局范围内;fENABLE_CALL_ON_VARIANT(f)A1, ..., AnENABLE_VARIANT_CALL(A1, ..., An)
  2. 在执行调用时不需要显式命名输入参数的类型(例如call_on_variant<int, double, string>(v, f, ...)).命名返回类型是可以的,因此例如call_on_variant<void>(v, f, ...)可以接受.

按照一个示范性的例子,希望澄清我的愿望和要求.

例:

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");
}
Run Code Online (Sandbox Code Playgroud)

该程序的输出应为:ABC.

最佳(失败)尝试:

我得到所需解决方案的最接近的是这个宏:

#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); \
}();
Run Code Online (Sandbox Code Playgroud)

如果只允许在本地类中使用模板成员,那么哪种方法可以正常工作(请参阅此问题).有没有人知道如何解决这个问题,或者提出另一种方法?

And*_*owl 7

一段时间过去了,C++ 14正在最终确定,编译器正在增加对新功能的支持,比如通用lambda.

通用lambda与下面显示的机制一起,允许用不相关的类实现所需的(动态)多态性:

#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; });
Run Code Online (Sandbox Code Playgroud)

让我们付诸实践.假设有以下两个不相关的类:

#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;
    }
};
Run Code Online (Sandbox Code Playgroud)

可以通过foo()这种方式调用多态:

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;
}
Run Code Online (Sandbox Code Playgroud)

正如预期的那样,输出是:

A::foo(42, 3.14, Hello)
1
B::foo(1337, 6.28, World)
2
Run Code Online (Sandbox Code Playgroud)

该计划已于2013年11月的CTP上在VC12上进行了测试.不幸的是,我不知道任何支持通用lambda的在线编译器,所以我不能发布一个实例.