136 points by HeliumHydride 29 days ago | 18 comments
anon-3988 29 days ago
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 29 days ago
> 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.

drysine 29 days ago
>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.

pilianas 29 days ago
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.
likpok 29 days ago
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.

xmcqdpt2 28 days ago
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.

drysine 29 days ago
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
kccqzy 29 days ago
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.

drysine 23 days ago
As I stated above, it's the same kind of situation. See my another comment in this thread for an example with designated initializers.

>your code possibly contains undefined behavior

Only if f() returns a pointer to a2 (which is my point). Or did you imply that in the case when f() returns a pointer to a1 and it gets passed to the constructor of a1, provenance matters?

drysine 20 days ago
Actually, provenance matters:

"During the construction of an object, if the value of the object or any of its subobjects is accessed through a glvalue that is not obtained, directly or indirectly, from the constructor's this pointer, the value of the object or subobject thus obtained is unspecified." [0]

Reading an unspecified value isn't UB, that's good, but I don't understand why the standard says 'unspecified' because it clearly can be indeterminate if a sub-object hasn't been initialized yet.

[0] https://eel.is/c++draft/class.cdtor#2

badmintonbaseba 29 days ago
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.
drysine 29 days ago
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
badmintonbaseba 29 days ago
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.
drysine 23 days ago
Good point, I should've used direct initialization in this example.

  struct A { 
    int x;
    A(A *a) { if (a) a->x = 42;}
  };

  struct B {
    A a1;
    A a2;
  };

  void f()
  {
    B b{.a1{nullptr}, .a2{&b.a1} };
  }
This code is valid.

Now if I change the struct definition to

  struct B {
    A a2;
    A a1;
  };
it will become UB. Luckily it won't compile because of the difference between the order of declaration and the order of designated initializers.

The alternative way is to always initialize the sub-objects in the order of designated initializers (what do we do if not all initializers are provided?), but this would mean that the order of constructor calls wouldn't match the (reversed) order of destructor calls. Or we would need to select the destructor dynamically based on the way the object was initialized.

https://godbolt.org/z/MPoqEhTvf

badmintonbaseba 20 days ago
My gripe was not the form of initialization of the elements, but forming `b.a1` before `b`'s lifetime has started. It hasn't started before all of the elements are initialized.
drysine 20 days ago
But do we need the lifetime of b to be started? Isn't it enough that a1's lifetime is started? Taking of address of a1 happens after that. [0]

Upd:

There is an interesting sentence in [class.cdtor] but I don't think it applies here because B has no constructors:

"For an object with a non-trivial constructor, referring to any non-static member or base class of the object before the constructor begins execution results in undefined behavior."[1]

[0] https://eel.is/c++draft/dcl.init.aggr#7

[1] https://eel.is/c++draft/class.cdtor#1

29 days ago
Night_Thastus 29 days ago
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.

bluGill 29 days ago
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.

Night_Thastus 28 days ago
>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.

pjmlp 29 days ago
Remember the master, no raw loops!. :)
bluGill 29 days ago
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.
arjonagelhout 29 days ago
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.

drysine 29 days ago
>hides what the code actually does.

As does any function call.

f1shy 29 days ago
God forbids! I want functions to hide HOW is it done, not WHAT. One thing is abstraction, the other is obfuscation.
drysine 29 days ago
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.

f1shy 29 days ago
>> 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"

bluGill 29 days ago
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.

forrestthewoods 29 days ago
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

pjmlp 29 days ago
Unfortunely so it is with anything committee design, including SQL, C and Khronos APIs as well, it is not only C++.
Guthur 29 days ago
Because they know the cost of everything but the value of nothing.
dxuh 29 days ago
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.
Tsche 29 days ago
indeed
pjmlp 29 days ago
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++.

kzrdude 29 days ago
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.

NL807 29 days ago
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 29 days ago
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"},
    };
uecker 29 days ago
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 },
  };
gpderetta 29 days ago
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.

jcelerier 29 days ago
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
jjmarr 29 days ago
magic_enum addresses your issue but will absolutely kill compile time on a large project due to recursive template instantiation.
verandaguy 27 days ago
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.

SideQuark 29 days ago
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 29 days ago
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.

logicchains 29 days ago
>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.

arjonagelhout 29 days ago
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.

harry8 29 days ago
>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).

vouwfietsman 29 days ago
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.

29 days ago
Tsche 28 days ago
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.
vouwfietsman 27 days ago
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.

jcelerier 29 days ago
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.
weinzierl 29 days ago
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.

flohofwoe 29 days ago
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 ;)

29 days ago
jcranmer 29 days ago
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".
xmcqdpt2 28 days ago
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.

nickelpro 29 days ago
Compile-time reflection is by far the most useful

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

badmintonbaseba 29 days ago
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.
pjmlp 29 days ago
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.

DannyBee 29 days ago
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.

bluGill 29 days ago
> 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.

DannyBee 28 days ago
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.

cedws 29 days ago
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.

harrison_clarke 29 days ago
i think it's neat that you can do that

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

witx 29 days ago
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
hyperhello 29 days ago
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.
eqvinox 29 days ago
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.

orphea 29 days ago

  > the footguns in C++ are more benign
I don't think I can agree. https://pvs-studio.com/en/blog/posts/cpp/1215/
eqvinox 29 days ago
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.

hyperhello 29 days ago
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.
eqvinox 29 days ago
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.
drivebyhooting 29 days ago
Is JS simpler than Python? I don’t think so but willing to change my mind.
hyperhello 29 days ago
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.
flakes 29 days ago
> 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.

friendzis 29 days ago
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.

porridgeraisin 29 days ago
> 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.

eqvinox 29 days ago
> 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.

porridgeraisin 29 days ago
Oh it has its problems for sure. Especially the bloat.

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

dingi 29 days ago
Which uses JS made for a browser with some modifications.
vlovich123 29 days ago
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?
quietbritishjim 29 days ago
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.

gpderetta 29 days ago
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...

feverzsj 29 days ago
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

p0w3n3d 29 days ago

  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."
sagarm 29 days ago
The post mentions this obvious solution before delving into madness.
harrison_clarke 29 days ago
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 })`

p0w3n3d 29 days ago
Smells like UB. Is this defined in the standard?
harrison_clarke 21 days ago
C99 spec, section 6.5.2.5: https://www.open-std.org/jtc1/sc22/WG14/www/docs/n1256.pdf

> 6. The value of the compound literal is that of an unnamed object initialized by the initializer list. If the compound literal occurs outside the body of a function, the object has static storage duration; otherwise, it has automatic storage duration associated with the enclosing block.

it's valid until the end of the block. so, if the function returns the pointer, you can even use that until the end of the block

there's actually an example in the spec, on that page:

> drawline(&(struct point){.x=1, .y=1}, &(struct point){.x=3, .y=4});

nialv7 29 days ago
I am scared by what C++ people think is fun.
bluGill 29 days ago
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.

badmintonbaseba 29 days ago
It's mostly C++. You are right to be scared.
knorker 29 days ago
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.

BodkinsOdds 29 days ago
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.
oilkillsbirds 29 days ago
Poor, poor C++... cries in C
anitil 29 days ago
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 29 days ago
>> 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

anitil 29 days ago
That's great, I wish I'd known about this ages ago
tjalfi 29 days ago
C++20 added designated initializers, so they're also an option.

    my_func({.arg1 = val1, .arg2 = val2});
anitil 29 days ago
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

AlotOfReading 29 days ago
It was C99. It took C++ 21 years to copy it.
eqvinox 29 days ago
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)

uecker 29 days ago
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++.

Cyph0n 29 days ago
Well, C is not the same as C++. It even says in that doc that “this extension is not implemented in C++”.
anitil 29 days ago
Yeah sorry in my head I assumed that it would have been adopted
pragma_x 29 days ago
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.
brandmeyer 29 days ago
> 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...

anitil 29 days ago
Wow I had no idea, that's great!
ranger_danger 29 days ago
at that point why not just pass in a struct as your argument
anitil 29 days ago
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
ranger_danger 29 days ago
yes a pointer to struct would be acceptable as well
RossBencina 29 days ago
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?
gpderetta 29 days ago
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).
BodkinsOdds 29 days ago
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.
jcelerier 29 days ago
depending on the platform this may cause things to be pushed / poped on the stack instead of being passed as registers
thom 29 days ago
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.
the_hoffa 29 days ago
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!?

spacechild1 29 days ago
> 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...

the_hoffa 29 days ago
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).

thom 29 days ago
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.
the_hoffa 29 days ago
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 :)

thom 29 days ago
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.
spacechild1 29 days ago
> 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.

pjmlp 28 days ago
Easy, anything Khronos related.
gpderetta 29 days ago
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!

spacechild1 29 days ago
> 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.

gpderetta 29 days ago
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.

jcranmer 29 days ago
The closest I think they got to a concrete proposal was web_view (https://wg21.link/p1108).
spacechild1 29 days ago
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)?

kllrnohj 29 days ago
> 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.

pjmlp 29 days ago
Smalltalk-80 when it was at Xerox PARC, Objective-C at NeXT, VB (native version, not .NET), Hypercard.
kllrnohj 29 days ago
That list would be like saying "Java on Android". A single platform's narrow usage of a language != language's standard library having it.
pjmlp 29 days ago
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.

igouy 29 days ago
"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...

pjmlp 29 days ago
That is not Smalltalk-80 as originally designed....
igouy 28 days ago
Smalltalk-80 is not Smalltalk "as originally designed".

(Moving goal posts.)

kllrnohj 29 days ago
We're not talking about platforms, we're talking about languages.
pjmlp 28 days ago
Which in several cases are the same.
prabhu-yu 29 days ago
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...

maleldil 29 days ago
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.
pipeline_peak 28 days ago
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.

hoseja 29 days ago
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 ;_;
LeicaLatte 29 days ago
Might be a fun exercise to write a serialuzation/deserialization library using these features.
injidup 29 days ago
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.

lallysingh 29 days ago
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.

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