Skip to content(if available)orjump to list(if available)

Fun with C++26 reflection: Keyword Arguments

anon-3988

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?

mcdeltat

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

The reality is C++ is a ridiculously complex and legacy-ridden language, with a difficult goal to preserve backwards compatibility. I haven't read the history on the keyword args proposals but I'm guessing they were declined due to a deluge of silly edge case interactions with C++ semantics that became too hard to work around. Like how struct designated initialisers have to be in order of member declaration due to object lifetime rules or something like that.

I would recommend trying to not be outraged at the state of C++ these days. It's time to stop hoping that C++ gets the nice features we need in any sort of reasonable manner. The reality of the language is not compatible with much niceness.

Night_Thastus

Personally, I write the simplest subset of C++ I possibly can at all times. Loops, variables, classes and the STL library when useful.

The more advanced features you use, the easier it is to make subtle mistakes, confuse someone who hasn't seen that feature before, or just make everyone working on the software wary of ever touching it.

arjonagelhout

I do the same thing. C++’s feature is incredibly broad, and sprinkling in just a bit too much templating can result in code that is cleaner, but harder to reason about when reading it back after a month.

For example, using C++ 20 ranges over simple loops is an example of where brevity and “cleanliness” can reduce clarity because the heavy templating and operator overloading hides what the code actually does.

On the other hand, sometimes one can’t avoid the complexity when writing a library that aims to work on many platforms, and with special tricks such as SIMD, as is present in the Eigen library.

NL807

Honestly, all I ever want is to be enumerate a struct data member or an enum at compile time, and be able to get the name, type and value of each iterated member. That's it. That's all I want.

Gibbon1

Would be happy with a enum that's a named key value.

    key_enum one_two_three 
    {
       FIRST = { .name = "first"},
       SECOND = { .name = "second"},
       THIRD = { .name = "third"},
       LAST = { .value = 255, .name = "last"},
    };

SideQuark

Reflection is such an insanely useful tool that pretty much all modern languages have it. As usual C++ is 20 years behind language design and brings terrible syntax.

But it is tremendously useful.

https://en.wikipedia.org/wiki/List_of_reflective_programming...

sky2224

Isn't the reason why so many other languages have reflection due to the fact that they essentially have the information there from the get-go?

For example, C# and Java have their intermediate language that has a bunch of meta information attached to it already or the info can be added easily if need be. Is C++ not more raw in its compilation process from code to object files? I mean sure, I guess meta-data could be added to the object files themselves for runtime inspection, but if it were really that simple, why wouldn't they have just added it if reflection is indeed that much of a sore spot for the language's purpose?

Now, I'd like to add, my understanding of compiler internals is quite limited, so if I'm way off base here, please correct me.

Additionally, while I too find reflection useful, I've seen a lot of cases where it ends up being a bandaid for poor design and gives people an all too enticing shovel to dig themselves deeper into a hole of technical debt. When used correctly, reflection can be quite elegant, but I think those cases are seldom advantageous in comparison to a different and likely better design approach.

arjonagelhout

When I started out programming in C#, I used reflection sometimes to circumvent the language’s design and restrictions. This resulted in brittle and hard to reason about code. Reflection should never be used to do this.

So I do think you’re right that reflection can (and will) be abused by beginners if present as a language feature.

With that said, I don’t know much about the internals of the C++ compiler, but having built a simple reflection system for C++, I think the important thing is just being able to serialize and deserialize POD structs to and from some representation (e.g. json). For more advanced data, such as images or specific binary (file) formats, it’s easier to write custom writing / reading, encoding / decoding logic.

jcelerier

C++ compilers also had this information since forever(basically reflection is giving to the end user some level of access to the AST nodes information) - the objections against reflection were always more political or about end-user design of the feature.

logicchains

>Isn't the reason why so many other languages have reflection due to the fact that they essentially have the information there from the get-go?

C++26 reflection is compile-time reflection, not runtime reflectiom, it doesn't require any extra information in the binary. It just uses information that the compiler already has at compilation time.

hyperhello

At this point, you should just switch to JavaScript. The desperation to have the simple ease of JavaScript without having to say you're going near "that terrible language" is twisting C++ into ridiculous loops. I'm serious. It's like a bizarre Victorian relic now.

drivebyhooting

Is JS simpler than Python? I don’t think so but willing to change my mind.

hyperhello

The syntax? Probably a little. It’s a typeless C. As for what you can use it for, it’s limited to browsers, practically, so not really comparable.

forrestthewoods

Reflection is spectacularly useful and sorely missed in C++

But because this is C++ the committee designs the most insane and terrible version of reflection possible

Guthur

Because they know the cost of everything but the value of nothing.

BodkinsOdds

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.

quietbritishjim

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.

p0w3n3d

  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."

prabhu-yu

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...

feverzsj

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

oilkillsbirds

Poor, poor C++... cries in C

anitil

I've seen cases where people do things like

  my_func(/*arg1=*/val1,/*arg2=*/val2)
And I suppose you could write a validator to make sure that this worked. Or using an anonymous structure in C99, or a named structure in C89. And of course a pointer if you care about register/stack usage etc.

I'm not sure what the other options are.

wffurr

>> I suppose you could write a validator to make sure that this worked

No need to write it:

https://clang.llvm.org/extra/clang-tidy/checks/bugprone/argu...

Efb

tjalfi

C++20 added designated initializers, so they're also an option.

    my_func({.arg1 = val1, .arg2 = val2});

anitil

Oh I thought that was a C99 addition? It's been a while since I've used them.

Edit: struggling to find a source, though this GCC doc suggests it's C99 (but maybe that's only GCC?) - https://gcc.gnu.org/onlinedocs/gcc/Designated-Inits.html

brandmeyer

> And I suppose you could write a validator to make sure that this worked.

Like this one!

https://clang.llvm.org/extra/clang-tidy/checks/bugprone/argu...

ranger_danger

at that point why not just pass in a struct as your argument

anitil

I suppose it depends on your application. I worked in embedded devices where stack was limited and the compiler was .... less than reliable about how it would manage passing a large struct to your function, which is why I mentioned passing in a pointer instead

jcelerier

depending on the platform this may cause things to be pushed / poped on the stack instead of being passed as registers

nialv7

I am scared by what C++ people think is fun.

mgaunard

why not just synthetise a matching aggregate as well as a function that takes that aggregate and forwards it to the normal function?