Annotate a type¶
Describe how a struct travels on the wire: byte order, packing, and how those choices reach nested types. The struct itself is untouched; only the bytes RBE reads and writes change.
Source: example/howto/annotations.cpp, CMake target howto_annotations.
Annotate the struct¶
An annotation on the struct applies to every member.
// Every member travels packed and big-endian
struct [[=rbe::pack, =rbe::big]] Header {
std::uint16_t length;
std::uint8_t type;
std::uint32_t sequence;
};
static_assert(sizeof(Header) == 8); // the struct keeps its C++ layout...
static_assert(rbe::wire_size_of<Header>() == 7); // ...only the bytes on the wire are packed
Override on a member¶
A member's own annotation replaces the one it inherits, one dimension at a time: crc below is still
packed, only its byte order changes.
// A member's own annotation wins over the one it inherits from the struct
struct [[=rbe::pack, =rbe::big]] Record {
std::uint32_t timestamp; // big-endian, inherited
[[=rbe::little]] std::uint32_t crc; // little-endian, explicit
};
Nested types¶
A nested struct with no annotations of its own inherits the enclosing struct's. One that has its own keeps them, so a type declared once with its wire format is written the same way wherever it appears.
struct Point { // no annotations of its own...
std::int16_t x;
std::int16_t y;
};
struct [[=rbe::pack_be]] Shape {
Point origin; // ...so it inherits pack and big from Shape
std::uint8_t sides;
};
struct [[=rbe::little]] LegacyFooter { // a nested type with its own annotations keeps them
std::uint32_t checksum;
};
struct [[=rbe::pack_be]] Message {
Shape shape; // packed, big-endian
LegacyFooter footer; // packed (inherited), little-endian (its own)
};
static_assert(rbe::wire_size_of<Shape>() == 5);
static_assert(rbe::wire_size_of<Message>() == 9);
Presets¶
rbe::pack_le and rbe::pack_be cover the common pairs. rbe::derive<...> groups any annotations into a
value you can name and reuse.
struct [[=rbe::pack_le]] A { std::uint32_t v; }; // same as [[=rbe::pack, =rbe::little]]
struct [[=rbe::pack_be]] B { std::uint32_t v; }; // same as [[=rbe::pack, =rbe::big]]
// your own preset: group annotations once, reuse them by name
inline constexpr auto wire_format = rbe::derive<rbe::pack, rbe::big, rbe::fmt>;
struct [[=wire_format]] C { std::uint32_t v; };
Defaults¶
With no annotation a type travels little-endian with its C++ layout, padding included. That makes it
trivially wirable: the wire bytes are the object's bytes, and every operation is a memcpy.
// No annotation at all: little-endian and the regular C++ layout, padding included
struct Plain {
std::uint8_t flag;
std::uint32_t value;
};
static_assert(rbe::wire_size_of<Plain>() == sizeof(Plain)); // 8, not 5
static_assert(rbe::trivially_wirable<Plain>); // so it is copied with one memcpy
Compile-time checks¶
Two annotations of the same kind in the same place, or an annotation on something it cannot apply to, is a
compile error at the point of use. rbe::well_annotated is the check as a concept.
// Two annotations of the same dimension in one place is a mistake the compiler reports
struct [[=rbe::little, =rbe::big]] Conflicting { std::uint32_t v; };
static_assert(rbe::well_annotated<Message>);
static_assert(not rbe::well_annotated<Conflicting>);
See also: Annotations reference for every annotation and the exact inheritance rules.