Wow this got really long. I was one of the coauthors for a reflection proposal (N3340) over a dozen years ago. Implementing compile-time reflection is honestly trivial - you basically transfer data from the symbol table on-demand into template specializations. It was roughly 1500 LOC to modify g++ to do it.
Looking at the examples (https://isocpp.org/files/papers/P2996R4.html#examples) what really stands out is the direct integration of type-syntax into the language. It fits in with a certain token-substitution way that connects back to templates. It also replaces some of the uglier operators (typeof?).
I hope it goes int! During the language's stagnation I left for a while, perhaps it'll be competitive again soon.
I think another good example might be extracting function signatures (parameter names & types, default values -- if any) at compile-time for various purposes.
While I love this paper and this proposal in general, as a C++ developer every time C++ adds a new major feature I get somewhat worried about two things:
1. how immense the language has become, and how hard it got to learn and implement
2. how "modernising" C++ gives developers less incentives to convince management to switch to safer languages
While I like C++ and how crazy powerful it is, I also must admit decades of using it that teaching it to new developers has become immensely hard in the last few years, and the "easier" inevitably ends up being the unsafe one (what else can you do when the language itself tells you to refrain from using `new`?).
I think the focus on smart pointers is a huge mistake. Code bases using shared_ptr inevitably will have cycles and memory leaks, and no one understands the graphs any more.
Tree algorithms that are simple in literature get bloated and slow with shared_ptr.
The only issue with pointers in C++, which C does not have, is that so many things are copied around by default if one is using classes. So the way to deal with tree algorithms is to have a hidden tree with pointers and a class that wraps the tree and deletes all dangerous copy methods, implicit and explicit.
Yeah that's because you're not supposed to be using "new" anymore since the introduction of smart pointers in
C++11. Std::shared_ptr and std::unique_ptr are preferred. Shared pointers ref count and auto-delete, and unique pointers can't be copied.
Do I understand correctly that this proposal does not include annotations (i.e. attributes).
More specifically, with this I can iterate over struct members and get their names and types, but I cannot attach additional information to these members, like whether they should be serialized or under which name.
The referenced proposal P1887R1 covers this, but that's not included here, right?
Can I ask a naive question that consists of two parts and please don't flame me? lol
* What type of problems static reflection could solve, in general?
* Are there specific cases and / or situations where static reflection could resolve such case, even simplify an unnecessary complexity?
The main canonical use case for static reflection is serialization, where serialize<T>() can easily have a default case of calling serialize() on each of the fields. In the more general case, you basically want to have some library method that does something based on the structure of a struct or class, without having to define some sort of explicit, intrusive interface that said struct or class implementation has to provide.
Does static reflection simplify such cases? ... Outlook unclear. It's definitely gnarlier to actually write the serialize() method, and in many cases, it does feel like a better option is to write a specific domain-specific language to specify what you want to specify, with a tool to operate on it as appropriate (think something like protobufs for serialization).
Any sort of reflection brings C++ one step closer to Python.
Implementing serialization for complex types often requires manual code writing or external tools. With static reflection you could automate this process
Maybe this doesn't count as static, but I used to regularly use reflection in C# to generate code for interacting with foreign DLLs.
This was a video game mod, essentially. I needed to create a text interface to modify settings for any other mod that might be installed. Other mods would simply implement a settings class with certain attributes, then I could list out all fields and their types. The list was processed into a sort of tree presented through the chat interface. From there I can generate code to modify that settings class from outside its assembly and raise value change events.
The reflection part of that was extremely simple, but just because that's how C# works. C# makes a task like this almost trivial.
At my current job, we have a similar thing. Classes decorated with attributes. We inspect them and check the generic type they implement. This way we register message handlers by their message type dynamically. You write a handler class and it simply works.
Windows Forms had a PropertyGrid control which did the same thing as my text interface, but with a grid of properties you can edit freely.
Most of this stuff is typically done at runtime. But you could have it be static if you wanted. A precious job did this to access the backing array inside of a List<> object. I offer no explanation or excuse for that one.
I have been waiting for static reflection for the last 20 years. The current proposal seems quite nice, but the real question is whether any non trivial usage will kill compilation performance.
Finally. I think there have been proposals since C++17 at least, and all I really wanted is for them to solve the common problem of basic static reflection for enums (without hacks like magic_enum uses).
This looks surprisingly fine! The opaque, extensible types remind me of Win32 with its extensibility through ever new message types. The syntax looks better than expected, too - well, it's better than templates...
Yes, that's a clever way of demonstrating viability (and with more than one compiler implementation).
I do like the examples that I see there.
This seems like the kind of language feature that I might not make much use of directly in general application code, but would wrap up in utility functions or use via lower-level libraries that the application code builds on. E.g., they showed command-line parsing, but I could also see this for benchmarking and testing frameworks to automatically find the workloads and tests in a non-hacky way.
I also wonder about how this feature interacts with translation units or modules and linkage, though. I'm reminded of the static initialization order fiasco; this seems like it might open up issues that make that look tame by comparison. (Not a complaint; I'm actually looking forward to this.)
But the `member_number` functions in § 3.2 look disturbing to me. It's not discernible how invalid arguments are handled. Normally I'd look at generated assembly to answer a question like that, but this probably doesn't make sense with compile-time-fu (`constexpr`)…
Having implemented reflection in languages like C(++), before, it is most certainly not bloat-free. There are sorts of 'obvious' things programmers do (like enum-reflection) that end up injecting strings all over the place. The overhead is (worst case) proportional to the source-code size, in those cases. In other cases, you end up with bloat proportional to heavily-utilized template libraries. However, unless the reflection system is very clever, i.e., exposes enough information to the linker to strip duplicate-ish symbols, you end up with a bunch of reflection copies.
RTTI is a super vital feature for deserializing without having to generate code or write a ton of tedious boilerplate. I'd be very happy with RTTI in C++ and my life would be instantly easier if there were RTTI in typescript so I didn't have to use any of the hacky solutions out there for deserializing JSON on backends without RTTI.
I suppose C++'s template system might be able to generate JSON deserializers with static reflection as well
I haven't touched C++ since undergrad. Neither have I written any Qt code. But from memory, doesn't Qt's moc implement some of this stuff because it wasn't available in C++? Could this replace moc?
Qt's moc can already be replaced and it increasingly is being relied on less and less as time goes on but dropping moc requires dropping all lower C++ standards or maintaining separate moc and modern-c++ versions.
And while it can currently be replaced with templates alone in fairly old versions of C++ (C++14 is the oldest I think), compile times are egregious unless you use very new, shiny features.
And as much as I am pro "move to new shiny C++", one of the big commercial uses of Qt is in semi-embedded applications like car entertainment centers where you are stuck with whatever (often outdated) toolchain your SOC source relies on. So pushing for shiny new Qt risks either splitting Qt in half or abandoning a lot of very well paying users.
Qt has straight-up dynamic reflection. You can get pointers to functions from strings, and such. This is just static reflections (which is still very useful!), so it's not a complete replacement. Even if it was, I would Qt would replace its build system.
I've always wondered what's the point of "replacing moc". I mean what's the problem with moc? It's required by Qt, and completely transparent by the build system. You don't even know it's used.
I mean, GCC also has some helper tools used to compile C++ code and we don't talk about "replacing them".
I'm surprised at the positive response in this thread. I find the syntax of this beyond atrocious! My goodness C++ really does not know how to do anything simply does it?
Many C++ features are useless outside of writing libraries, but your typical developer is going to be forced to understand how they work at some point. The result is just a burden.
What does this have to do with reflection. Also why do you need networking in the C++ standard library? Networking is neither something that is relevant as a vocabulary that needs to be common between libraries nor is it something that makes sense to be set in stone like basic algorithms. Just use OS interfaces or a third-party abstraction FFS.
lallysingh|1 year ago
Looking at the examples (https://isocpp.org/files/papers/P2996R4.html#examples) what really stands out is the direct integration of type-syntax into the language. It fits in with a certain token-substitution way that connects back to templates. It also replaces some of the uglier operators (typeof?).
I hope it goes int! During the language's stagnation I left for a while, perhaps it'll be competitive again soon.
cb321|1 year ago
stiglitz|1 year ago
qalmakka|1 year ago
1. how immense the language has become, and how hard it got to learn and implement
2. how "modernising" C++ gives developers less incentives to convince management to switch to safer languages
While I like C++ and how crazy powerful it is, I also must admit decades of using it that teaching it to new developers has become immensely hard in the last few years, and the "easier" inevitably ends up being the unsafe one (what else can you do when the language itself tells you to refrain from using `new`?).
naertcxx|1 year ago
Tree algorithms that are simple in literature get bloated and slow with shared_ptr.
The only issue with pointers in C++, which C does not have, is that so many things are copied around by default if one is using classes. So the way to deal with tree algorithms is to have a hidden tree with pointers and a class that wraps the tree and deletes all dangerous copy methods, implicit and explicit.
stdlib++ seems to use that approach as well.
pjmlp|1 year ago
Even Go is rediscovering that staying simple just doesn't happen for any language that gets industry adoption at scale.
fragmede|1 year ago
unknown|1 year ago
[deleted]
oldpersonintx|1 year ago
Indeed I wish they were even more aggressive about breaking changes
Rust is nifty but there is simply too much existing C/C++ out there and "rewrite it in Rust" is not a serious suggestion
Maybe one day we have some cool AI that magically rewrites old C/C++ automatically, but by then I also assume we will have AI-designed languages
Until then, we need C/C++ to be maintained and modernized because we are actually running the world with these languages
w4rh4wk5|1 year ago
More specifically, with this I can iterate over struct members and get their names and types, but I cannot attach additional information to these members, like whether they should be serialized or under which name.
The referenced proposal P1887R1 covers this, but that's not included here, right?
P1887R1: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p18...
stefanos82|1 year ago
edflsafoiewq|1 year ago
* Converting enum values to strings, and vice versa
* Parsing command line arguments from a struct definition (like Rust's clap)
* Simple definition of tuple and variant types, without the complex metaprogramming tricks currently used
* Automatic conversion between struct-of-arrays and array-of-structs form
* A "universal formatter" that can print any struct with all its fields
* Hashing a struct by iterating over its fields
* Convert between a struct and tuple, tuple concatenation, named tuples
jcranmer|1 year ago
Does static reflection simplify such cases? ... Outlook unclear. It's definitely gnarlier to actually write the serialize() method, and in many cases, it does feel like a better option is to write a specific domain-specific language to specify what you want to specify, with a tool to operate on it as appropriate (think something like protobufs for serialization).
greenavocado|1 year ago
Implementing serialization for complex types often requires manual code writing or external tools. With static reflection you could automate this process
Simplified property systems Simplified template metaprogramming Easier to write generic algorithms that work with arbitrary typesquotemstr|1 year ago
Imagine making a plain
and wanting to serialize it to JSON without further ceremonyutensil4778|1 year ago
This was a video game mod, essentially. I needed to create a text interface to modify settings for any other mod that might be installed. Other mods would simply implement a settings class with certain attributes, then I could list out all fields and their types. The list was processed into a sort of tree presented through the chat interface. From there I can generate code to modify that settings class from outside its assembly and raise value change events.
The reflection part of that was extremely simple, but just because that's how C# works. C# makes a task like this almost trivial.
At my current job, we have a similar thing. Classes decorated with attributes. We inspect them and check the generic type they implement. This way we register message handlers by their message type dynamically. You write a handler class and it simply works.
Windows Forms had a PropertyGrid control which did the same thing as my text interface, but with a grid of properties you can edit freely.
Most of this stuff is typically done at runtime. But you could have it be static if you wanted. A precious job did this to access the backing array inside of a List<> object. I offer no explanation or excuse for that one.
bdd8f1df777b|1 year ago
unknown|1 year ago
[deleted]
adamnemecek|1 year ago
gpderetta|1 year ago
a1o|1 year ago
TillE|1 year ago
jjmarr|1 year ago
ahartmetz|1 year ago
bingo3131|1 year ago
steveklabnik|1 year ago
Looks like it did very well in St. Louis!
pjmlp|1 year ago
a_e_k|1 year ago
I do like the examples that I see there.
This seems like the kind of language feature that I might not make much use of directly in general application code, but would wrap up in utility functions or use via lower-level libraries that the application code builds on. E.g., they showed command-line parsing, but I could also see this for benchmarking and testing frameworks to automatically find the workloads and tests in a non-hacky way.
I also wonder about how this feature interacts with translation units or modules and linkage, though. I'm reminded of the static initialization order fiasco; this seems like it might open up issues that make that look tame by comparison. (Not a complaint; I'm actually looking forward to this.)
guardian5x|1 year ago
tempodox|1 year ago
But the `member_number` functions in § 3.2 look disturbing to me. It's not discernible how invalid arguments are handled. Normally I'd look at generated assembly to answer a question like that, but this probably doesn't make sense with compile-time-fu (`constexpr`)…
flykespice|1 year ago
Dwedit|1 year ago
thechao|1 year ago
armchairhacker|1 year ago
shortrounddev2|1 year ago
I suppose C++'s template system might be able to generate JSON deserializers with static reflection as well
kstrauser|1 year ago
jacoblambda|1 year ago
And while it can currently be replaced with templates alone in fairly old versions of C++ (C++14 is the oldest I think), compile times are egregious unless you use very new, shiny features.
And as much as I am pro "move to new shiny C++", one of the big commercial uses of Qt is in semi-embedded applications like car entertainment centers where you are stuck with whatever (often outdated) toolchain your SOC source relies on. So pushing for shiny new Qt risks either splitting Qt in half or abandoning a lot of very well paying users.
fluoridation|1 year ago
shkurski_|1 year ago
self_awareness|1 year ago
I mean, GCC also has some helper tools used to compile C++ code and we don't talk about "replacing them".
Why people want to remove moc from Qt?
account42|1 year ago
forrestthewoods|1 year ago
z_open|1 year ago
pjmlp|1 year ago
huhtenberg|1 year ago
syedhswhw|1 year ago
[deleted]
jdeaton|1 year ago
[deleted]
nox101|1 year ago
https://www.youtube.com/watch?v=EqiLTgQcDPM
But still, happy to have the new solutions
raymond_goo|1 year ago
See also: https://github.com/cplusplus/networking-ts
account42|1 year ago
lpribis|1 year ago
SilverSlash|1 year ago