It will be about such a simple, but often used pattern like chain of responsibility. The essence of the pattern is that for processing any event we use several handlers, each of which decides what and when to pass to the next. There are a lot of examples of implementations in C ++ on the web, but I want to show the implementation only on lambdas. In this implementation, you can see a bit of
street template-magic.
So, let's say we have an object:
class Elephant { public: std::string touch_leg() { return "it's like a pillar"; } std::string touch_trunk() { return "it's like a snake"; } std::string touch_tail() { return "it's like a rope"; } void run_away() { m_is_gone = true; std::cout << "*** Sound of running out elephant ***\n"; } bool is_elephant_here() { return !m_is_gone; } private: bool m_is_gone = false; };
And we will have 3 handlers, each of which will pass the object further:
In this example, there are 3 handlers, each of which is a lambda expression, which are chained together using the ChainOfRepsonsibility class.
Here is the class implementation itself:
#include <functional> struct ChainOfRepsonsibility { template<typename... Args> struct Chain { template<typename Callee, typename Next> Chain(const Callee c, const Next& n) { m_impl = c; m_next = n; } template<typename Callee> decltype(auto) attach(Callee c) { return Chain(c, *this); } void operator()(Args... e) { m_impl(e..., m_next); } std::function<void(Args..., std::function<void(Args...)>)> m_impl; std::function<void(Args...)> m_next; }; template<typename... Args> struct ChainTail { template<typename Callee> ChainTail(Callee c) { m_impl = c; } template<typename Callee> decltype(auto) attach(Callee c) { return Chain<Args...>(c, m_impl); } void operator()(Args... e) { m_impl(e...); } std::function<void(Args... e)> m_impl; }; template<typename> struct StartChain; template<typename C, typename... Args> struct StartChain<void (C::*)(Args...) const> { using Type = ChainTail<Args...>; }; template<typename Callee> static decltype(auto) start_new(Callee c) { return StartChain<decltype(&Callee::operator())>::Type(c); } };
It works like this:
- First, we create a chain of responsibility using the start_new function. The main problem at this stage is to get a list of arguments from the passed lambda and create a “prototype” of the handler based on them.
- To get the arguments of the lambda, the StartChain class is used and a clever example with a specialization of templates. First we declare a generic version of the class, and then the specialization struct StartChain <void (C :: *) (Args ...) const> . This construct allows us to access the list of arguments by the passed class member.
- Having a list of arguments, we can already create the ChainTail and Chain classes, which will be a wrapper for handlers. Their task is to save lambda to std :: function and call it at the right time, and also to implement the attach method (setting the handler from above).
The code was tested on visual studio 2015, for gcc it may be necessary to correct the StartChain specialization by removing const from there, if besides the lambdas you just want to transfer functions, you can add another StartChain specialization. You can still try to get rid of copying in attach, do move, but not to complicate the example, I left only the simplest case.