Hacker News new | past | comments | ask | show | jobs | submit login
Fun with C++26 reflection: Keyword Arguments (pydong.org)
136 points by HeliumHydride 4 days ago | hide | past | favorite | 137 comments





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?


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


>Like how struct designated initialisers have to be in order of member declaration due to object lifetime rules or something like that.

It matters in which order sub-objects are initialized - if you have a class with the members A and B, and B takes pointer or reference to A in its constructor and does something with A, A better be already initialized. Sub-objects are initialized in the order of their declaration and having different order of designed initializers would be confusing. In fact, exactly that problem we have in C++ with the list of initializers of base classes and data members in constructors.


You still don't need to syntactically require same order initialization, it's an easy job for a compiler to reorder things so that all dependencies work out - every language with order independent declarations have to do that for example.

You don't need to require same-order initialization, but allowing people to do different orders will be confusing when actions are reordered behind the scenes. Especially imagine if there are dependencies between the objects you're passing in.

  struct A {
    B one;
    C two;
  };
  struct B {
    B() {
      cout << "init B" << endl;
    }
  }
  struct C {
    C() {
      cout << "init C" << endl;
    }
  }

Mixing up the order is confusing:

  A{.two=B(),.one=A()}
since `two` is initialized after `one` despite coming before (the comma operator <expr a>, <expr b> usually means `expr a` happens before `expr b`.

This case is a little contrived, but run the evolution forward: you can have members that depend on each other, or have complex initialization logic of their own. There, debugging the specific order of events is important.


Scala does it by pulling out the keyword arguments to variables so that your example would become

{ val x$1 = B() val x$2 = A() A(.one = x$2, .two = x$1) }

This maintains left-to-right evaluation order while allowing you to pass arguments in any order.

There is probably some dark and forbidden reason why C++ can't do that.

ETA: That's basically what the post does.


In a general case you can't do that with separate compilation. [0]

  struct A { A(A*); };

  A* f(struct B *b);

  struct B {
    A a1;
    A a2;
    B(): a1(f(this)), a2(f(this)) {}
  };

  //in a different translation unit
  A* f(B *b)
  {
    return &b->a1; //or a2, we don't know
  }

[0] https://godbolt.org/z/xMb64ssYK

Your code snippet does not use the designated initializer feature that this comment thread is talking about.

Furthermore your code possibly contains undefined behavior depending on the behavior of the constructor of A.


You can't easily take the pointer to an other member in a designated initializer. It's still a problem for members that are implicitly initialized by their default member initializer, but that can be sorted out.

I think it is easy enough to be a potential footgun [0]:

  struct A { A(A*);};

  struct B {
    A a1;
    A a2;
  };

  void f()
  {
    B b{.a1 = A(nullptr), .a2 = A(&b.a1) };
  }

[0] https://godbolt.org/z/cGaxzh17T

That's a stretch to call it "easy enough", you are explicitly pointing the gun at your foot. That `b.a1` might not be explicitly UB, but that's quite suspect when b's lifetime didn't start yet. Accessing members through `this` in constructors have some special allowance to not make that UB.

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.


I have almost entirely removed loops from my definition of the simple subset of C++. Turns out that in nearly every case anytime I think loop I'm able to find a STL algorithm that does the same thing in a more expressive way.

I do use lambdas all the time, they are very powerful and so worth learning the complex syntax to make them work. I strongly recommended you add them to your list of things you use all the time (replacing loops)

I use templates, but only when I'm writing generic code. Templates in the right place can save a lot of code and errors. However they are rarely needed and so you only need a few people on a large project to write them, and everyone else can say "not in my subset of C++, talk to [someone else]"

> more advanced features you use, the easier it is to make subtle mistakes, confuse someone who hasn't seen that feature before,

Also the more people get used to seeing it. Soon everyone knows those things, and so they won't make the subtle mistakes of be confused. Of course you need to pick the right things to add. Ranges look really useful for people who work on a different type of problem from the type I normally work on - thus ranges are not in my subset of C++, but my impression is they should be very common in other areas. Modules are not currently on my list, but everything I know suggests in 5 years the tools will (finally!) work and I will be converting my code to modules. I'm not clear where reflection sits, I suspect like templates a few experts will be needed on a large project and everyone else just uses them - but only time will tell.


>Also the more people get used to seeing it.

This is a great idea in theory, but doesn't always hold up well. With a sufficiently large and old project, and when people move on and off either parts of the code or the entire project regularly...it doesn't hold up as well.

No one may see or touch that code again for years. By the time someone realizes there's a missed edge case or it needs expansion, it may simply be too late. The person that worked on it may be gone completely or haven't worked on that part of the code in those years.


Remember the master, no raw loops!. :)

No raw loops, no new/delete (malloc/free). The two keys to making C++ a better language for you. You can still run into a lot of foot guns, but the above rules two eliminate a large portion of them.

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.


>hides what the code actually does.

As does any function call.


God forbids! I want functions to hide HOW is it done, not WHAT. One thing is abstraction, the other is obfuscation.

When you add "actually" to "what" it becomes "how")

What the code using C++ ranges is doing is quite clear, but understanding how it is doing it requires some knowledge of how ranges work.


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

That stating that is not stating the obvious, shows how screwed we are. Indeed, why use things that are not necessary? "Just because you can"?

And still I find everywhere programs less than 1000 lines of code, that can be written maybe in 10 lines of python, but in C++ they use templates, inheritance, and the most obscure corners of the STL. Mind you, I do not mean some random github repo. where somebody was just practicing and learning. I mean production software, made "professionally"


Those 1000 lines of what can be 10 in python are a good thing when the total code base is 10million lines and you need all those obscure corners. However many people hear that and decide that everything should be the 1000 line template/inheritance mess instead of 10 lines because their code base is multi-million lines line, when in reality most of your code will never need all the advantages the complex 1000 line solution gives.

There are places I have that 1000 line template mess that nobody can understand in less than a month of staring at it (including me and I wrote it!) because it is the right answer. Most of the time I don't write that because it is hard and there is no advantage, but once in a while it is the right answer to a complex problem. Those 1000 lines can turn many many 100 line problems into 5 line problems and so overall be a net reduction when done right.

The longer I write code the less use I have for inheritance. Templates are still useful.


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


Unfortunely so it is with anything committee design, including SQL, C and Khronos APIs as well, it is not only C++.

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

This is a nifty little gadget, not a production level feature. C++ lacks a lot of things, but it's objectively cool that you can hack some of them yourself (like kwargs). No one should use this in important code. This is just fun.

indeed

Apparently you have not been around many Lisp, Python, Ruby, Haskel, Scala,... folks.

This kind of geekyness is surely loved by many, not only C++.


Exploring like this is a way to get familiar with the feature, explore its limits and inform you on how to use it in the future.

It's curiosity, play and learning. It's great.


I think there's a humour to C++ being like Python in that, left alone, the language's community will almost immediately try and stuff as much syntax as possible into the language.

The difference is how much power the community has to turn these syntactic fever dreams a reality. With C++, you get first-class stuff like the STL and even the C preprocessor, and now reflection. With Python, it usually comes down to magic method abuse (see pathlib's use of `__div__`), maybe messing around with metaclasses, and the PEP process (though I think they're pumping the brakes at this point -- walrus was fun, but I really hope PEP 750 doesn't make it in, and it seems to me that it'll stall).

You technically have `ast`, but that'll only go so far since unless you really go overboard with your hack, you still have to write valid, parseable Python before `ast` will ingest it.


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.

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"},
    };

What is wrong with the following? Do you want type more type safety, e.g. linking the enumeration constants to the array?

  enum { FIRST, SECOND, THIRD };
  struct { char *name; int value; } table[] = {
    [FIRST] = { .name = "FIRST", .value = 0 },
    [SECOND] = { .name = "SECOND", .value = 1 },
    [THIRD] = { .name = "THIRD", .value = 3 },
  };

You still need generic code to link the enum type to its format string, for example for use in std::format or std::ostream.

Nothing really difficult, normally all of this is done by hand, which is tedious and error prone, or incorporated in a macro. It would just be nice if there was a standard provided turn-key solution to the problem.


I have yet to see a project where there have never been bugs due to someone forgetting to update the metadata attached with the enum when there's a change

magic_enum addresses your issue but will absolutely kill compile time on a large project due to recursive template instantiation.

The fascination i sort of get - sometimes rabbit holes are fun to jump down if you have time.

I'm working on a satisfactory node modeling tool for fun, and built one of the stores (I have a few different stores) using CRDT's and added collaborative support over webrtc. Is it practical? The result, yes. The process - no, it's a total waste of time and energy compared to much simpler alternatives. No question.

Is it fun? Yes. It has been a lot of fun.

Was it fascinating - yes, i learned a lot.

So the fascination i get - sometimes people do this stuff to learn more about it, or because they are jumping down a rabbit hole they like, or whatever.

At the same time, i totally agree with you that this article presents something wildly impractical as-is. But i'm not sure it was meant to be practical. I hope not, since it totally isn't :)

For C++ itself, this is why Google, and others, gave up (on the evolution of the language) after decades of trying. Or at least one reason. C++ exposes all this complexity because it's not willing to break anything, ever.

If you go look at the reflection paper, they use one single opaque reflection type, and the whole justification is that baking it into the language is dangerous because we might have to change it and that would break things. They give an example of something standardized in 2003 that would have broken if they gave more specific types. Note this is now proposed for 2026. To me, 23 years before breaking something would be a pretty reasonable thing. Especially if migration can be automated. Worse, this logic unfortunately can be applied generally - if you are never going to break anything, it severely restricts your ability to generate more useful interfaces, because you can't depend on anything that might ever change.

Which is of course, a losing war anyway - you won't accurately predict all the things that you will want to change, so either you get it wrong anyway, or you now actually block the language by what you did choose to depend on. So the interfaces will only get worse over time, as you are either restricted by the existing stuff not changing, or because you guessed wrong and became one of the reasons those existing things can't change. This has a very predictable end.

For every other language, they would just eventually break things, and figure out how to do effective language migration. When they do, you hope it's for something better enough to be worth it, but that's a product problem, and that the language migration takes care of ~all of it for you, which is an engineering one.


> C++ exposes all this complexity because it's not willing to break anything, ever.

That is also why people use C++ for large complex problems. Not doing that is why python3 took forever to replace python2. When you have a lot of code that works it isn't worth the money to rewrite it if you can help it.


Python2-3 is a worst case example where the benefits were small or negative compared to cost.

There are also plenty of very successful languages used for large complex problems and have broken compatibility in various ways over the years.

You are right nobody wants to pay to rewrite it but if the tooling takes care of it for you nobody cares about it.

Python 2-3 had tooling that, for larger customers and projects, often could not do even 50% of the conversion, and when it came out the other side, nobody felt it was really better.


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


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.


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


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.


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

Why not just use a visitor? (Genuine question, not c++ snark).


Takes a bunch of manual work per POD, that's the gist of it.

Fundamentally, if I have struct { int x = 3; std::string y = "bla"; }

I want the json to look like { x: 3, y: "bla" } without writing any code specific to the struct, and have it work bidirectionally. This is currently not possible because of a lot of reasons, but most trivially because the names "x" and "y" do not even exist anymore in your compiled code.


This is absolutely possible, even currently - albeit in a very much non-portable way. For example boost::pfr and my own (wip) repr library have the required machinery for this.

Interesting! There seem to be a lot of limitations though:

> Boost.PFR library works with types that satisfy the requirements of SimpleAggregate: aggregate types without base classes, const fields, references, or C arrays:

And in general seems to be dependent on C++20 for getting field names.

Do you know how this works? Initializer lists seem somehow involved.


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.

The article is about compile-time reflection, which is not nearly as useful as what the Wikipedia article you linked is about. It's only upside is that it is "zero cost" by some definitions of cost.

Runtime reflection on the other hand is useful but it also puts up obstacles not easy to overcome. For example it is one major reason we still have no decent ahead-of-time compilation in Java.


I'm in the opposite camp, runtime reflection is useless, and almost always points to a design flaw while compile time reflection is actually useful (for obvious things like automatically building a serialization layer or an UI which represents the type, or building types from other types).

I'm sure that C++26 has implemented it in a way which is highly unpleasant to use though ;)


The element of runtime reflection that is the most useful is something along the lines of "here's function, here's a list of arguments, go call this function with those arguments".

Having to do this by reflection is a problem in principle solvable by types. Here is a function of type (A, B, C) => R, and here is a tuple of arguments of type (A, B, C), go call this function with those arguments.

Most programming languages make it hard to easily express those constraints so reflection is used instead.


Compile-time reflection is by far the most useful

It replaces the plethora of code generators that currently surround C++ like flies


You can build runtime reflection on top of compile time reflection, it's impossible the other way around. Compile time reflection is a superset of what runtime reflection can do.

We have add decent AOT compilation in Java almost since 2000, with Excelsior JET being one of the first vendors to offer it, but few are willing to pay for such tools.

Also I consider ART, GraalVM and OpenJ9 decent enough as free beer AOT.


99% of "modern" C++ is just masturbation and trying to keep up with what other languages had 10 years ago. The introduction of new standards isn't improving any code, in fact it's just causing old code to rot faster as the gap widens between the latest standard and the standards companies actually use.

It's obvious to any developer with even a little experience that less is more, complexity is the enemy. C++ continually embraces complexity, it even appears to encourage it as an opportunity to show everybody else how smart you are.


i think it's neat that you can do that

i'm going to stick to C, to prevent it from compiling, though


It feels to me that C++ has long become a language for "academic" or compiler-minded people to try yet another thing just for the sake of it or because its fun. Yes reflection eases some issues we have on C++ but the syntax is horrible and clutters the language

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.

https://javascriptwtf.com/

Arguably this is worse than C++ because for basic things/beginner to intermediate users, the footguns in C++ are more benign (e.g. performance related, rather than correctness) than the ones in JavaScript.


  > the footguns in C++ are more benign
I don't think I can agree. https://pvs-studio.com/en/blog/posts/cpp/1215/

I'm not gonna claim C++ is a great language, just that JavaScript starts its weirdness "earlier"/"lower" than C++. Especially since a bunch of the issues listed on your linked reference are technically UB but compilers (a) can warn about it, and (b) have reigned back somewhat on exploiting UB for optimizations.

Now that I think about it, the fact that you necessarily have to use a compiler with C++ is also part of the equation here. It's basically a mandatory linter. With JavaScript as with any interpreted language there's a lot of mines you can trip over waaaaaay down the line, especially if you aren't taking steps to use linters.


The same tired edge cases. Especially the null pointers. Yes, but what did you want the language to do? Stop you? You can do that yourself.

Honestly? I want the language to add integers. I don't understand how anyone can take seriously a language that only has floats for numbers. It's straight up ridiculous.

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

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.

> it’s limited to browsers, practically

That massively ignores node.js and all of the many popular backend frameworks built on it today. JS might not be everyone's cup of tea, but it is certainly not limited to browsers, practically.


We don't even write utilities in JVM languages, because spawning whole JVM is generally considered too expensive.

Yet there are people who will happily spawn whole web browser and even go as far as recommend doing so. All because they can't even write a left-pad function.


> We don't even write utilities in JVM languages, because spawning whole JVM is generally considered too expensive.

This is definitely not why, at least today. People are even willing to spawn an entirely new userspace (docker) for their utilities.

> Yet there are people who will happily spawn whole web browser and even go as far as recommend doing so.

Electron, like the JVM is nice and cross-platform with no BS. The only problem with the JVM is that it doesn't have a lot of important features/historical advantage that web tech does, not to mention the difference in quality between web UI and Swing, JavaFX, etc,. The combination of all of this is pretty much why electron is preferred. And I guess I should say chrome-tech instead of web-tech.

> All because they can't even write a left-pad function

Well, they certainly want the functions they write to work on every possible combination of OS/userspace.


> Electron, like the JVM is nice and cross-platform with no BS.

If we were talking in person I'd look at you like you had 2 heads now. In my bubble it is extremely en vogue to hate on Electron being a piece of bloated shit…

…but then again, I will agree that this is a piece of social signaling and would need some actual evaluation. There's probably some real problems with Electron that have caused people to start hating on it, but who knows how relevant those are. Trivial junk can easily snowball into social signals like this.


Oh it has its problems for sure. Especially the bloat.

I was just bringing up the similarity in ease of packaging.


Which uses JS made for a browser with some modifications.

Ah yes, the language that has no built in support to test if two non-primtive types are equal (& not just objects but arrays of primitives too), that has no proper integer type, whose standard library is thread-bare, and is an exercise in masochism if you don’t transpile it from TypeScript. TypeScript is at least somewhat livable but can we get beyond arguing which terrible language is better?

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.


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


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


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

The post mentions this obvious solution before delving into madness.

i believe this is standard since c99

you do have to cast it, though: `foo((vec2){.x=2, .y=2})`

the oldest MSVC on godbolt also accepts it, so it should be very portable as long as you're not using some embedded compiler from the mid 90s

a related fun thing is that you can pass it by pointer, and the lifetime will extend until the function is done:

`foo(&(vec2){ .x=2, .y=2 })`


Smells like UB. Is this defined in the standard?

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

What do you do with your Friday nights?

I rewrote the boot sector on a floppy drive in raw machine code (not assembly, I wrote the bytes by hand) before trying to see how I could abuse C++.

I consider the above normal engineering/hacking and wonder why someone who wouldn't think the above is fun is doing reading hackernews.


It's mostly C++. You are right to be scared.

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.


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.

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

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.


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


That's great, I wish I'd known about this ages ago

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

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

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


It was C99. It took C++ 21 years to copy it.

It's been the primary and most annoying interop issue for us when we have to integrate C++ code into our primarily C codebase (= build some pieces of our code in C++, which necessarily has to interact with at least our header files).

(Second place: differences in available GCC/clang compiler extensions between C and C++ [our software does not support Windows/MSVC], third place: differences in what casts are permitted/how they are done)


My number one issue are VLA parameters

int foo(int N, char buf[N]);

which I like to use because they improve warning messages, but they are not accepted in C++.


Well, C is not the same as C++. It even says in that doc that “this extension is not implemented in C++”.

Yeah sorry in my head I assumed that it would have been adopted

Even though you need to declare a struct for this (or other) functions to receive these fields, I feel like this is the cleanest approach. What I'm not sure about is if reference, value, or pointer is the best way to let the compiler optimize this.

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


Wow I had no idea, that's great!

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

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

yes a pointer to struct would be acceptable as well

In 1998 perhaps. It wouldn't be acceptable to today's C++ value-semantics crowd. Are you sure that you want to take the indirection penalty? or that the compiler will optimise it away?

If stack space usage is an issue, then it means that the struct is already being passed by hidden pointer (as opposed to using a register passing calling convention). Making the pointer explicit won't have any effect on performance (but it will on semantics).

C++ is already so reliant on optimizing `const&` parameters that I can't imagine why you wouldn't use them if you happen to be stuck with a shitty embedded toolchain (or MSVC) that can't be relied on to pass struct parameters efficiently.

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

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.

I'm not sure if you mean to have a /s in there, but personally I never really liked reflection.

C# had it but it was also in part because it interop'd with .NET which had C++.NET, VB.NET, F#.NET, VBScript.NET, ASP.NET, Core.NET, Web.NET, Net.NET and so much more .. reflection was an "easy" way to have other dev's interact with each others code as a type of "contract".

I really like C# and what it can do, but having to check if a particular method exists within an external dependency is in part what lead to "dll-hell" .. it's the antithesis to an API and a "software contract" .. honestly it feels like C++26's "reflection" is more an answer to the ABI issue that has plagued C++ since its inception.

If C++ really wants to help "game-engines" or make actual strides, then it should add basic GUI support to the language itself. That'd kill off 90% of the other framework/libraries out there.

As with other parts of the language, you don't -have- to use it .. and since it's trying to be the next Java in it's eternal update path, why not add GUI support at a language level ??? Hell the std::thread just calls pthread_create or CreateThread under the hood anyways, just with a 15+ high stack frame .. why not add GUI!?


> If C++ really wants to help "game-engines" or make actual strides, then it should add basic GUI support to the language itself.

This feels like a total non-sequitur. What does GUI have to do with component and property systems?

> why not add GUI!?

Because that is 100% out of scope for the C++ standard library. Also, I don't even want to imagine what a GUI library designed by committee would look like...


Agree it's a non-sequitur; as is any idea of "game-engine" code in C++ (as the post I replied to mentioned), hence the argument.

And 100% out of scope of C++, like the std::thread?? I've worked on many an embedded system that has no concept of threads, yet the C++ committee decided to add the std::thread as a part of the STL in C++11 instead of agree on a standard ABI. So why not GUI's, or sockets, or any other more "common" modern idiom?

If you don't want to imagine what a GUI library designed by committee would look like, I'd argue what a language like C++ looks like designed by committee (6 versions in 10 years).


People are going to build plugin and component systems whether we like it or not. People have massive investments in C++ whether we like it or not. These two concerns often intersect in game engines, apologies if that felt off topic.

Nah! I agree that plugin/component systems will be built out, no matter the language, more just trying to point out that C++, as-is-designed, in my opinion, seems like they're trying to go that route without actually going that route :|

I'm more saying we should cut out the middle-man (as it were).

The difference between C++03 and C++26 is, at a language/STL level, ultimately negligible when it comes to what I can "really do" with the language if I started in 03 .. and I don't mean that 26 doesn't "add more", but if I started with 03 and didn't have threading, file handling, delegates (std::function), sockets, graphics, and so much more, I'd likely use something that wrapped all of that (a plugin/component system) ... and switching away from that with an "antiquated" code base would be really hard at this point. Using 03 with a library and then just making it compile with C++26 doesn't really "add much", and switching away from that component system to C++26 requires design, building, testing, etc. etc. :|

And even if I'm starting with C++26 now (assuming my compilers are actually compliant, stable, non-breaking, ABI resilient and/or are actually available across the various platforms I want to target), while it does give me a lot more of a feature-set, how much of that is actually viable from an efficiency (CPU/memory) perspective over just proper/decent C++03/11 (I say 11 because of the threads) ...??

I know it's also up to the individual programmer using C++ to actually "do it good", so it's more just an old-man-yelling-at-clouds rant (observation) at how C++ is evolving, lol!

To be clear: not trying to be argumentative, I regularly work in C++ and enjoy it over many other languages .. just "saying" is all, hehe :)


Gotcha, totally fair points. It is an inherently conservative approach to standards, to take workarounds and pragmatic solutions that are already available within the community, and provide slightly cleaner (perhaps debatable) baked-in versions. You're right it doesn't add much, but at the same time... I feel the ick factor reducing with each release and my enjoyment of the language has grown over the years. I would certainly love to see those features used in common libraries and frameworks, but for practical reasons I suspect they think the same way you do, which is entirely valid.

Easy, anything Khronos related.

> as is any idea of "game-engine" code in C++ (as the post I replied to mentioned)

Huh? That post only mentioned component and property systems as a possible use case for reflection. I didn't see anyone proposing to add such systems to the standard.

> And 100% out of scope of C++, like the std::thread??

No, threading is definitely in-scope. I would agree that networking should be in the standard library (they have been trying for years now). These things have a pretty well-defined scope. GUI libraries, on the contrary, tend to be massive and also rather opinionated. There is no single widely accepted GUI paradigm that could be standardized.


Sockets has been in the work for the last 15 years. We almost got asio in the standard. There is significant interest in this, but also monumental bikeshedding.

A GUI proposal also was in the work for a while, then dropped because of lack of interest.

Threads definitely belong in the standard. Just because some platforms can't implement everything it doesn't mean the standard should be the minimum common denominator. Some embedded platforms don't even have malloc!

edit: but I think you are arguing in favor and I just failed at reading comprehension!


> A GUI proposal also was in the work for a while, then dropped because of lack of interest.

Are your sure? I can remember proposals for 2D graphics, but I have never heard of a GUI proposal. Graphics is only concerned with drawing things on a canvas. GUIs are about, well, graphical user interfaces which also involve user input, even handling, window management, etc.


You might be right and I might be misremembering. I thought there was a some point a subcomittee looking into GUIs in addition to the 2d graphic proposal.

edit: I think SG13 was supposed to look into GUIs at some point, but with even the 2d graphic proposal failing, it was disbanded.


The closest I think they got to a concrete proposal was web_view (https://wg21.link/p1108).

Interesting! AFAICT this would allow to run some JS code in a web browser, right? One thing I don't understand:

> Each web_view class instance represents an independent, asynchronous web-content interface. The provided web_view shall support content complying with the [HTML5], [PNG], and [ECMAScript] standards.

Who is supposed to provide the runtime?

Also, it shows how to call a JS function and get the result back in C++, but how would you interface with C++ from within JS (e.g. in an event handler)?


> If C++ really wants to help "game-engines" or make actual strides, then it should add basic GUI support to the language itself. That'd kill off 90% of the other framework/libraries out there.

Find me a single language that has an included GUI that anyone uses. I'll wait.

Even VB.NET, for which building GUIs was its entire reason to exist at all, has multiple GUIs officially, they couldn't even stick to one.

This is absolutely a terrible idea for a language, any language, to engage with at a language / standard library level.


Smalltalk-80 when it was at Xerox PARC, Objective-C at NeXT, VB (native version, not .NET), Hypercard.

That list would be like saying "Java on Android". A single platform's narrow usage of a language != language's standard library having it.

It is the standard library on the platform, of course it counts.

Get Smalltalk-80 reference manual without the UI documentation of its standard library, and no, GNU Smalltalk is not a complete implementation.


"The standard does not attempt to specify areas where current implementations differ in significant ways. In particular, as the goal statement implies, we did not include graphics, user interface, or database accessing objects in the library."

1997 DRAFT ANSI Smalltalk Standard

https://wiki.squeak.org/squeak/uploads/172/standard_v1_9-ind...


That is not Smalltalk-80 as originally designed....

Smalltalk-80 is not Smalltalk "as originally designed".

(Moving goal posts.)


We're not talking about platforms, we're talking about languages.

Which in several cases are the same.

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


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.


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.

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 ;_;

Might be a fun exercise to write a serialuzation/deserialization library using these features.

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.


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.


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



Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: