我正在为C ++功能样式开发事件调度程序驱动程序的框架。在此框架中,用户可以使用驱动程序为事件注册操作。稍后当事件发生时,驱动程序将使用注册的操作调度事件,如下所示:
#include <stdio.h>
#include <tuple>
using namespace std;
template < class ... ES >
class EventDriver {
public :
template<class T> using Func = void (*)(T& x);
tuple< Func< ES > ... > actions;
// Func< > default_action;
template < class EVENT>
void setAction ( void (*fp )(EVENT& ) ){
std::get< Func<EVENT> >( actions )=fp;
}
template < class EVENT>
void notify( EVENT& e ){
Func<EVENT> f = std::get< Func<EVENT> >( actions );
if( f ){
f( e );
}else{
printf("Could not process event. No registered action found \n");
}
}
};
// TEST the EventDriver
//
class EventAA{};
class EventBB{};
class EventCC{};
void processAA( EventAA& a ){
printf("Processing EventAA \n");
}
int main (){
EventDriver <EventAA, EventBB, EventCC> ed;
ed.setAction( &processAA );
EventAA aa;
ed.notify( aa );
EventBB bb;
ed.notify( bb ); // will display that no action registered
}
这很有效。现在作为最后一部分,我想为事件操作未向驱动程序注册的情况设置默认操作。我想为默认操作定义一个常用的模板化函数:
template<class E>
void processAnyEvent( E& a ){
printf("Processing Event %s \n", a.toString(); );
}
并使用EventDriver
ed.setDefaultAction( & processAnyEvent );
中的方法注册
如果没有找到注册的动作,将调用它。
我发现实现此要求并获得许多编译器错误非常困难。我使用的是gcc版本6.3.0,c ++ 14
default_action成员变量的类型应该是什么?
EventDriver::setDefaultAction
函数的签名应该是什么?
还有其他任何方法可以实现吗?
答案 0 :(得分:1)
默认初始化action
如下?
std::tuple<Func<ES>...> actions { &processAnyEvent<ES>... };
- 编辑 -
OP要求一种方法将不同的默认处理程序传递给不同的对象。
问题是模板的功能是
template <typename E>
void processAnyEvent (E &)
{ std::cout << "Generic Processing Event"; }
不是单个函数,而是一组函数。 EventDriver
需要所有这些(或者,至少每个模板参数一个),我不知道传递函数指针集的简单方法。
我能想象的最好的方法是用struct
static
方法写一些func()
作为
struct genEv1
{
template <typename E>
static void func (E &)
{ std::cout << "Generic Processing Event 1"; }
};
struct genEv2
{
template <typename E>
static void func (E &)
{ std::cout << "Generic Processing Event 2"; }
};
您可以将其作为EventDriver
EventDriver<genEv1, EventAA, EventBB, EventCC> ed;
并使用它来默认初始化actions
template <typename GPES, typename ... ES>
struct EventDriver
{
// ...
std::tuple<Func<ES>...> actions{ &GPES::template func<ES>... };
// ...
};
另一种方法是使用模板struct
通用处理程序作为构造函数参数传递func()
EventDriver<EventAA, EventBB, EventCC> ed{ genEv1{} };
在EventDriver
template <typename ... ES>
struct EventDriver
{
// ...
std::tuple<Func<ES>...> actions;
template <typename GPES>
EventDriver (GPES const &) : actions{ &GPES::template func<ES>... }
{ }
// ...
};
观察如果将genEv1
作为模板参数传递,则具有不同默认处理程序的两个EventDriver
对象是不同类型的对象,其中,如果genEv1
是构造函数参数,则它们是相同的类型(当Es...
类型相同时)。
答案 1 :(得分:0)
这是可能的,但路径有点刮风。
首先,除了在编译器能够确定只使用一个实际函数类型的某些情况下,不允许使用指向函数名称的指针,该函数是重载函数和/或函数模板。因此,&func_name
永远不会代表可以使用多种类型调用的内容。
但是C ++确实有一个可以用多种类型调用的东西:任何具有重载的类对象(称为仿函数)和/或名为operator()
的模板成员函数。因此,让setDefaultAction
成为接受任何此类仿函数的模板成员函数,只要可以使用所有可能的事件类型调用它。
但是我们想要在课堂上存储该仿函数的副本。 setDefaultAction
可以是一个功能模板,但我们不想让类EventDriver
需要一个模板参数,所以我们需要某种类型的删除&#34;类型擦除& #34;技术。 std::function
应该会有所帮助,因为它可以存储任何类型的仿函数并将调用转发给它。 (std::function
是指向函数的指针概念的概括,它表示可以像函数一样使用的任何东西,包括指向函数,函子和指向类成员的指针。)
但是,使用std::function
需要特定的参数列表类型(和返回类型),因此我们不能声明可以采用任何事件类型的std::function
。幸运的是,在EventDriver
专业化的背景下,我们有一个有限的可能类型列表。 C ++ 17有一个类,用于表示从可能类型的有限列表中选择的任何类型的值&#34;:std::variant
。这与函数std::visit
一起使用variant
中的当前值调用给定的仿函数。
除了我们想要的参数类型是对事件类型的引用,std::variant
不允许直接存储引用。有几个合理的解决方法:
存储std::reference_wrapper<E>
而不是E&
。在创建variant
时创建引用包装器对象,并在调用该仿函数时获取实际引用。
存储E*
而不是E&
。在创建variant
时获取事件的地址,并在调用仿函数时再次取消引用。
他们关于相同数量的工作,我认为reference_wrapper
更清楚地表明了意图,这就是我所展示的方式。
将以上所有内容放在一起:
#include <utility>
#include <functional>
#include <variant>
#include <iostream>
template < class ... ES >
class EventDriver {
public :
// ...
template<class T> using Func = std::function<void(T&)>;
std::tuple< Func< ES > ... > actions;
using VariantType = std::variant< std::reference_wrapper<ES>... >;
std::function<void(const VariantType&)> default_action;
template < typename F >
void setDefaultAction( F func ) {
auto unwrap_and_call = [func]( auto& ref )
{ func( ref.get() ); };
default_action = [unwrap_and_call]( const VariantType& v )
{ std::visit( unwrap_and_call, v ); };
}
template < class EVENT >
void notify( EVENT& e ){
Func<EVENT> f = std::get< Func<EVENT> >( actions );
if ( f ) {
f( e );
} else if ( default_action ) {
default_action( std::ref( e ) );
} else {
std::cout << "Could not process event."
" No registered action found \n";
}
}
// ...
};
现在它有效。请注意,如果您仍然喜欢使用一些普通的命名函数,函数指针就像setDefaultAction
作为类类型仿函数一样有效。
但接下来我要推荐一个额外的改变。由于仿函数比指针更灵活,并且该类现在接受setDefaultAction
的任何仿函数,为什么不允许setAction
中的仿函数?
从using Func = void (*)(T&)
更改为using Func = std::function<void(T&)>
是一件容易的事。这次遇到的麻烦是从一般的仿函数F func
确定事件类型并不是那么简单,因为它来自函数指针void (*fp)(EVENT&)
,我们需要知道哪个{{1}用于存储函子的插槽。
我完全没有详细介绍我对该问题的解决方案,但基本的想法是:
在tuple
函数签名中使用std::enable_if
,以确保可以使用其中一种可能的事件类型调用setAction
。
鉴于我们确切知道一个事件类型是可调用的,请定义一个帮助器特征结构来获取该事件类型。它使用递归来构建一次检查的结果。
一个完整的工作示例:
F
Run and/or edit this program on coliru.
我还定义了一个可以像// #include <cstdio>
#include <iostream>
#include <typeinfo>
#include <utility>
#include <type_traits>
#include <functional>
#include <tuple>
#include <variant>
namespace EventDriverPrivate {
template <typename F, class ArgTuple, typename Enable=void>
struct find_invocable_arg;
template <typename F, class E0, class ... ERest>
struct find_invocable_arg<F, std::tuple<E0, ERest...>,
std::enable_if_t<std::is_invocable<F&, E0&>::value>>
{
using type = E0;
};
template <typename F, class E0, class ... ERest>
struct find_invocable_arg<F, std::tuple<E0, ERest...>,
std::enable_if_t<!std::is_invocable<F&, E0&>::value>>
{
using type = typename find_invocable_arg<F, std::tuple<ERest...>>::type;
};
template <typename F, class ... ES>
using find_invocable_arg_t =
typename find_invocable_arg<F, std::tuple<ES...>>::type;
}
template < class ... ES >
class EventDriver {
public :
template<class T> using Func = std::function<void(T&)>;
std::tuple< Func< ES > ... > actions;
using VariantType = std::variant< std::reference_wrapper<ES>... >;
std::function<void(const VariantType&)> default_action;
template < typename F,
std::enable_if_t<(... + std::is_invocable<F&, ES&>::value) == 1, int> =0 >
void setAction ( F func ) {
std::get< Func< EventDriverPrivate::find_invocable_arg_t<F, ES...> > >
( actions ) = std::move(func);
}
template < class EVENT, typename F >
void setActionFor ( F func ) {
std::get< Func<EVENT> >( actions ) = std::move(func);
}
template < typename F >
void setDefaultAction( F func ) {
auto unwrap_and_call = [func]( auto& ref )
{ func( ref.get() ); };
default_action = [unwrap_and_call]( const VariantType& v )
{ std::visit( unwrap_and_call, v ); };
}
template < class EVENT>
void notify( EVENT& e ){
Func<EVENT> f = std::get< Func<EVENT> >( actions );
if ( f ) {
f( e );
} else if ( default_action ) {
default_action( std::ref( e ) );
} else {
std::cout << "Could not process event. No registered action found \n";
}
}
};
// TEST the EventDriver
//
class EventAA{};
class EventBB{};
class EventCC{};
void processAA( EventAA& ){
std::cout << "Processing EventAA \n";
}
int main (){
EventDriver <EventAA, EventBB, EventCC> ed;
ed.setAction( &processAA );
ed.setAction( [](EventBB&) { std::cout << "Processing EventBB\n"; } );
ed.setDefaultAction( [](auto& e)
{ std::cout << "Default action got type "
<< typeid(e).name() << "\n"; } );
EventAA aa;
ed.notify( aa );
EventBB bb;
ed.notify( bb );
EventCC cc;
ed.notify( cc );
}
一样使用的函数。如果您有一些实际上可以为多个事件类型调用的仿函数,这可能很有用。只需要一个可调用事件类型的ed.setActionFor<EventXX>(handler);
将失败,我们不希望编译器自动猜测要使用哪个事件槽,而是使用此备用函数可以指定要使用的事件处理程序。
此enable_if_t
概括中的一个缺点:如果一个事件类型可能在同一个setAction
列表中使用不同的事件类型作为基类,只有一个可调用事件类型的要求不起作用,因为用于基本事件的处理程序也可以从从该基类派生的事件中调用。在这种情况下,可以提出一些更复杂的模板助手来查找一个可调用的事件类型,它比所有其他可调用的事件类型更加派生....