如何在不相关的类型上实现动态多态性(运行时调用分派)? [英] How to achieve dynamic polymorphism (run-time call dispatch) on unrelated types?

查看:72
本文介绍了如何在不相关的类型上实现动态多态性(运行时调用分派)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

目标:



我想实现类型安全动态多态性(即运行时调度对不相关类型的函数调用) - 即对于不具有公共基类的类型。在我看来,这是可以实现的,或至少在理论上是健全的。



给定以下内容:




  • 两个或多个不相关类型 A1, $ R ;和

  • a boost :: variant< A1 *,...,An *> object v (或任何其他类型的变体),并且必须必须随时假设任何类型的一个值;



我的目标是在概念上写等于 vf(arg_1,...,arg_m); 的指令,如果中包含的值的实际类型,则在运行时分派到函数 Ai :: f v Ai 。如果调用参数与每个函数 Ai 的形式参数不兼容,编译器应引发错误。



当然,我不需要坚持语法 vf(arg_1,...,arg_m):例如, call(v,f,...)也可以接受。



在C ++中实现这一点,但到目前为止我没有提出一个解决方案(我有一堆坏的解决方案)。



> 好解决方案是让我模仿 vf(...)成语的任何东西,例如 call_on_variant(v,f,...); 符合以下限制:


  1. 不需要任何类型的单独声明,每个函数 f ENABLE_CALL_ON_VARIANT(f))或任何无关类型的列表 A1,...,An (例如 ENABLE_VARIANT_CALL(A1,...,An))在代码中的其他位置,特别是在全局范围上;

  2. 在执行调用时需要显式地命名输入参数的类型(例如 call_on_variant< int,double,string>(v,f,...))。命名 return 类型是可以的,因此例如 call_on_variant< void>(v,f,...)

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



EXAMPLE :

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

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

//不想要下面这样的东西:
// 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);

//不想要下面这样的:
// 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);
}

此程序的输出应为: ABC



BEST(FAILED)ATTEMPT



最接近我想要的解决方案是这个宏:

  #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); \
}();

这将很好地工作,如果只允许模板成员在本地类中 (请参阅此问题)。

解决方案

有些时间已经过去了,C ++ 14



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

  #include< boost / variant.hpp> 

模板< 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&
return vt.apply_visitor(v);
}

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

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

  #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;
}
};

可以调用 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;
}

输出结果如预期:

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

该程序已在VC12上测试,2013年11月的CTP。不幸的是,我不知道任何在线编译器支持通用lambdas,所以我不能发布一个实例。


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?

解决方案

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.

这篇关于如何在不相关的类型上实现动态多态性(运行时调用分派)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆