February 10th, 2025

Fun with C++26 reflection: Keyword Arguments

The blog post explores implementing order-independent keyword arguments in C++26 using reflection, discussing limitations of existing methods, proposing new techniques, and highlighting challenges in parsing lambda captures for implementation.

Read original articleLink Icon
Fun with C++26 reflection: Keyword Arguments

The blog post discusses the implementation of order-independent keyword arguments in C++ using the proposed reflection features of C++26. The author shares insights gained from experimenting with these features, highlighting the potential of reflection to enhance C++'s argument handling capabilities. The post outlines various methods to achieve keyword argument-like syntax, starting with designated initializers introduced in C++20, which allow for a form of keyword arguments but come with limitations such as requiring extra type definitions and maintaining order sensitivity. The author then presents a more flexible approach using helper objects and operator overloading to create a syntax resembling traditional keyword arguments. The discussion progresses to a reflective approach, where keyword arguments can be collected into a named tuple, allowing for optional and order-independent arguments. The author also introduces a macro to simplify the creation of keyword argument tuples and explores the use of lambda closures to manage captures as keyword arguments. The post concludes with a discussion on parsing lambda capture lists to facilitate the implementation of keyword arguments, emphasizing the complexity of parsing C++ syntax correctly. Overall, the post serves as a technical exploration of enhancing C++ function calls through innovative use of reflection and syntactic sugar.

- The blog explores implementing keyword arguments in C++26 using reflection.

- It discusses limitations of existing methods like designated initializers and proposes new techniques.

- The author introduces helper objects and macros to simplify keyword argument syntax.

- Reflection allows for order-independent and optional keyword arguments.

- The post highlights challenges in parsing lambda captures for keyword argument implementation.

Link Icon 18 comments
By @anon-3988 - 3 months
I just don't understand why some people are so fascinated by this. Can you all admit that this is not at all practical? I swear C++ folks like it for the sake of it. No other engineer do this. Only antiques people or whatever.

Can you imagine an engineer that is adamant on using his mystifying bespoke tool instead of just using a ruler. "But what if I have to measure it in the 4th dimension!?".

I was expecting something simple but good Lord, its kwargs. Not some magical asynchronous runtime.

inb4 there are still corner cases so you can't just say "users don't have to know the implementation details so only one person has to suffer". I bet money this abstraction is leaky.

Why can't you just do this at the language level like any sane person?

By @quietbritishjim - 3 months
This is all, as the title suggests, good fun, but I wouldn't use it for any real code. Instantiating the parameter struct on a separate line adds only a small amount of extra boilerplate at the call site, in return for which it's a million times easier to read than any of these tricks.

   FooArgs fooArgs;
   fooArgs.y = 4;
   foo(fooArgs);   // Didn't set .x so it has default value
Three lines instead of one seems like a lot of overhead, but in practice you would only bother for a function that takes loads of arguments so the extra overhead is really much smaller.

----

Smaller points:

Are the fields in FooArgs really initialised if they're not explicitly set? I can believe they are, after all it's brace initialisation. But IMHO that code isn't super obvious. I'd be more comfortable if they had default member initialisers, i.e., "int x = 0; int y = 0;" in FooArgs. In my version above, you really do need these (unless you remember to brace initialise).

It took me a while to see why they bothered to have a string template parameter for TypedArg in the first usage. It prevents mixing up two arguments: if TypedArg didn't have that, then you could call foo(y=3, x=2) and it would compile but have the effect that the parameter x would be 3 and y would be 2.

By @gpderetta - 3 months
If you are willing to use macros anyway, you can make the following work in C++ today:

   foo($(bar)=10, $(baz)="hello");
In fact you could 10 years ago when I implemented it[1]; in fact it allows significantly more than just named arguments (named tuples!), but please, consider it as some sort of art and not really something that should be anywhere close to production.

[1] https://github.com/gpderetta/libtask/blob/a5e6e16ddc4e00d9f7...

By @feverzsj - 3 months
There are tons of more insightful examples in the proposal[0]. C++26 reflection could replace most meta programming tricks, though the syntax isn't the most pleasant.

[0]: https://isocpp.org/files/papers/P2996R9.html

By @p0w3n3d - 3 months

  foo({.x=2, .y=2})
I remember using this syntax in C in my 2017 project. This is very clear to call methods like that, I used it with minor #defines, i found the inspiration in the book "21st century C."
By @nialv7 - 3 months
I am scared by what C++ people think is fun.
By @knorker - 3 months
I love C++. I've coded in C++ for like 30 years at this point. C++11 breathed new life into the language, to the point where it's a different and much better language now.

But some of these new features… I feel like they're a bit desperate attempts at fitting in with the kids.

Not to start a language war, but I don't see how any attempt at stapling modern features onto C++ make it a good choice in 2025. There are at least two viable plug in replacements, that have good interop.

Like I said, I've coded C++ for 30 years (along with other languages, sure), so I'm not a fad follower. I don't say it lightly, but I do say that coding C++ in 2025 means creating technical debt. And these features won't change that.

By @BodkinsOdds - 3 months
Needing that MakeArguments macro makes this substantially worse than just defining an aggregate struct for your arguments and using designated initializers. I've never wanted to reorder named arguments anyway, I've only ever wanted to elide some of them.
By @oilkillsbirds - 3 months
Poor, poor C++... cries in C
By @thom - 3 months
C++ reflection is now good enough that hopefully we’ll start to see more game engines using it to work out components and properties instead of weird macros. jcelerier’s work on things like Avendish really does feel quite fresh and modern, which is not my usual reaction to C++ frameworks. Obviously it’s lagging a good 20 years behind C# et al but we’ve come a long way since IUnknown.
By @prabhu-yu - 3 months
I liked the syntax of key word arguments in Python. Then asked myself on how come this is not popular in C/C++ world. After seaching internet, found the way and documented here.

https://prabhuullagaddi.substack.com/p/simulation-of-keyword...

By @maleldil - 3 months
IIUC, the very first option (designated initialisers with custom structs per function) is how Zig does it, and it seems to work well enough there. It's verbose, so I wouldn't use it for everything, but it doesn't seem all that unreasonable.
By @pipeline_peak - 3 months
Reflection always seems like a convenient way for programmers to write less code at the cost of performance overhead. Also the type of trivial code that could be easily generated by an LLM anyway.

Things like serializers and MVC event bindings come to mind.

By @hoseja - 3 months
I don't want "powerful reflection features" that are coincidentally another turing-complete system. I just want enums to be nice and Qt moc functionality ;_;
By @LeicaLatte - 3 months
Might be a fun exercise to write a serialuzation/deserialization library using these features.
By @injidup - 3 months
Vscode and visual studio now add parameter inlay hints so

Foo(10, 20)

In your code gets rendered in the IDE as

Foo(X=10, Y=20)

Which solves a subset of the problems named parameters solves, namely the readability of the code.

By @lallysingh - 3 months
I'm disappointed to see the responses here. Reflection in C++ has been a wiggly sack of cats for decades. Of course it has to be all conpile-time. If you want to reify it, this is the way you get to choose the runtime version you like.

And, being conpile-time, it ain't gonna be pretty. The new operator is nice. They found something that'll work compatibly with existing code. This is not easy stuff.

By @mgaunard - 3 months
why not just synthetise a matching aggregate as well as a function that takes that aggregate and forwards it to the normal function?