Caio Wakamatsu

C++ 26 Reflection Usage

C++ 26 brings us a lot of cool stuff with reflection. I’m using it in a project of mine where I have hundreds of packet schemas for Minecraft’s network protocol.

NOTE: This blog post is more of a show-and-tell than an instructive “How to reflection”, a guide to C++ reflection is coming soon and will be accessible here

Here’s two real neat thing I found you can do. Both examples are working on GCC16.1 with Clang support to hopefully come soon.

1) Automatic type-based schema (de)serialization

Instead of having to specify

export struct handshake_s {
  std::int32_t protocol_version;
  std::string server_address;
  std::uint16_t port;
  std::int32_t intent;

  static coro::task<handshake_s> read(game::connection *connection);
};

coro::task<handshake_s> handshake_s::read(game::connection *connection) {
  [[maybe_unused]] const auto [length, id] = co_await parse_packet_header(connection);

  co_return handshake_s{
      .protocol_version = co_await connection->read_var_int(),
      .server_address = co_await connection->read_string(),
      .port = co_await connection->read_u16(),
      .intent = co_await connection->read_var_int(),
  };
}

and write out the entire read (or write) function to de(serialize) things to the wire. I can specify the type once, and let reflection do the member iteration for me.

Here is some example code that prints the member variables and their values with C++ 26 reflection.

template <typename T>
void print_types_and_values(const T& obj) {
    constexpr auto ctx = std::meta::access_context::current();
    constexpr static auto members =
        std::define_static_array(std::meta::nonstatic_data_members_of(^^T, ctx));

    template for (constexpr auto member : members) {
        std::print("{}: {} = {}\n",
            std::meta::identifier_of(member),
            std::meta::display_string_of(std::meta::type_of(member)),
            obj.[:member:]
        );
    }
}

Exending this to do automatic parsing for each member based on the type is left as an exercise to the reader.

2) Type Synthesis

C++ 26 reflection also somewhat silently adds a very powerful feature. Type synthesis, allowing you to programtically create types from thing air. This includes creating types from json (Which inspired this section of the blog).

Creating a type from scratch in C++ requires a lot of steps. Let’s go through them with the goal of being able to make a simple type similar to.

struct {
    int a;
    float b;
    std::string hello;
};

Where we can make the types / names whatever we want. It is not a goal of this blog post to handle nested types, but this can be derived from the existing material.

Synthesising a type

We need a way to be able to go from member variable definitions to an actual usable type, the following code accomplishes this 1.

template <std::meta::info ...Members>
struct outer {
    struct inner;
    consteval {
        define_aggregate(^^inner, {Members...});
    }
};

template <std::meta::info ...Members>
using synthesis_type = outer<Members...>::inner

This code is a utility that allows us to go from a std::vector<std::meta::info> to a struct with the member info types.

consteval std::meta::info create_type_info() {
    auto members = std::vector<std::meta::info>();

    auto dms = std::meta::data_member_spec(^^int, {.name ="a"});
    members.push_back(std::meta::reflect_constant(dms));

    dms = std::meta::data_member_spec(^^float, {.name ="b"});
    members.push_back(std::meta::reflect_constant(dms));

    dms = std::meta::data_member_spec(^^std::string, {.name ="hello"});
    members.push_back(std::meta::reflect_constant(dms));

    return substitute(^^synthesis_type, members);
}

using synthesised = typename [:create_type_info():];

This code is the one that generates the member variable specifications. The second parameter which we use just for naming is std::meta::data_member_options, which looks like

struct data_member_options
{
    std::optional</*name-type*/> name;
    std::optional<int> alignment;
    std::optional<int> bit_width;
    bool no_unique_address = false;
    std::vector<std::meta::info> annotations;
};

You now know a little bit more about C++ 26 static reflection and it’s abilities :)

Footnotes

  1. Source