Rendered at 20:40:25 GMT+0000 (Coordinated Universal Time) with Cloudflare Workers.
vanderZwan 3 minutes ago [-]
> The header is the cost. Not the reflection. The reflection algorithm is fast – asymptotically ~0.07 ms per enumerator, essentially the same as the hand-rolled switch in the X-macro version (~0.06 ms). What makes reflection look expensive is <meta>: just including it costs ~155 ms per TU over the baseline.
So speaking of old ways, I'm not a C++ dev, but a while ago saw someone mentioned that they still organize their C++ projects using tips from John Lakos' Large-scale C++ software design from 1997, and that their compile times are incredibly fast. So I decided to find a digital copy on the high seas and read it out of historical curiosity. While I didn't finish it, one wild thing stood out to me: he advised for using redundant external include guards around every include, e.g.
The reason for this being that (in 1997) every include required that the pre-processor opened the file just to check for an include guard and reading it all the way to the end to find the closing #endif, causing potentially O(N*2) disk read overhead (if anyone feels like verifying this, it's explained on pages 85 to 87).
Again, that was in 1997. I have no idea what mitigations for this problem exist in compilers by now, but I hope at least a few, right?
This conclusion is making me wonder if following that advice still would have a positive impact on compile times today after all though. Surely not, right? Can anyone more knowledgeable about this comment on that?
sagacity 12 hours ago [-]
Oof, that first example (the idiomatic C++26 way) looks so foreign if you're mostly used to C++11.
delegate 6 hours ago [-]
I was very curious to see what C++ 26 brings to the table, since I haven't used C++ in a while.
When I saw the 'no boilerplate' example, the very first thought that came to my mind:
This is the ugliest, most cryptic and confusing piece of code I've ever seen.
Calling this 'no boilerplate' is an insult to the word 'boilerplate'.
Yeah, I can parse it for a minute or two and I mostly get it.
But if given the choice, I'd choose the C-macro implementation (which is 30+ years old) over this, every time. Or the good old switch case where I understand what's going on.
I understand that reflection is a powerful capability for C++, but the template-meta-cryptic-insanity is just too much to invite me back to this version of the language.
spacechild1 1 hours ago [-]
> But if given the choice, I'd choose the C-macro implementation (which is 30+ years old) over this, every time.
Why? The implementation is not pretty, but you only need to write it once and then it works for all enums. The actual usage is trivial, it's just a function call.
The C macro version is horrendous in comparison. Why would I want to declare my enums like that just because I might want to print them?
SuperV1234 6 hours ago [-]
It is "cryptic" and "ugly" to you just because you're not familiar with it. You'd pick the macro-based implementation because you are familiar with it.
Seeing this argumentation is so tiresome, because it feels like there is a lack of self-awareness regarding what is "familiar" and what isn't, which is subconsciously translated to "ugly" and "bad".
delegate 6 hours ago [-]
Have you ever used other (modern) programming languages ?
In a lot of languages, you achieve the same with 1 line of code. It's not about familiarity, it's about the fact that it's a long and convoluted incantation to get the name of an enum.
Why do I have to be familiar with all those weird symbols just to do a trivial thing ?
Update:
Zig:
const Color = enum { red, green, blue };
const name = @tagName(Color.red); // "red"
Rust:
#[derive(Display)]
enum Color { Red, Green, Blue }
let name = Color::Red.to_string(); // "Red"
Clojure:
(name :red) => "red"
throwaway7356 1 hours ago [-]
And what if you want to implement something like Rust's "derive"? That is what the article shows.
As far as I understand you would have to mess with individual parser tokens in Rust instead of high-level structures like "enum" (C++ reflection). It would be much, much uglier to implement anything like "to_enum_string" in Rust as you would have to re-implement parts of the compiler to get the "enum" concept out of a list of tokens.
SuperV1234 5 hours ago [-]
C++:
enum Color { red, green, blue };
auto name = to_enum_string(Color::Red); // "Red"
shooly 4 hours ago [-]
... and where does that `to_enum_string` come from exactly? It doesn't seem to be built-in, which is the point of the parent comment.
SuperV1234 2 hours ago [-]
It's a fair comparison. The parent comment isn't showing the compiler source code for the built-in reflection mechanisms.
You won't have to care about ^^ and [:X:] if you just want to consume reflection-based utils, which was the whole point of my comment.
gpderetta 1 hours ago [-]
The whole point of reflection is that it doesn't have to be builtin.
spacechild1 1 hours ago [-]
I find it quite readable. I can understand what it does even though I haven't written reflection code yet myself.
randusername 8 hours ago [-]
I was a fool to assume that the same forces shaping the ugliness of C++ syntax would not also be at work in C++ 26.
mananaysiempre 27 minutes ago [-]
Reflect/reify, quasiquote/unquote, etc. are the final boss of syntax design. Even Template Haskell looks rather bad.
bluGill 7 hours ago [-]
You realize c++11 is closer in age to C++98 than C++26?
mananaysiempre 19 minutes ago [-]
I’m not sure the nominal publication date of a standard is all that relevant when the implementors’ reaction is as lukewarm as it has been to C++ ≥20.
ginko 8 hours ago [-]
Is it? I'm mostly used to (pre-)C++11 and the only unusual operators I see are ^^T (which I presume accesses the metadata info of T) and [:e:] (which I assume somehow casts the enumerator metadata 'e' to a constant value of T).
And template for but I assume that's like inline for like in zig.
CamouflagedKiwi 8 hours ago [-]
requires is also new (not sure exactly when that appeared, it's after the last time I wrote C++ in anger) although I think it's fairly clear what it means. I can only guess at the other two.
Not familiar with Zig but AFAICT `inline for` is about instructing the compiler to unroll the loop, whereas `template for` means it can be evaluated at compile time and each loop iteration can have a different type for the iteration variable. It's a bit crazy but necessary for reflection to work usefully in the way the language sets it up.
Well yes, but the _effect_ is to unroll the loop for runtime, if the inline-for survives that long.
A for loop executed during comptime is just
const stuff = comptime stuff: {
for (0...8) |i| {
// etc, build up some stuff
}
break :stuff some_stuff;
};
The difference is that a comptime block won't leave behind runnable 'residue', only whatever data is constructed for later. An inline for might not leave behind an unrolled loop either, but it can.
w4rh4wk5 8 hours ago [-]
I've been wondering about debug-ability of code using reflection. X-Macros are quite annoying to step through in most debuggers, though possible. While the code in the first example is evaluated fully at compile-time, how would you approach debugging it?
kevin_thibedeau 22 minutes ago [-]
Keep macro generated code isolated in self-contained wrapper functions that just return a static object corresponding to an argument. Then you can treat them like black boxes that never fail and never need to be stepped over.
theICEBeardk 2 hours ago [-]
The answer is being debated at the moment in c++ papers and building on experience from other languages with extensive compile time evaluators like D. One thing that is happening is that we will get compile time exceptions (a paper for that is aiming to add this to the language in c++29 has come out) which may help us in reporting problems. Which will be important as there is also a lot of papers and talk about an extension of reflection allowing for better output generation which as far as I know was deferred until reflection had been accepted.
But there is also good news that with the advent of JIT like components for compile time evaluation in progress and the like of CLion having the beginnings of a compile debugger in combination with concepts there is a chance some help is available and on the way.
However right now you have to rely on compiler errors and static_asserts which is not ideal of course.
SuperV1234 6 hours ago [-]
Nothing that makes it straightforward. Testing via `static_assert` is a good strategy, but it's not debugging. I believe there are some ways of printing custom diagnostics during compilation, but I am not aware of any step-by-step debugging tool that runs at compile-time.
In practice, I haven't really needed to ever debug `consteval` functions -- it's quite easy to get the right behavior down thanks to `static_assert`-based testing and thanks to the fact that they do not depend on external state (simpler).
cenamus 7 hours ago [-]
I mean it's still C++ that's compiled and executed, surely the compiler would be able to provide a way to hook into that?
usefulcat 6 hours ago [-]
I don't recall the source, but I don't believe most (any?) c++ compilers implement compile-time code evaluation by compiling and running code.
For one thing they are required to disallow all undefined behavior for compile time execution, and some forms of UB only occur when the code is run.
varispeed 3 hours ago [-]
Why people are still using debuggers?
I never felt the need for them when doing TDD.
cv5005 2 hours ago [-]
Never quite understood why people are so obsessed with meta programming capabilities in a language, be it templates, comptime, macros, whatever.
I program mostly in C, if I need 'meta' programming I just write another C program that processes C source code (I've written a simple C parser), then in my build script I build in two stages, build meta program, run it, build rest of program.
Simple, effective, debuggable (the meta program is just normal C), infinite capabilities - can nest this to arbitritary depths, need meta-meta programming? Make a program that generates a meta program.
jstimpfle 5 minutes ago [-]
Actually why even specify metaprogram as C like source code? It must be convenience. But there is little practical use, like a good program always models a lot of different representations of more or less the same things, just recombined and processed a little differently. Why would we want to deal with semantics of C types for example, if we can model a much clearer and better constrained universe of types used in e.g. a de/serialization framework? Even only pointers are quite special, and often only of very immediate use, but there is no point in e.g. persisting them to disk or sending them over the network.
rddbs 2 hours ago [-]
One obvious answer is that people probably don’t want to write a whole parser and wire up new steps in their build pipeline just to do something simple like get the name of enum cases as a string.
Without taking a stance on whether in-language meta programming facilities are good or bad, it’s not hard to find examples of cases where people find it useful to have them.
44 minutes ago [-]
jmalicki 58 minutes ago [-]
This works for extreme needs.
But you're probably not doing s ton of metaprogramming all the time like you should be, and would with a language that allows it.
The lack of metaprogramming is also why C is so slow compared to C++
uecker 4 minutes ago [-]
C is not slow compared to C++. C++ compilation time are slow though.
jstimpfle 4 minutes ago [-]
C is not slow compared to C++, that is a strange myth.
pandaman 2 hours ago [-]
Writing a C++ parser is much harder than a C parser to the point there had been just 3 parsers used among all C++ compilers for quite a while. So you'd need to use some library for parsing. So now you are looking into the library's parser compatibility with the compiler you are using (it might not support the C++ standard you are on at all, it can have bugs preventing it from parsing the code that the compiler parses just fine) and not just on your code but on the library headers you include in your code. What are you going to do when cindex/libclang or whatever chokes on a libstdc++ header? You also have the issue with builtin macros: are they are the same in your library parser? Most likely not. Good luck testing all that.
Two-stage compilation is just a bonus on top: you add a sequential dependency in your build graph and if you have enough of these parsing programs you are going to wait till they are all built before your build can go wide.
24 minutes ago [-]
psyclobe 44 minutes ago [-]
Absolutely spot on, easier, and way more effective
ironman1478 2 hours ago [-]
Meta programming in C++ can enable you to remove lots of runtime branching in your code at the cost binary size.
wat10000 39 minutes ago [-]
Why would I write a parser that almost-but-not-quite matches the compiler's own parser, when I could just use the compiler's parser directly? I don't want to write a parser, and I especially don't want to debug weird corner cases where my implementation diverges somehow. I just want to write some code that goes like, for each field in T, do X.
C++ metaprogramming is bad, but the problem there is the C++ part, not the metaprogramming-in-the-language part.
HarHarVeryFunny 8 hours ago [-]
No doubt reflection has been built with other use cases in mind, but it sure would have been nice just to have std::to_string(enum)
bluGill 7 hours ago [-]
C++ conference speakers (including keynotes) are now begging everyone to stop using enum to string in their example. While they are a simple and easy to understand example, reflection is for much more interesting problems. I can't think of any other example that I would type into a comment box or put on a slide.
maccard 7 hours ago [-]
Serialization is the canonical example. Being able to turn
struct MyStruct {
int val = 42;
string name = "my name";
};
into
{
"val": 42, // if JSON had integers, and comments of course
"name": "my name",
}
is incredibly powerfuly. If reflection supported attributes (i can't believe it shipped without, honestly), then you could also mark members as [[ignore]] and skip them.
(The link above shows ImGui generation, but the same exact logic can be applied for serialiation to JSON/YAML/whatever.)
maccard 5 hours ago [-]
Sure, but
> The magic sauce? Boost.PFR! An incredibly clever library that enables reflections on aggregates, even in C++17.
That's not vanilla C++!
SuperV1234 5 hours ago [-]
...so what? It's just a header you have to #include.
maccard 2 hours ago [-]
By that logic why would anything have to be standardised?
gpderetta 1 hours ago [-]
The question is whether something belong in the language or in a library (possibly the standard library).
A guiding principle of C++ is that if something can be implemented cleanly and efficiently in a library, the language should not be extended to support the use case.
Now boost.pfr is exceedingly clever, but relying on speculative pack expansions or using stateful metaprogramming hacks is not something I would call clean and efficient, so proper reflection is warranted.
I do worry about the compile time impact though.
bluGill 6 hours ago [-]
It is powerful, but I'm not sure it is a good idea. Other languages have it, and there is lots of experience in all the ways things go wrong in the real world. I'm inclined to say you should hand write this code because eventually you will discover something weird anyway.
electroly 6 hours ago [-]
Can you give an example of a language ecosystem that went with reflection-based JSON serialization/deserialization and then went on to regret it? I can't think of any, and don't agree with your conclusion. It works great, and manually writing serialization and matching deserialization code is terrible, annoying, error-prone work.
maccard 5 hours ago [-]
I disagree. Rust's defacto default is serde, golang comes with batteries included, dotnet/java have had it for _years_, and all the dynamic languages do it.
SuperV1234 6 hours ago [-]
I think this is a very bad take -- once you write it by hand you have to manually keep it in sync with the actual struct and ensure you made no mistakes. Reflection guarantees 1-1 future-proof mapping with the actual C++ struct, avoids boilerplate, and ensures that the serialization logic is correct.
bluGill 5 hours ago [-]
The protocol is important though, not the internal structure. When you only have exactly one version of a program talking to the same version of itself you don't care. However when you are mixing versions or worse programming language (and thus can't mix structs which are implementation details of your language) the protocol is what matters.
That is if you are worried about doing this by hand reflection is not the answer, something like protobuf where your data structures are generated is the answer.
gpderetta 1 hours ago [-]
I completely understand your point. Then again you might be able to use reflection to verify that your manually rolled implementation actually serializes all fields.
cogman10 7 hours ago [-]
It comes up pretty frequently in java. Serialization/Deserialization, adding capabilities based on type, Adding new capabilities to a type, general tuning (for example, adding a timing or logging call onto methods).
Almost all the Java web frameworks are giant balls of reflection. Name a function the right way or add the right magic annotation and the framework will autowire it correctly.
It's a pretty powerful tool. (IDK if C++'s reflection is as capable, but this is what was enabled by java's reflection).
SuperV1234 6 hours ago [-]
Java reflection is another beast altogether as it is runtime reflection. C++26 reflection is purely compile-time, which not only means it adds zero runtime cost, but also prevents those kind-of-insane use cases you see in Java and C#.
david422 6 hours ago [-]
> Almost all the Java web frameworks are giant balls of reflection. Name a function the right way or add the right magic annotation and the framework will autowire it correctly.
I find this to be very powerful, and also very unintuitive/undiscoverable at the same time.
cogman10 6 hours ago [-]
Initially, but it very quickly becomes discoverable once you are familiar with how things are working.
Most frameworks in Java are very similar. The ones that aren't are effectively doing what "expressjs" does in terms of setup, which is still pretty discoverable.
Most java frameworks rely on annotations rather than naming schemes which makes everything a lot easier to grok.
kuboble 6 hours ago [-]
Reflection is simply a syntax vinegar for duck typing.
theICEBeardk 2 hours ago [-]
I mean a readable implementation of tuple with minimal overhead is a great case for me (went from around 1.6k lines to approximately 250 lines). I wrote an implementation including the normally difficult to implement tuple_cat based on c++26 within a few hours.
My favorite thing is that I will get to remove and replace most of the cryptic template recursion stuff I have with "template for" and maybe a bit of reflection. Debugging the unrolled stuff will be a joy in comparison.
surajrmal 7 hours ago [-]
Anybody the derive traits rust has are a good demo.
jsd1982 7 hours ago [-]
I think the conclusion section should indicate that they are based entirely on GCC 16's behavior and current implementation. We should avoid generalizing one compiler's behavior and performance. Curious how this same test would behave once clang ships C++26 reflection.
SuperV1234 6 hours ago [-]
I explicitly mentioned that GCC 16.1 was the compiler used in the benchmarking section, do you think I also need to add a disclaimer in the conclusion section as well?
Regardless, I don't think things are going to differ much with Clang. Without PCH/modules, standard header inclusion is still the "slow part" of C++ compilation, regardless of the compiler used and the standard library used (libstdc++ vs libc++). `#include` is fundamentally the same on any modern compiler.
Because the reflection feature itself seems quite fast on GCC (compared to the cost of the header), I predict the results will be similar on Clang as well.
bluGill 7 hours ago [-]
I was thinking the same thing. Modules are still not widely used, it is a reasonable guess that there are a lot of optimization opportunities left.
SuperV1234 6 hours ago [-]
That is true, but on the other hand Modules were standardized more than 6 years ago.
Promises and claims have been made for longer than that on how Modules would have improved compilation times and made everyone's lives easier. In 2026, I still have to see any real evidence of that, especially when PCH + unity builds are much easier to use (except on damn Bazel, which supports neither) and deliver great results.
If after 6+ years of development Modules are still so far behind, it is fair to question if the problem is with the design/implementability of the feature itself.
spacechild1 47 minutes ago [-]
> it is fair to question if the problem is with the design/implementability of the feature itself.
The module story is just insane. How was it possible to get such a big feature into the standard without any working reference implementation? Isn't this the requirement for standard proposals to get accepted? If I compare this with how they treated JeanHeyd and his #embed proposal, the difference is staggering. To me it seems like a few powerful comittee members wanted to get modules into C++20 at any cost. This was just irresponsible.
miguel_martin 3 hours ago [-]
I agree with some other's in this thread: this is example is not great, but I get why it was used: to compare with X-macros. How about something that would require code-generation e.g. via libclang?
My guess is: libclang is more suited for this situation if you care about compile times, even if Python is used.
randusername 7 hours ago [-]
I can't imagine myself using reflection much, but maybe it will eliminate a lot of feature proposals bogging down the committee and they can focus on harder problems.
It would be cool if the stated goal of C++29 was compile times.
w4rh4wk5 7 hours ago [-]
I'd argue reflection is very much a feature for libraries. You wouldn't use it directly, but your JSON / YAML serialize is then built on top of it. So are your bindings for scripting engines like Lua.
SuperV1234 6 hours ago [-]
You can already automatically serialize/deserialize arbitrarily nested structs since C++17 (using Boost.PFR). Since C++20, you can also serialize/deserialize the struct data member names automatically.
There are a lot of things that are very very important for a tiny niche. In any non-trivial project you will end up with a lot of custom libraries and some of them really benefit from some obscure feature that no place else in your project would want.
agentultra 6 hours ago [-]
Also nice for UI tooling; game tools, debuggers, etc. Pull apart a struct and display it on screen and not have to patch the UI tool every time you change the struct is pretty nice.
I don't see how a library like Enchantum could handle everything reflection does. (How) does it figure out duplicate enum values, for example? And (how) does it discover arbitrarily large, discontiguous ranges? And (how) does it do these on MSVC?
SuperV1234 6 hours ago [-]
In short, it probes enum values in a pre-defined range (e.g. [-256; 256]), and parses the `__PRETTY_FUNCTION__` macro at compile-time to extract the name of the enumerator.
Once you have that in place, you can easily detect duplicates, etc...
Curious to see if Epic Games ever refactors their reflection in Unreal Engine to use C++ 26 reflections or not.
psyclobe 3 hours ago [-]
Man that aucks was looking forward to some kind of speed improvement. Using magic enum atm and I guess we'll continue to do so.
C++ build times are hard pill to swallow when migrating from c. This is just another reason we'll probably stick to writing c as t the company where I work. It's like asking someone to give up instant compilation for cleaner easier to read apps?
Also now that we have cleanup handlers in c (destructors) even less of a reason to move...
king_geedorah 8 hours ago [-]
Another win for X macros and for C style in general, though the author didn’t declare it as such.
spacechild1 42 minutes ago [-]
The downside is, of course, that it's ugly and very awkward to use.
That's the essence of C++: you're basically trading ergonomics for compile times.
uecker 5 minutes ago [-]
Are X macros awkward? I find them very straightforward and clear.
SuperV1234 7 hours ago [-]
Author here. It isn't a clear "win" at all, there are tradeoffs to each approach.
TZubiri 7 hours ago [-]
"Enum to string"
We've come full circle huh?
Why do you need this, logging? In that case I would rather reflect the logging statement to pribt any variable name, or hell, just write out the string.
If saving for db, maybe store as string, there's more incentive for an enum in the db, if that's a string you might as well. At any rate it doesn't seem a great idea to depend on a variable name, imagine changing a variable name and stuff breaks.
SuperV1234 6 hours ago [-]
Logging, debugging, auto-generation of UIs/editors, etc... This is an extremely common operation and for a good reason.
So speaking of old ways, I'm not a C++ dev, but a while ago saw someone mentioned that they still organize their C++ projects using tips from John Lakos' Large-scale C++ software design from 1997, and that their compile times are incredibly fast. So I decided to find a digital copy on the high seas and read it out of historical curiosity. While I didn't finish it, one wild thing stood out to me: he advised for using redundant external include guards around every include, e.g.
The reason for this being that (in 1997) every include required that the pre-processor opened the file just to check for an include guard and reading it all the way to the end to find the closing #endif, causing potentially O(N*2) disk read overhead (if anyone feels like verifying this, it's explained on pages 85 to 87).Again, that was in 1997. I have no idea what mitigations for this problem exist in compilers by now, but I hope at least a few, right?
This conclusion is making me wonder if following that advice still would have a positive impact on compile times today after all though. Surely not, right? Can anyone more knowledgeable about this comment on that?
When I saw the 'no boilerplate' example, the very first thought that came to my mind:
This is the ugliest, most cryptic and confusing piece of code I've ever seen. Calling this 'no boilerplate' is an insult to the word 'boilerplate'.
Yeah, I can parse it for a minute or two and I mostly get it.
But if given the choice, I'd choose the C-macro implementation (which is 30+ years old) over this, every time. Or the good old switch case where I understand what's going on.
I understand that reflection is a powerful capability for C++, but the template-meta-cryptic-insanity is just too much to invite me back to this version of the language.
Why? The implementation is not pretty, but you only need to write it once and then it works for all enums. The actual usage is trivial, it's just a function call.
The C macro version is horrendous in comparison. Why would I want to declare my enums like that just because I might want to print them?
Seeing this argumentation is so tiresome, because it feels like there is a lack of self-awareness regarding what is "familiar" and what isn't, which is subconsciously translated to "ugly" and "bad".
In a lot of languages, you achieve the same with 1 line of code. It's not about familiarity, it's about the fact that it's a long and convoluted incantation to get the name of an enum.
Why do I have to be familiar with all those weird symbols just to do a trivial thing ?
Update:
Zig:
const Color = enum { red, green, blue };
const name = @tagName(Color.red); // "red"
Rust:
#[derive(Display)]
enum Color { Red, Green, Blue }
let name = Color::Red.to_string(); // "Red"
Clojure:
(name :red) => "red"
As far as I understand you would have to mess with individual parser tokens in Rust instead of high-level structures like "enum" (C++ reflection). It would be much, much uglier to implement anything like "to_enum_string" in Rust as you would have to re-implement parts of the compiler to get the "enum" concept out of a list of tokens.
You won't have to care about ^^ and [:X:] if you just want to consume reflection-based utils, which was the whole point of my comment.
And template for but I assume that's like inline for like in zig.
Not familiar with Zig but AFAICT `inline for` is about instructing the compiler to unroll the loop, whereas `template for` means it can be evaluated at compile time and each loop iteration can have a different type for the iteration variable. It's a bit crazy but necessary for reflection to work usefully in the way the language sets it up.
https://ziglang.org/documentation/master/#inline-for
A for loop executed during comptime is just
The difference is that a comptime block won't leave behind runnable 'residue', only whatever data is constructed for later. An inline for might not leave behind an unrolled loop either, but it can.But there is also good news that with the advent of JIT like components for compile time evaluation in progress and the like of CLion having the beginnings of a compile debugger in combination with concepts there is a chance some help is available and on the way.
However right now you have to rely on compiler errors and static_asserts which is not ideal of course.
In practice, I haven't really needed to ever debug `consteval` functions -- it's quite easy to get the right behavior down thanks to `static_assert`-based testing and thanks to the fact that they do not depend on external state (simpler).
For one thing they are required to disallow all undefined behavior for compile time execution, and some forms of UB only occur when the code is run.
I never felt the need for them when doing TDD.
I program mostly in C, if I need 'meta' programming I just write another C program that processes C source code (I've written a simple C parser), then in my build script I build in two stages, build meta program, run it, build rest of program.
Simple, effective, debuggable (the meta program is just normal C), infinite capabilities - can nest this to arbitritary depths, need meta-meta programming? Make a program that generates a meta program.
Without taking a stance on whether in-language meta programming facilities are good or bad, it’s not hard to find examples of cases where people find it useful to have them.
But you're probably not doing s ton of metaprogramming all the time like you should be, and would with a language that allows it.
The lack of metaprogramming is also why C is so slow compared to C++
Two-stage compilation is just a bonus on top: you add a sequential dependency in your build graph and if you have enough of these parsing programs you are going to wait till they are all built before your build can go wide.
C++ metaprogramming is bad, but the problem there is the C++ part, not the metaprogramming-in-the-language part.
(The link above shows ImGui generation, but the same exact logic can be applied for serialiation to JSON/YAML/whatever.)
> The magic sauce? Boost.PFR! An incredibly clever library that enables reflections on aggregates, even in C++17.
That's not vanilla C++!
A guiding principle of C++ is that if something can be implemented cleanly and efficiently in a library, the language should not be extended to support the use case.
Now boost.pfr is exceedingly clever, but relying on speculative pack expansions or using stateful metaprogramming hacks is not something I would call clean and efficient, so proper reflection is warranted.
I do worry about the compile time impact though.
That is if you are worried about doing this by hand reflection is not the answer, something like protobuf where your data structures are generated is the answer.
Almost all the Java web frameworks are giant balls of reflection. Name a function the right way or add the right magic annotation and the framework will autowire it correctly.
It's a pretty powerful tool. (IDK if C++'s reflection is as capable, but this is what was enabled by java's reflection).
I find this to be very powerful, and also very unintuitive/undiscoverable at the same time.
Most frameworks in Java are very similar. The ones that aren't are effectively doing what "expressjs" does in terms of setup, which is still pretty discoverable.
Most java frameworks rely on annotations rather than naming schemes which makes everything a lot easier to grok.
My favorite thing is that I will get to remove and replace most of the cryptic template recursion stuff I have with "template for" and maybe a bit of reflection. Debugging the unrolled stuff will be a joy in comparison.
Regardless, I don't think things are going to differ much with Clang. Without PCH/modules, standard header inclusion is still the "slow part" of C++ compilation, regardless of the compiler used and the standard library used (libstdc++ vs libc++). `#include` is fundamentally the same on any modern compiler.
Because the reflection feature itself seems quite fast on GCC (compared to the cost of the header), I predict the results will be similar on Clang as well.
Promises and claims have been made for longer than that on how Modules would have improved compilation times and made everyone's lives easier. In 2026, I still have to see any real evidence of that, especially when PCH + unity builds are much easier to use (except on damn Bazel, which supports neither) and deliver great results.
If after 6+ years of development Modules are still so far behind, it is fair to question if the problem is with the design/implementability of the feature itself.
The module story is just insane. How was it possible to get such a big feature into the standard without any working reference implementation? Isn't this the requirement for standard proposals to get accepted? If I compare this with how they treated JeanHeyd and his #embed proposal, the difference is staggering. To me it seems like a few powerful comittee members wanted to get modules into C++20 at any cost. This was just irresponsible.
For example, what does https://miguelmartin.com/blog/nim2-review#implementing-a-sim... look like with C++26's std::meta::info?
My guess is: libclang is more suited for this situation if you care about compile times, even if Python is used.
It would be cool if the stated goal of C++29 was compile times.
For many useful use cases, you don't need C++26 reflection at all. E.g. https://www.linkedin.com/posts/vittorioromeo_cpp-gamedev-ref...
Once you have that in place, you can easily detect duplicates, etc...
Of course, there are major limitations, as it's all a big hack: https://github.com/ZXShady/enchantum/blob/main/docs/limitati...
Similarly interesting is Boost.PFR, which gives you reflection superpowers since C++14: https://github.com/boostorg/pfr
C++ build times are hard pill to swallow when migrating from c. This is just another reason we'll probably stick to writing c as t the company where I work. It's like asking someone to give up instant compilation for cleaner easier to read apps?
Also now that we have cleanup handlers in c (destructors) even less of a reason to move...
That's the essence of C++: you're basically trading ergonomics for compile times.
We've come full circle huh?
Why do you need this, logging? In that case I would rather reflect the logging statement to pribt any variable name, or hell, just write out the string.
If saving for db, maybe store as string, there's more incentive for an enum in the db, if that's a string you might as well. At any rate it doesn't seem a great idea to depend on a variable name, imagine changing a variable name and stuff breaks.