Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/160.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/templates/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 是否可以在C++;?_C++_Templates_Collections_Lambda_Initializer List - Fatal编程技术网

C++ 是否可以在C++;?

C++ 是否可以在C++;?,c++,templates,collections,lambda,initializer-list,C++,Templates,Collections,Lambda,Initializer List,我想编写一个支持传递任意数量参数的事件管理器。为了向您展示表单,下面是一个示例。请注意,一个目标是不需要为每个事件定义类。相反,事件由字符串名称表示。首先,让我们为同一事件注册四个侦听器。它们接受的参数数量不同 Events events; events.listen("key", [=] { cout << "Pressed a key." << endl; }); events.listen("key", [=](int code) { cout

我想编写一个支持传递任意数量参数的事件管理器。为了向您展示表单,下面是一个示例。请注意,一个目标是不需要为每个事件定义类。相反,事件由字符串名称表示。首先,让我们为同一事件注册四个侦听器。它们接受的参数数量不同

Events events;

events.listen("key", [=] {
    cout << "Pressed a key." << endl;
});

events.listen("key", [=](int code) {
    cout << "Pressed key with code " << code << "." << endl;
});

events.listen("key", [=](int code, string user) {
    cout << user << " pressed key with code " << code << "." << endl;
});

events.listen("key", [=](int code, string user, float duration) {
    cout << user << " pressed key with code " << code << " for " << duration
         << " seconds." << endl;
});

events.listen("key", [=](string user) {
    cout << user << " pressed a key." << endl;
});
事件;
事件。侦听(“键”,[=]{
库特
一个目标是不需要为每个事件定义类

这是一个很好的迹象,你想要的东西比C++更适合你的目的,因为它没有动态反射能力。(如果你使用更动态的东西,但仍然需要与C++接口,那么你需要弥补这个差距,所以这个答案可能还是不一定有用。) 现在,虽然可以构建(有限的)动态系统,但您应该问问自己,这是否是您真正想要做的。例如,如果您“关闭”事件及其回调签名的世界,您将保留大量类型安全性:

// assumes variant type, e.g. Boost.Variant
using key_callback = variant<
    function<void(int)>                  // code
    , function<void(int, string)>        // code, user
    , function<void(int, string, float)> // code, user, duration
    , function<void(string)>             // user
>;

using callback_type = variant<key_callback, …more event callbacks…>;
//采用变量类型,例如Boost.variant
使用key\u callback=variant<
函数//代码
,函数//代码,用户
,函数//代码,用户,持续时间
,函数//用户
>;
使用callback_type=variant;
但是,本着坚持您的要求的精神,以下是如何存储任何†回调,并且仍然能够调用它:

using any = boost::any;
using arg_type = std::vector<any>;

struct bad_signature: std::exception {};
struct bad_arity: bad_signature {};
struct bad_argument: bad_signature {
    explicit bad_argument(int which): which{which} {}
    int which;
};

template<typename Callable, typename Indices, typename... Args>
struct erased_callback;

template<typename Callable, std::size_t... Indices, typename... Args>
struct erased_callback<Callable, std::index_sequence<Indices...>, Args...> {
    // you can provide more overloads for cv/ref quals
    void operator()(arg_type args)
    {
        // you can choose to be lax by using <
        if(args.size() != sizeof...(Args)) {
            throw bad_arity {};
        }

        callable(restore<Args>(args[Indices], Indices)...);
    }

    Callable callable;

private:
    template<typename Arg>
    static Arg&& restore(any& arg, int index)
    {
        using stored_type = std::decay_t<Arg>;
        if(auto p = boost::any_cast<stored_type>(&arg)) {
            return std::forward<Arg>(*p);
        } else {
            throw bad_argument { index };
        }
    }
};

template<
    typename... Args, typename Callable
    , typename I = std::make_index_sequence<sizeof...(Args)>
>
erased_callback<std::decay_t<Callable>, I, Args...> erase(Callback&& callback)
{ return { std::forward<Callback>(callback) }; }

// in turn we can erase an erased_callback:
using callback_type = std::function<void(arg_type)>;

/*
 * E.g.:
 * callback_type f = erase<int>([captures](int code) { ... });
 */
使用any=boost::any;
使用arg_type=std::vector

如果你有一个类型特征可以猜测一个可调用类型的签名,你可以编写一个使用它的
erase
(同时仍然允许用户在无法推断的情况下填写)。我在示例中不使用一个,因为那是另一个蠕虫


†:“any”表示任何可调用对象接受一定数量的可复制参数,返回
void
——您可以通过使用类似于
boost::any

的仅移动包装来放宽对参数的要求。我同意Luc的观点,即类型安全方法可能更合适,但以下解决方案确实如此或多或少是您想要的,但有一些限制:

  • 参数类型必须是可复制的
  • 参数总是被复制,从不移动
  • 当且仅当
    fire()
    的前N个参数的类型与处理程序的参数类型完全匹配时,才会调用具有N个参数的处理程序,并且不会执行隐式转换(例如,从字符串文字到
    std::string
  • 处理程序不能是具有多个重载
    运算符()
    的函子
  • 这就是我的解决方案最终允许您编写的内容:

    void my_handler(int x, const char* c, double d)
    {
        std::cout << "Got a " << x << " and a " << c 
                  << " as well as a " << d << std::endl;    
    }
    
    int main()
    {
        event_dispatcher events;
    
        events.listen("key", 
                      [] (int x) 
                      { std::cout << "Got a " << x << std::endl; });
    
        events.listen("key", 
                      [] (int x, std::string const& s) 
                      { std::cout << "Got a " << x << " and a " << s << std::endl; });
    
        events.listen("key", 
                      [] (int x, std::string const& s, double d) 
                      { std::cout << "Got a " << x << " and a " << s 
                                  << " as well as a " << d << std::endl; });
    
        events.listen("key", 
                      [] (int x, double d) 
                      { std::cout << "Got a " << x << " and a " << d << std::endl; });
    
        events.listen("key", my_handler);
    
        events.fire("key", 42, std::string{"hi"});
    
        events.fire("key", 42, std::string{"hi"}, 3.14);
    }
    
    而第二次调用将产生以下输出:

    Got a 42
    Got a 42 and a hi
    Bad arity!
    Bad argument!
    Bad arity!
    
    Got a 42
    Got a 42 and a hi
    Got a 42 and a hi as well as a 3.14
    Bad argument!
    Bad argument!
    
    这是一本书

    该实现基于
    boost::any
    。它的核心是
    dispatcher
    functor。它的call操作符接受一个类型为擦除参数的向量,并将它们分派给构造它的可调用对象(处理程序)。如果参数类型不匹配,或者如果处理程序接受的参数多于提供的参数,则它只会将错误打印到标准输出,但如果您愿意,可以使其抛出,或者执行任何您喜欢的操作:

    template<typename... Args>
    struct dispatcher
    {
        template<typename F> dispatcher(F f) : _f(std::move(f)) { }    
        void operator () (std::vector<boost::any> const& v)
        {
            if (v.size() < sizeof...(Args))
            {
                std::cout << "Bad arity!" << std::endl; // Throw if you prefer
                return;
            }
    
            do_call(v, std::make_integer_sequence<int, sizeof...(Args)>());
        }    
    private:
        template<int... Is> 
        void do_call(std::vector<boost::any> const& v, std::integer_sequence<int, Is...>)
        {
            try
            {
                return _f((get_ith<Args>(v, Is))...);
            }
            catch (boost::bad_any_cast const&)
            {
                std::cout << "Bad argument!" << std::endl; // Throw if you prefer
            }
        }    
        template<typename T> T get_ith(std::vector<boost::any> const& v, int i)
        {
            return boost::any_cast<T>(v[i]);
        }        
    private:
        std::function<void(Args...)> _f;
    };
    
    很明显,如果处理程序是一个带有多个重载调用运算符的函子,那么整个过程将不起作用,但希望这个限制对您来说不会太严重

    最后,
    event\u dispatcher
    类允许您通过调用
    listen()
    将类型擦除处理程序存储在多重映射中,并在使用适当的键和参数调用
    fire()
    时调用它们(您的
    events
    对象将是此类的实例):

    struct事件调度器
    {
    公众:
    模板
    void listen(std::string const&event,F&&F)
    {
    _部署(事件,make_调度程序(std::forward(f));
    }
    模板
    无效火灾(标准::字符串常量和事件,参数常量和…参数)
    {
    自动rng=\u回调。相等\u范围(事件);
    用于(自动it=rng.first;it!=rng.second;++it)
    {
    调用(it->second,args…);
    }
    }
    私人:
    模板
    无效调用(F常量和F、参数常量和…参数)
    {
    向量v{args…};
    f(v);
    }
    私人:
    std::multimap\u回调;
    };
    

    同样,整个代码都是可用的。

    这是完全可能的,实际上我有一个非常好的实现,只是有一些细微的差异。我有一些建议可能是相关的:1)字符串生成的键非常糟糕,因此除非您有非常具体的理由将字符串作为键,否则我建议您坚持使用某种类型的标记。好处通常是开销稍小,但首先是静态类型检查和捕获键入错误。2)这里的大多数逻辑错误都会在运行时被异常捕获,我不太明白每次调用信号调用抛出都有什么好处。不管怎样,我可以在明天的某个时候在办公室向您展示我的实现,它很灵活,支持将具有不同签名的lambda映射到信号,但使用静态标记进行调度。@Ylisar那太好了!当然,签名不匹配可以我知道,只有在实际触发事件时才会检测到。如果使用适当的类型实例化事件,则可以使其在最后两个事件上出现编译错误。基本思想是创建一个从X继承的类X,该类为该参数集定义一个函数,并具有触发器()函数,该函数将事件发送给其侦听器和基类。基本上,递归模板为每个参数集提供显式函数以支持&一个触发器函数来绑定它们。或者,将它们包装在lambda中,隐藏参数并存储在单个类中。@dascandy感谢您
    template<typename T>
    struct dispatcher_maker;
    
    template<typename... Args>
    struct dispatcher_maker<std::tuple<Args...>>
    {
        template<typename F>
        dispatcher_type make(F&& f)
        {
            return dispatcher<Args...>{std::forward<F>(f)};
        }
    };
    
    template<typename F>
    std::function<void(std::vector<boost::any> const&)> make_dispatcher(F&& f)
    {
        using f_type = decltype(&F::operator());
    
        using args_type = typename function_traits<f_type>::args_type;
    
        return dispatcher_maker<args_type>{}.make(std::forward<F>(f)); 
    }
    
    template<typename T>
    struct function_traits;
    
    template<typename R, typename C, typename... Args>
    struct function_traits<R(C::*)(Args...)>
    {
        using args_type = std::tuple<Args...>;
    };
    
    template<typename R, typename C, typename... Args>
    struct function_traits<R(C::*)(Args...) const>
    {
        using args_type = std::tuple<Args...>;
    };
    
    struct event_dispatcher
    {
    public:
        template<typename F>
        void listen(std::string const& event, F&& f)
        {
            _callbacks.emplace(event, make_dispatcher(std::forward<F>(f)));
        }
    
        template<typename... Args>
        void fire(std::string const& event, Args const&... args)
        {
            auto rng = _callbacks.equal_range(event);
            for (auto it = rng.first; it != rng.second; ++it)
            {
                call(it->second, args...);
            }
        }
    
    private:
        template<typename F, typename... Args>
        void call(F const& f, Args const&... args)
        {
            std::vector<boost::any> v{args...};
            f(v);
        }
    
    private:
        std::multimap<std::string, dispatcher_type> _callbacks;
    };