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?
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.
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.
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.
{ 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.
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/xMb64ssYKFurthermore your code possibly contains undefined behavior depending on the behavior of the constructor of A.
>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?
"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.
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 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.
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]
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 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.
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.
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.
As does any function call.
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.
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"
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.
But because this is C++ the committee designs the most insane and terrible version of reflection possible
This kind of geekyness is surely loved by many, not only C++.
It's curiosity, play and learning. It's great.
key_enum one_two_three
{
FIRST = { .name = "first"},
SECOND = { .name = "second"},
THIRD = { .name = "third"},
LAST = { .value = 255, .name = "last"},
};
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 },
};
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.
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.
But it is tremendously useful.
https://en.wikipedia.org/wiki/List_of_reflective_programming...
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.
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.
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.
Why not just use a visitor? (Genuine question, not c++ snark).
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.
> 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.
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 sure that C++26 has implemented it in a way which is highly unpleasant to use though ;)
Most programming languages make it hard to easily express those constraints so reflection is used instead.
It replaces the plethora of code generators that currently surround C++ like flies
Also I consider ART, GraalVM and OpenJ9 decent enough as free beer AOT.
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.
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.
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.
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'm going to stick to C, to prevent it from compiling, though
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/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.
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.
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.
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.
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.
I was just bringing up the similarity in ease of packaging.
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.
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...
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."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 })`
> 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});
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.
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.
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.
No need to write it:
https://clang.llvm.org/extra/clang-tidy/checks/bugprone/argu...
Efb
my_func({.arg1 = val1, .arg2 = val2});
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
(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)
int foo(int N, char buf[N]);
which I like to use because they improve warning messages, but they are not accepted in C++.
Like this one!
https://clang.llvm.org/extra/clang-tidy/checks/bugprone/argu...
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!?
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...
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).
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 :)
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.
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!
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.
edit: I think SG13 was supposed to look into GUIs at some point, but with even the 2d graphic proposal failing, it was disbanded.
> 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)?
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.
Get Smalltalk-80 reference manual without the UI documentation of its standard library, and no, GNU Smalltalk is not a complete implementation.
1997 DRAFT ANSI Smalltalk Standard
https://wiki.squeak.org/squeak/uploads/172/standard_v1_9-ind...
https://prabhuullagaddi.substack.com/p/simulation-of-keyword...
Things like serializers and MVC event bindings come to mind.
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.
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.