按名称调用不同签名的方法
Calling methods of different signatures by name
我有一组使用 std::function
的委托,它们指向具有不同签名的函数。我希望能够使用字符串键在 运行 时间检索这些代表。我似乎无法使用地图,因为它们指向具有不同签名的函数。我可以在没有 switch 语句的情况下拥有这样的功能吗?
例如,现实世界的用例是 RPC 系统。他们真的只是让方法具有相同的签名或使用代码生成吗?
E:与所选答案相关的有用链接,需要花费大量时间才能掌握
http://en.cppreference.com/w/cpp/utility/functional/function
http://en.cppreference.com/w/cpp/utility/forward
http://en.cppreference.com/w/cpp/utility/integer_sequence
http://en.cppreference.com/w/cpp/types/result_of
查找表和std::map
的问题是所有函数指针必须具有相同的签名。
但是,围绕这个问题有一个复杂的解决方案。它涉及函数对象 (functors) 和基础 classes.
让我们定义基本函子class:
struct Base_Parameters;
struct Base_Functor
{
virtual void execute(Base_Parameter * p_parameters) = 0;
};
这解决了如何声明 std::map
的问题:
typedef std::map<std::string, Base_Functor *> Function_Key_Container;
任何函数都将派生自基本函数结构:
结构 My_Functor : Base_Functor
{
无效执行(Base_Parameters * p_parameters)
{
//...
}
};
您可以将函数添加到地图中,如下所示:
Function_Key_Container function_map;
function_map["Apple"] = new My_Functor;
现在的问题是将参数传递给函数。
我还没有完全解决这个问题,但这里有一些建议:
- 使用boost::variant -- 变体记录。
- 从
Base_Parameters
创建一个子对象并在函数对象中使用
dynamic_cast
将 Base_Parameters
指针转换为指针
到子参数。
从类型包开始:
template<class...Ts>struct types{
using type=types;
enum{count = sizeof...(Ts)};
};
template<class T> struct tag{using type=T;};
现在我们在一个全局 types
包中定义所有支持的类型。
全局 types
包的索引通过网络发送,用于查找反序列化代码。
在你的协议命名空间(对于基本类型和 std
类型)和 T
的命名空间(对于其他类型)中应该有一个函数 boost::any read( wire_data, T* unused )
定义为 wire数据变成了boost::any
。 wire_data
只是一个占位符,用于表示您从电线中取出的任何东西,然后变成 T
。
我们通过魔术切换技术将类型索引转换为对 read
的调用:
template<size_t n> using index=std::integral_constant<size_t, n>;
template<class types, class T>
struct index_in;
template<class...Ts, class T>
struct index_in<types<T, Ts...>, T>:index<0> {};
template<class T0, class...Ts, class T1>
struct index_in<types<T0, Ts...>, T1>:index<
index_in<types<Ts...>, T1>::value+1
> {};
为我们提供了类型 T
在 types<Ts...>
中的偏移量。在发送方使用它来将您的类型映射到列表中的索引。
另一边,我们有:
template<class types, size_t n>
struct type_at;
template<class types, size_t n>
using type_at_t=typename type_at<types,n>::type;
template<class T0, class...Ts>
struct type_at<types<T0, Ts...>,0>: tag<T0> {};
template<class T0, class...Ts, size_t n>
struct type_at<types<T0, Ts...>,n>:
type_at<types<Ts...>, n-1>
{};
它接受一个 types<Ts...>
和一个索引,并且 return 是一个类型。
template<class types>
struct to_any {
template<size_t n>
struct worker {
boost::any operator()( wire_data w )const{
using protocol_ns::read;
return read( w, (type_at_t<types,n>*)nullptr );
}
};
};
使用 ADL 调度到 read
。
现在我们来写我们的快速魔法开关:
namespace details {
template<template<size_t>class action, class indexes>
struct magic_switch;
template<template<size_t>class action, size_t... Is>
struct magic_switch<action, std::index_sequences<Is...>>
{
template<class...Ts, class R=std::result_of_t< action<max>(Ts...) >>
R operator()(size_t i, Ts&&... ts)const {
using entry = R(*)(std::remove_reference<Ts>*...);
entry table[] = {
[](std::remove_reference<Ts>*...args)->R{
return action<Is>{}( std::forward<Ts>(*args)... );
}...
};
if (i > sizeof(table)/sizeof(entry))
throw std::out_of_range("i");
return table[i]( (&ts)... );
}
};
}
template<template<size_t>class action, size_t max>
struct magic_switch:
details::magic_switch<action,std::make_index_sequence<max>>
{};
然后
magic_switch<
to_any<all_types_supported>::template worker,
all_types_supported::count
>
是无状态函数对象的类型,当传递 n
和 wire_data
时,将为该类型调用适当的 read
函数,并且 return boost::any
.
好的,现在我们已经完成了一半。
下半部分涉及到我们的签名函数 Z(Args...)
,并编写一个类型橡皮擦,它采用 std::vector<boost::any>
存储 Args...
和 return 一个 boost::any
存储一个 Z
.
std::function<boost::any(std::vector<boost::any>)> erased_func_t;
template<class... Args, class F>
erased_func_t erase_func(F&& f) {
// TODO
}
一旦我们写完了,我们就可以为我们的 table 函数存储一个从字符串到 erased_func_t
的映射。
我们查找 erased_func_t
。我们使用上面的反序列化基础设施从传入的参数中生成一个 std::vector<boost::any>
。我们调用它,如果失败则抛出异常。
鲍勃是你的叔叔。
如果你想发回答案,你需要键入擦除回到有线格式,并将 erased_func_t
更改为 return 所需的 wire_data
通过线路而不是 boost::any
将其发回。那可能是最好的。
None 以上代码已经过测试。其中一些需要C++14(没那么多,主要是_t
别名),一些声称支持C++11的编译器不支持我写的magic_switch
实现(差不多纯 C++11,我相信 _t
别名除外)。但是可以写一个等效的,如果更冗长的话。
最后,像许多事情一样,从头开始编写 RPC 协议通常不是一个好主意。很可能我错过了上面的重要步骤。
我有一组使用 std::function
的委托,它们指向具有不同签名的函数。我希望能够使用字符串键在 运行 时间检索这些代表。我似乎无法使用地图,因为它们指向具有不同签名的函数。我可以在没有 switch 语句的情况下拥有这样的功能吗?
例如,现实世界的用例是 RPC 系统。他们真的只是让方法具有相同的签名或使用代码生成吗?
E:与所选答案相关的有用链接,需要花费大量时间才能掌握
http://en.cppreference.com/w/cpp/utility/functional/function
http://en.cppreference.com/w/cpp/utility/forward
http://en.cppreference.com/w/cpp/utility/integer_sequence
http://en.cppreference.com/w/cpp/types/result_of
查找表和std::map
的问题是所有函数指针必须具有相同的签名。
但是,围绕这个问题有一个复杂的解决方案。它涉及函数对象 (functors) 和基础 classes.
让我们定义基本函子class:
struct Base_Parameters;
struct Base_Functor
{
virtual void execute(Base_Parameter * p_parameters) = 0;
};
这解决了如何声明 std::map
的问题:
typedef std::map<std::string, Base_Functor *> Function_Key_Container;
任何函数都将派生自基本函数结构: 结构 My_Functor : Base_Functor { 无效执行(Base_Parameters * p_parameters) { //... } };
您可以将函数添加到地图中,如下所示:
Function_Key_Container function_map;
function_map["Apple"] = new My_Functor;
现在的问题是将参数传递给函数。
我还没有完全解决这个问题,但这里有一些建议:
- 使用boost::variant -- 变体记录。
- 从
Base_Parameters
创建一个子对象并在函数对象中使用dynamic_cast
将Base_Parameters
指针转换为指针 到子参数。
从类型包开始:
template<class...Ts>struct types{
using type=types;
enum{count = sizeof...(Ts)};
};
template<class T> struct tag{using type=T;};
现在我们在一个全局 types
包中定义所有支持的类型。
全局 types
包的索引通过网络发送,用于查找反序列化代码。
在你的协议命名空间(对于基本类型和 std
类型)和 T
的命名空间(对于其他类型)中应该有一个函数 boost::any read( wire_data, T* unused )
定义为 wire数据变成了boost::any
。 wire_data
只是一个占位符,用于表示您从电线中取出的任何东西,然后变成 T
。
我们通过魔术切换技术将类型索引转换为对 read
的调用:
template<size_t n> using index=std::integral_constant<size_t, n>;
template<class types, class T>
struct index_in;
template<class...Ts, class T>
struct index_in<types<T, Ts...>, T>:index<0> {};
template<class T0, class...Ts, class T1>
struct index_in<types<T0, Ts...>, T1>:index<
index_in<types<Ts...>, T1>::value+1
> {};
为我们提供了类型 T
在 types<Ts...>
中的偏移量。在发送方使用它来将您的类型映射到列表中的索引。
另一边,我们有:
template<class types, size_t n>
struct type_at;
template<class types, size_t n>
using type_at_t=typename type_at<types,n>::type;
template<class T0, class...Ts>
struct type_at<types<T0, Ts...>,0>: tag<T0> {};
template<class T0, class...Ts, size_t n>
struct type_at<types<T0, Ts...>,n>:
type_at<types<Ts...>, n-1>
{};
它接受一个 types<Ts...>
和一个索引,并且 return 是一个类型。
template<class types>
struct to_any {
template<size_t n>
struct worker {
boost::any operator()( wire_data w )const{
using protocol_ns::read;
return read( w, (type_at_t<types,n>*)nullptr );
}
};
};
使用 ADL 调度到 read
。
现在我们来写我们的快速魔法开关:
namespace details {
template<template<size_t>class action, class indexes>
struct magic_switch;
template<template<size_t>class action, size_t... Is>
struct magic_switch<action, std::index_sequences<Is...>>
{
template<class...Ts, class R=std::result_of_t< action<max>(Ts...) >>
R operator()(size_t i, Ts&&... ts)const {
using entry = R(*)(std::remove_reference<Ts>*...);
entry table[] = {
[](std::remove_reference<Ts>*...args)->R{
return action<Is>{}( std::forward<Ts>(*args)... );
}...
};
if (i > sizeof(table)/sizeof(entry))
throw std::out_of_range("i");
return table[i]( (&ts)... );
}
};
}
template<template<size_t>class action, size_t max>
struct magic_switch:
details::magic_switch<action,std::make_index_sequence<max>>
{};
然后
magic_switch<
to_any<all_types_supported>::template worker,
all_types_supported::count
>
是无状态函数对象的类型,当传递 n
和 wire_data
时,将为该类型调用适当的 read
函数,并且 return boost::any
.
好的,现在我们已经完成了一半。
下半部分涉及到我们的签名函数 Z(Args...)
,并编写一个类型橡皮擦,它采用 std::vector<boost::any>
存储 Args...
和 return 一个 boost::any
存储一个 Z
.
std::function<boost::any(std::vector<boost::any>)> erased_func_t;
template<class... Args, class F>
erased_func_t erase_func(F&& f) {
// TODO
}
一旦我们写完了,我们就可以为我们的 table 函数存储一个从字符串到 erased_func_t
的映射。
我们查找 erased_func_t
。我们使用上面的反序列化基础设施从传入的参数中生成一个 std::vector<boost::any>
。我们调用它,如果失败则抛出异常。
鲍勃是你的叔叔。
如果你想发回答案,你需要键入擦除回到有线格式,并将 erased_func_t
更改为 return 所需的 wire_data
通过线路而不是 boost::any
将其发回。那可能是最好的。
None 以上代码已经过测试。其中一些需要C++14(没那么多,主要是_t
别名),一些声称支持C++11的编译器不支持我写的magic_switch
实现(差不多纯 C++11,我相信 _t
别名除外)。但是可以写一个等效的,如果更冗长的话。
最后,像许多事情一样,从头开始编写 RPC 协议通常不是一个好主意。很可能我错过了上面的重要步骤。