Parsing your config file before the program exists
Two C++26 features are neat alone and more useful together. #embed pulls a file into the translation unit as data. Reflection enumerates a type’s members. Combine them and the compiler parses a configuration file into a struct. The field names double as the keys.
The part that removes work
The usual version of this code writes every key twice, once in the struct and once in the parser:
cfg.host = lookup(text, "host");
cfg.port = lookup(text, "port");
cfg.retries = lookup(text, "retries");
Reflection gets the names from the members themselves, so the struct is the only place the schema is written:
template <typename T>
consteval T parse_config(std::string_view text) {
constexpr auto ctx = std::meta::access_context::unchecked();
constexpr auto members =
std::define_static_array(std::meta::nonstatic_data_members_of(^^T, ctx));
T out{};
[&]<std::size_t... I>(std::index_sequence<I...>) {
((out.[:members[I]:] = lookup(text, std::meta::identifier_of(members[I]))), ...);
}(std::make_index_sequence<members.size()>{});
return out;
}
The function identifier_of makes it work. It returns the member’s own name as a compile-time string, and the parser uses that name as the lookup key. Add a field to config and the parser looks it up without any edit.
The part that catches mistakes early
Because the whole thing runs during translation, the result is a constant and the checks are assertions:
constexpr config cfg = parse_config<config>(kRaw);
static_assert(!cfg.host.empty(), "config is missing host");
static_assert(cfg.port == "8080");
Delete a line from the config file and the build fails with that message. Most programs still do the opposite. They discover the problem when the service will not start, in an environment whose file differs from the one you tested with.
That is the real argument for this approach. Speed is a side benefit, since nothing is parsed at run time. The point is that a configuration error becomes a compile error, and compile errors happen to the person who made them.
Where it stops
The format here is deliberately trivial: key equals value. A full parser would be longer without being more instructive. The technique scales to anything you can parse in a consteval function, which since C++26 is a surprisingly large set. Every format you support is a parser you now maintain in constant-expression form.
It also only applies to configuration that is genuinely fixed at build time. A file the operator edits after deployment does not qualify. These do:
- feature flags
- embedded schemas
- route tables
- build-stamped defaults
Both halves work on a released compiler. GCC 16.1 takes this file with -std=c++26 -freflection and the standard <meta> header. The clang-p2996 fork also builds it. Reflection is further along than “wait for the next standard” suggests.
Sources: P2996 “Reflection for C++26” · P1967 “#embed” · source in the examples repo