graham.reeds
graham.reeds

Reputation: 16476

How does this pattern work?

I was reading a blog post on the Anthony Williams website when I somehow wandered onto an example of his just::thread library, his barber shop example.

In it he has a series of a structures that doesn't inherit from anything:

struct start_haircut {};
struct no_room {};
struct shop_closed {};

He then has a receive function that he chains the .match() template to:

jss::actor::receive()
    .match<start_haircut>(
        [&](start_haircut){
            //...
        })
    .match<no_room>(
        [&](no_room){
            //...
        })
    .match<shop_closed>(
        [&](shop_closed)
        {
            //...
        });

The receive function returns an unspecified_message_receiver object that specifies the type (shop_closed, etc.) and the lambda handler.

What goes inside the receive and match functions? How does the receive and match functions interact?

This is an interesting pattern that can have applications outside the threading model it is used on. I am interested in it for communications over tcp between sensors where small message packets and small amounts of data are being transferred continuously.

Upvotes: 1

Views: 162

Answers (2)

Aleksander Fular
Aleksander Fular

Reputation: 873

Here is one way to achieve it, be wary though as it uses runtime info instead of compile time information to achieve this, which may not be the way that the C++ template magician would achieve the solution :)!
godbolt link: https://godbolt.org/g/yZC8sM
You can try this online using ideone.

#include <typeinfo>
#include <map>
#include <memory>
#include <functional>
#include <iostream>

struct Erased {
    virtual ~Erased(){};
};

template <typename T>
struct Handler : public Erased {
    std::function<void(T)> handl;
};

struct cont {

  template<typename T>
  void handle(T msg) {
    const std::type_info *t = &typeid(T);
    auto iter = handlers.find(t);
    if(iter != handlers.end()) {

      dynamic_cast<Handler<T>*>(iter->second.get())->handl(msg);
    }
  }

  template <typename T>
  void match(std::function<void (T)> handler) {
    auto realHandler = std::make_shared<Handler<T>>();
    realHandler->handl = handler;
    const std::type_info* t = &typeid(T);
    handlers.insert(std::make_pair(t, realHandler));
  }

  std::map<const std::type_info*, std::shared_ptr<Erased>> handlers;


};

int main() {
  cont container;
  container.match<int>([](int x) { std::cout << " got  avalue of ... " << x; });
  container.handle(5);         
}

Just wanted to fill in on my answer here:
What i've written above is a solution using the runtime data to do this dispatch ( much simpler to understand), however you can create such dispatch using compile time dispatchers without std::map at all. small notes how -

You need to create a special type for each chain that is: .match([](int&) { ...}).match([](double&) {. .. }); where each match would create a complete new type with multiple methods, then using the type erasure you will get a generic type on which you can do a virtual dispatch to that erased type - and then using template generated code invoke your desired handler.

Upvotes: 0

molbdnilo
molbdnilo

Reputation: 66441

This looks (unsurprisingly) like Erlang.

This is pretty clearly described in the documentation you linked to, and quoted.

The receive function returns an unspecified_message_receiver object

so jss::actor::receive() is an unspecified_message_receiver,

Calling match() on a receiver adds the specified MsgType to the list of handled messages, and registers the specified handler to be called when a message of that type is received.

so

.match<start_haircut>(
    [&](start_haircut){
        //...
    })

registers the lambda to handle messages of type start_haircut, in the receiver returned previously.
Since each match returns a message receiver, you can chain them to register more handlers.

I'm not sure what else can be said to clarify, but a more realistic use might employ some types that carry some kind of payload, such as

struct start_haircut { enum { Long, Short, Shaved } style; };

jss::actor::receive()
    .match<start_haircut>(
        [&](start_haircut cut){
            switch (cut.style)
            {
                case start_haircut::Long:
                // ...
            }
        })
    .match<no_room>(
        [&](no_room){
            //...
        })
    .match<shop_closed>(
        [&](shop_closed)
        {
            //...
        });

(This kind of interface probably makes more sense if you take a peek at an Erlang tutorial, like "Learn you some Erlang for great good!").

Upvotes: 1

Related Questions