Skip to content

Dispatch on a message id

A header carries an id; the id says which of several messages follows. rbe::frames::any holds the candidates and match dispatches to the right one.

Source: example/howto/any_match.cpp, CMake target howto_any_match.

Declare the candidates

The header marks the field the id is read from with =rbe::id; each candidate declares the id it answers to with =rbe::id(value). The two must have the same type.

enum class msg_type : std::uint8_t { order = 1, cancel = 2, heartbeat = 3 };

// the header says which message follows: rbe::id marks the field
struct [[=rbe::pack_be]] Header {
  [[=rbe::id]] msg_type type;
  std::uint16_t         sequence;
};

// each candidate says which id it answers to: rbe::id(value) on the type
struct [[=rbe::pack_be, =rbe::id(msg_type::order)]]  Order  { std::uint32_t order_id; std::uint64_t price; };
struct [[=rbe::pack_be, =rbe::id(msg_type::cancel)]] Cancel { std::uint32_t order_id; };

// a message with no fields: rbe::empty, zero bytes on the wire
struct [[=rbe::id(msg_type::heartbeat), =rbe::empty]] Heartbeat { };

using message = rbe::frames::frame<Header, rbe::frames::any<Order, Cancel, Heartbeat>>;

Match

One callback per candidate you handle, plus a fallback for everything else.

auto msg = rbe::deserialize<message>(bytes);

msg.payload().match(
    [](Order const& order) {               // by value: the Order is decoded, then handed over
      std::println("order {} @ {}", order.order_id, order.price);
    },
    [](rbe::dsrl::proxy<Cancel> cancel) {  // by proxy: decoded field by field, on access
      std::println("cancel {}", cancel.field<"order_id">());
    },
    [](Heartbeat) { std::println("heartbeat"); },
    [](rbe::unmatched auto other) {        // mandatory: ids the code does not know, or does not handle here
      std::println("id {} {}", std::to_underlying(other.id), other.known_id ? "not handled" : "unknown");
    }
);
  • Take a candidate by value (Order const&) to have it decoded before the call, or as rbe::dsrl::proxy<T> to read it field by field. Not both: that is ambiguous and does not compile.
  • A candidate marked =rbe::empty is always taken by value.
  • The rbe::unmatched callback is mandatory. It receives both ids the code does not know and candidates no other callback handles; known_id tells them apart, id is the value on the wire and data the bytes.
  • Every callback must return the same type.

Ask instead of matching

auto payload = rbe::deserialize<message>(bytes).payload();

bool is_order = payload.is<Order>();
auto id       = payload.id();              // the id on the wire, as the header's field type
bool known    = payload.known_id();        // whether one of the candidates answers to it

std::optional<Order>                   order = payload.as<Order>(rbe::dsrl::eager);  // nullopt unless is<Order>()
std::optional<rbe::dsrl::proxy<Order>> view  = payload.as<Order>();                  // lazy by default

See also: Frames reference, any.