Interestingly enough I implemented something almost identical to this (although not quite as clean) in my codebase before discovering this talk. I can vouch that, for my application (massively parallel fluid simulation), there were performance benefits over std::variant that made it worth it, and the implementation is nothing crazy.
great talk by a knowledgeable speaker. I found the colors on the slides difficult to make out however, until I was blowing my retinas out with the white background
Thanks for your kind words! My apologies for the slides - I had them on a dark background originally. I had technical issues day-of, and as a result I didn't realize that the projector in the room didn't like my dark color scheme until the last minute. I switched the color scheme about 15 minutes before the start of the presentation and so I didn't have any time to fine tune it. A dark background should be available in the CppCon GitHub repo.
If you have tuple and need stable iteration, you can use tuple where the INT in each pair represents the vector to look into next (this type INT needs to hold the number of vectors, i.e. needs not be size_t; for < 256 vectors, std::uint8_t is fine). The manager must keep track of the last vector (store 1 INT each) that was inserted into, so that its last value’s INT component can be updated when another element is inserted. It does not have to track the whole element, because insertion order in a vector _is_ stable. When iterating, one must keep track of the next element in each vector.
very interesting have you made any benchmarks with this technique? Obviously it adds a bit of runtime overhead when iterating and inserting but it should still be better than the pointer indirection from the polymorphic approach, right?
I would think that you'd probably have to use a tuple to maintain logical ordering. The INT keeps the same meaning as OP's, while the size_t maintains the next index into the indirected vector. Otherwise, all one would have is knowledge of which vector is next in the logical ordering, but not which element. The INT type would also need be signed, as it's trivially conceivable that you'd have to iterate into a prior vector of the outer tuple.
Thanks for the talk. Very interesting. I would only use this when it has a significant impact on performance in an area where it matters. It looks quite convoluted. I'd pick it over SFINAE every day though 😝.
It is not as convoluted as it looks. As is common with those types of talks, in the effort to show all that can be done, the talk gives the feeling of something overwhelming. Applied to real code however, you rarely need all of that and certainly never all at the same time. That being said, it is still true it's just a tool in the toolbox, not a silver bullet.
@@etherstrip I couldn't agree more. With just a little practice, this approach to writing code is not very complex - you just have to learn how to think about things through a different lens.
Meantime while CPP 20 becomes mainstream (10 years or so), we can use the CRTP (Curiously Recurring Template Pattern ) pattern that has been around for a long to create static interfaces, unless CPP 20 is covering something else that CRTP can not.
@@chanaaron2533 You can't. You will end up having to create virtual base class, inherit the CRTP class and other classes from that. Tadaaa and you have just invented polymorphism with extra steps.
For me, the only good use cases for virtual functions are: (1) having a container that can hold multiple implementations of the same base class and (2) decoupling implementations to have more unit-testable code. Variants or tuples of containers are sometimes a good alternative for case 1. However, there is no really good replacement for case 2. that does not involve some level of indirection. Also, I should remind people here that the main cost of virtual functions is the fact that they are hard to inline. Their overhead is comparable to normal non-inlined functions, and usually, people mistake the overhead of virtual functions for the overhead incurred by the fact that they allocated their polymorphic objects on the heap.
Usually a return type name can get complicatedly long so it makes it easier to spot the function name. I think it's a nice idea to write like this, might be even nicer if C++ would allow to skip the auto part as well: function() -> (return_type)
For consistency that's a pretty good idea to do. The advantage of auto name() -> type is that you can use the parameter to define the return type. Example: auto myFunction(auto parameter) -> decltype(parameter.someFunction()) ... Imagine that someFunction() may have a different type depending on the type of parameter. You can't do that if you put the return type before the function name. It's also consistent with lambdas. You can specify the return type in lambdas, contrary to some people's beliefs, but you need to do it with the arrow; const auto myLambda = [] () -> SomeType { return {1,2,3}; }; So it has a lot of advantages.
it would have been good to have some data showing performance differences using this method. While I think the subject and talk is very interesting, I remain skeptical about the performance gains.
He literally mentioned another talk that already happened that shows hard numbers for the performance benefits, while explaining why he didn't show any numbers.
Virtual function performance is often dependent on the compiler to determine if a call is really virtual or not. But on mature CPUs you also have branch prediction, so a virtual function will have zero performance overhead if and only if the CPU can perfectly predict the destination address. On tiny embedded cpus that might be an issue. I agree that a programmer always have to consider if a virtual function is needed or not.
Maybe, maybe not? When discussing runtime polymorphism, the more important scenario is "many objects in a container, processed sequentially". This is what OOP proponents commonly cite as an advantage of their favourite paradigm ("I can store a vector of Employee objects and I don't need to care about the internal implementation details"). In this scenario it's very unlikely that the compiler will be able to devirtualize, because the container is usually populated at runtime. CPU branch prediction can only do so much to help, you'll still likely get a performance penalty. Another factor is the vptr storage, which means that, at the very least, you can store fewer objects in a cache line because each object now occupies an extra 8 bytes.
You have a triple indirection. From the this object, to the vmt table to the code. Tell me which CPU can optimize this away? One better solution was Eiffel, not only gave it perfect multiple inheritance but it was also faster for most cases. THey generated switch statement trampolines on a per function base, not a VMT for the whole class.
Good presentation on concepts. BUT you don't need unique_ptr / shared_ptr to call virtual functions. They can be stack allocated a passed around as references. In that case with a final keyword it compiler will most likely devirtualize and you write simpler code. Also why would you define all other constructors/operators if you pass-around the type as a pointer/reference. I would say : 1. You want to represent a value. Use variant for polymorphism when you need it. 2. You want to represent a API to achieve something go for virtuals ;) I you design the API well the virtual call cost is negligible.
Yes true but then they're polymorphic pointers with the guise of a reference.. This can lead to confusion - usually when I see something taking const Foo& my mind doesn't immediately think ooh this thing's actually polymorphically derived from Bar and implements 3 of its virtual functions. And if they're stack allocated marked final in a visible translation unit the class layout would be resolved and even inlined anyway then there isnt really anything to devirtualize. I think virtual functions are useful for realizing APIs across different sometimes tricky boundaries (extern, PIMPL, dlsym/dyld type stuff) where theres only so much a compiler can do, at that point yah I agree.. you feel way less bad about a bunch of virtual calls because guess what the method u just shimmed i.e the guy next door has 20 more of his own on the callstack!
"They can be stack allocated a passed around as references" - I'd argue the most common scenario is allocating a container of heterogeneous objects at runtime (e.g. read from a database and use a factory to populate a vector); in this case I guess it's obvious that the compiler can't devirtualize. For APIs, I'd be careful about using virtuals because you could have methods that are called very frequently (a common example are naive graphics APIs that add a shape to the canvas, or objects that have a Render method). I guess the more important question is why use virtual at all.
- It is very convoluted and therefore error-prone - It requires lot of boiler-plate - You need to know all the types at compile time to create an instance e.g., "Foo". Virtual inheritance allows run-time selection e.g., Base *base = Base::New("Derived")
Yup, this thole presentation is so far from real world application that it's frightening such people take active part in language development. Design by comitee at it's worst.
yeah and in python you dont even have to know the type to create an instance. See i can do your level of criticism too. Point is that convenience comes at a cost. This talk is about a way to achieve polymorphic behavior without the cost in the first place. Yes its a bit more convoluted and not as convenient as you have to know every type at compile time, but for certain applications its more performant than using polymorphism.
@@DFsdf3443d You can achieve C++ behavior with C, so lets switch to C? What kind of argument is that? Please prove your point for more performance. Assembler is also more performant than C++, yet nobody could ever prove it in a real world application, because the compiler is better at generating assember. take an 100k loc application and rewrite in in assembler, and run the tests.
Can you use polymorphism without virtual? Can you use concept to achieve this? Can they serve the same purpose? Can you feel, Can you feeeeeel myyyy heaaaart?
30:00 downsides is missing a point. Need to know fixed list of types at the time where you place the container that will store your things. That means if a 3rd party wants to extend your code in the future, they have no option other than modifying/patching your code. Where-as with virtual, it wouldn't matter as you just insert a pointer to the base class.
@@dexio85 still it is a cost, that can be avoided. Also im not sure i believe you when you say its a small cost, in certain scenarios it can be come quite costly.
he addressed this at he start of the talk, he is not saying to never use the virtual keyword again, he is simply showing a more performant way to achieve polymorphic behavior that may be better suitable in certain applications. Obviously a third party polymorphic library interface isn't a suitable application in this case.
In the final code, is this a good example of using Concepts to avoid the use of CRTP for implementing static polymorphism (to enforce that concrete types implement a specific interface)?
I 100% agree with the "bold statement" - you don't need to use virtual, you don't need to use smart pointers. I used that methodology in my c++20 order book project, which I talk about on my channel
Ok, I came to this because my daytime job is mostly Java, where every non-static method is virtual, and I was trying to see how this would work out. I have to admit our class hierarchies potentially grow deep and wide. It is a lot of behavioral inheritance with abstract methods as placeholders for different variations of behavior depending on where in the hierarchy you are. Somehow I fail to make the connection with this talk. I am guessing your usage of class hierarchies is simply to achieve different goals than we do. So far you seem to say that "if you don't need virtual you can get quite far without it" as well as "concepts can do neat things." That last one is definitely a takeaway for me...
It sounds to me like your primary usage of virtual is primarily the first case on slide 2-1 - binding a specific interface. If that's correct, your takeaway that concepts can serve a very similar function is spot on!
I learned too much on concepts & the main subject. Thank🙏 If anyone who read this comment knows a reference to learn more about cpp concepts please let me know… (video or blog …)
Constructive criticism: 1) Use better code font and size 2) Don't clear your throat constantly in the microphone. Otherwise thanks for the talk, very interesting.
The sample code is completely unreadable. Why use such small fonts? There's vast areas of white space to the left, right top and bottom. The margins are white, and the code is in dark theme. Unreadable. What's the presenter thinking?
std::variant is not enough, there are many use cases when it is inapplicable or ineffective such as huge difference between sizeof's, required incomplete type (variant needs to know all types when created while type erasure can be used for recursive types etc) For example: struct expression { expression* a; expression* b; }; Its impossible to do with variant. And yes, virutal functions is bad, but solutions exist already...(cant post links here =)))
the variant will not be recursing much like how a virtual class can't inherit itself if you are referring to how a class that is included in the variant can contain the variant, that is not possible with OOP and inheritance either, you do not store values of base classes, you store references to them; that is to say that a class satisfying the variant's concept should not be storing the variant but a pointer to it (and all pointers are sizeof(void*) so there's no recursion)
Yes, let me add that you must know that the type is OK for the framework developer, but it is really really bad for the framework user because the framework developer cannot know the type to be defined by the user in advance, which is very disgusting.
Looking at this code makes me want to say, "C++ people, are you freaking kidding me?". C++ has become an overly complicated mess of a language, it has to go.
Don't worry, almost nobody uses template salad in real world code. C++ is expressive enough even without use of these "modern" features. And to be really honest, language itself is very stagnant. Whole c++ philosophy these days is "we do that in std". And that led us to atrocities such as std::initializer_list. Or very non-obvious template rules. Template rules are so stupid, that sometimes I feel like it's simpler to write a code generator in python. Only in most trivial cases they work as intended.
For lists that don't need to be created as instances, I've found the multiton pattern to be very similar in usage, and a common base class is not needed en.m.wikipedia.org/wiki/Multiton_pattern
Thanks, Gigachad.
lmao
Damn you...hard to unsee. Cool talk tho, good presenter. :)
A gigachad talk :)
😊
Interestingly enough I implemented something almost identical to this (although not quite as clean) in my codebase before discovering this talk. I can vouch that, for my application (massively parallel fluid simulation), there were performance benefits over std::variant that made it worth it, and the implementation is nothing crazy.
great talk by a knowledgeable speaker. I found the colors on the slides difficult to make out however, until I was blowing my retinas out with the white background
Thanks for your kind words!
My apologies for the slides - I had them on a dark background originally. I had technical issues day-of, and as a result I didn't realize that the projector in the room didn't like my dark color scheme until the last minute. I switched the color scheme about 15 minutes before the start of the presentation and so I didn't have any time to fine tune it. A dark background should be available in the CppCon GitHub repo.
If you have tuple and need stable iteration, you can use tuple where the INT in each pair represents the vector to look into next (this type INT needs to hold the number of vectors, i.e. needs not be size_t; for < 256 vectors, std::uint8_t is fine). The manager must keep track of the last vector (store 1 INT each) that was inserted into, so that its last value’s INT component can be updated when another element is inserted. It does not have to track the whole element, because insertion order in a vector _is_ stable. When iterating, one must keep track of the next element in each vector.
very interesting have you made any benchmarks with this technique? Obviously it adds a bit of runtime overhead when iterating and inserting but it should still be better than the pointer indirection from the polymorphic approach, right?
I would think that you'd probably have to use a tuple to maintain logical ordering. The INT keeps the same meaning as OP's, while the size_t maintains the next index into the indirected vector. Otherwise, all one would have is knowledge of which vector is next in the logical ordering, but not which element.
The INT type would also need be signed, as it's trivially conceivable that you'd have to iterate into a prior vector of the outer tuple.
Thanks for the talk. Very interesting.
I would only use this when it has a significant impact on performance in an area where it matters. It looks quite convoluted. I'd pick it over SFINAE every day though 😝.
It is not as convoluted as it looks. As is common with those types of talks, in the effort to show all that can be done, the talk gives the feeling of something overwhelming. Applied to real code however, you rarely need all of that and certainly never all at the same time.
That being said, it is still true it's just a tool in the toolbox, not a silver bullet.
whats wrong with SFINAE
@@etherstrip I couldn't agree more. With just a little practice, this approach to writing code is not very complex - you just have to learn how to think about things through a different lens.
It’s less convoluted than using virtual functions
Great presentation.
Thank you - I really appreciate it!
How did we ended up making the syntax even more complex...
Because his goal was not to optimize syntax but run-time cost.
Meantime while CPP 20 becomes mainstream (10 years or so), we can use the CRTP (Curiously Recurring Template Pattern ) pattern that has been around for a long to create static interfaces, unless CPP 20 is covering something else that CRTP can not.
I just wonder how do you implement a heterogeneous container to store CRTP objects? Would you mind sharing some tips?
@@chanaaron2533 You can't. You will end up having to create virtual base class, inherit the CRTP class and other classes from that. Tadaaa and you have just invented polymorphism with extra steps.
Heh. Fellow CRTP-ers. You guys seen C++23's "deducing this?" You gotta admit, that's at least nicer for CRTP... Pretty excited for that one.
That tuple of vectors good idea but need to see in practice
For me, the only good use cases for virtual functions are: (1) having a container that can hold multiple implementations of the same base class and (2) decoupling implementations to have more unit-testable code. Variants or tuples of containers are sometimes a good alternative for case 1. However, there is no really good replacement for case 2. that does not involve some level of indirection.
Also, I should remind people here that the main cost of virtual functions is the fact that they are hard to inline. Their overhead is comparable to normal non-inlined functions, and usually, people mistake the overhead of virtual functions for the overhead incurred by the fact that they allocated their polymorphic objects on the heap.
Totally agree. What would you perfer for real interface? Ftadle?
For the first use case, you should consider using a type-erased solution. (check Klaus Iglberger's talk on type erasure)
Great talk!
Why do you use
auto update() -> void {
and not
void update() {
?
Style consistence
That's just a style choice I guess
Usually a return type name can get complicatedly long so it makes it easier to spot the function name.
I think it's a nice idea to write like this, might be even nicer if C++ would allow to skip the auto part as well:
function() -> (return_type)
For consistency that's a pretty good idea to do. The advantage of auto name() -> type is that you can use the parameter to define the return type. Example:
auto myFunction(auto parameter) -> decltype(parameter.someFunction()) ...
Imagine that someFunction() may have a different type depending on the type of parameter.
You can't do that if you put the return type before the function name.
It's also consistent with lambdas. You can specify the return type in lambdas, contrary to some people's beliefs, but you need to do it with the arrow;
const auto myLambda = [] () -> SomeType { return {1,2,3}; };
So it has a lot of advantages.
it would have been good to have some data showing performance differences using this method. While I think the subject and talk is very interesting, I remain skeptical about the performance gains.
He literally mentioned another talk that already happened that shows hard numbers for the performance benefits, while explaining why he didn't show any numbers.
Just before I gave my talk, Ivica Bogosavljevic gave a talk on performance. It's live on TH-cam already - check it out!
Virtual function performance is often dependent on the compiler to determine if a call is really virtual or not. But on mature CPUs you also have branch prediction, so a virtual function will have zero performance overhead if and only if the CPU can perfectly predict the destination address. On tiny embedded cpus that might be an issue. I agree that a programmer always have to consider if a virtual function is needed or not.
Maybe, maybe not? When discussing runtime polymorphism, the more important scenario is "many objects in a container, processed sequentially". This is what OOP proponents commonly cite as an advantage of their favourite paradigm ("I can store a vector of Employee objects and I don't need to care about the internal implementation details"). In this scenario it's very unlikely that the compiler will be able to devirtualize, because the container is usually populated at runtime. CPU branch prediction can only do so much to help, you'll still likely get a performance penalty. Another factor is the vptr storage, which means that, at the very least, you can store fewer objects in a cache line because each object now occupies an extra 8 bytes.
You have a triple indirection. From the this object, to the vmt table to the code. Tell me which CPU can optimize this away? One better solution was Eiffel, not only gave it perfect multiple inheritance but it was also faster for most cases. THey generated switch statement trampolines on a per function base, not a VMT for the whole class.
On pretty much any modern CPU a virtual function call is a guaranteed branch miss. Make sure they're not on a hot spot in the code at the very least.
Very good!
Thanks so much!
Good presentation on concepts. BUT you don't need unique_ptr / shared_ptr to call virtual functions. They can be stack allocated a passed around as references. In that case with a final keyword it compiler will most likely devirtualize and you write simpler code. Also why would you define all other constructors/operators if you pass-around the type as a pointer/reference.
I would say :
1. You want to represent a value. Use variant for polymorphism when you need it.
2. You want to represent a API to achieve something go for virtuals ;)
I you design the API well the virtual call cost is negligible.
Yes true but then they're polymorphic pointers with the guise of a reference.. This can lead to confusion - usually when I see something taking const Foo& my mind doesn't immediately think ooh this thing's actually polymorphically derived from Bar and implements 3 of its virtual functions. And if they're stack allocated marked final in a visible translation unit the class layout would be resolved and even inlined anyway then there isnt really anything to devirtualize. I think virtual functions are useful for realizing APIs across different sometimes tricky boundaries (extern, PIMPL, dlsym/dyld type stuff) where theres only so much a compiler can do, at that point yah I agree.. you feel way less bad about a bunch of virtual calls because guess what the method u just shimmed i.e the guy next door has 20 more of his own on the callstack!
"They can be stack allocated a passed around as references" - I'd argue the most common scenario is allocating a container of heterogeneous objects at runtime (e.g. read from a database and use a factory to populate a vector); in this case I guess it's obvious that the compiler can't devirtualize. For APIs, I'd be careful about using virtuals because you could have methods that are called very frequently (a common example are naive graphics APIs that add a shape to the canvas, or objects that have a Render method). I guess the more important question is why use virtual at all.
- It is very convoluted and therefore error-prone
- It requires lot of boiler-plate
- You need to know all the types at compile time to create an instance e.g., "Foo". Virtual inheritance allows run-time selection e.g., Base *base = Base::New("Derived")
Yup, this thole presentation is so far from real world application that it's frightening such people take active part in language development. Design by comitee at it's worst.
yeah and in python you dont even have to know the type to create an instance. See i can do your level of criticism too.
Point is that convenience comes at a cost. This talk is about a way to achieve polymorphic behavior without the cost in the first place. Yes its a bit more convoluted and not as convenient as you have to know every type at compile time, but for certain applications its more performant than using polymorphism.
@@dexio85 I did not even watch the video, I knew it was trash, Was looking for a comment to confirm the assumption.
@@DFsdf3443d You can achieve C++ behavior with C, so lets switch to C? What kind of argument is that? Please prove your point for more performance. Assembler is also more performant than C++, yet nobody could ever prove it in a real world application, because the compiler is better at generating assember. take an 100k loc application and rewrite in in assembler, and run the tests.
Can you use polymorphism without virtual?
Can you use concept to achieve this?
Can they serve the same purpose?
Can you feel, Can you feeeeeel myyyy heaaaart?
30:00 downsides is missing a point.
Need to know fixed list of types at the time where you place the container that will store your things.
That means if a 3rd party wants to extend your code in the future, they have no option other than modifying/patching your code.
Where-as with virtual, it wouldn't matter as you just insert a pointer to the base class.
Also it's missing any real performance data. In real world code the cost of virtual function is trival compared to other operations.
@@dexio85 still it is a cost, that can be avoided. Also im not sure i believe you when you say its a small cost, in certain scenarios it can be come quite costly.
he addressed this at he start of the talk, he is not saying to never use the virtual keyword again, he is simply showing a more performant way to achieve polymorphic behavior that may be better suitable in certain applications. Obviously a third party polymorphic library interface isn't a suitable application in this case.
The overusage of "foo" makes this hard to follow
in cases this presentation could substitute virtual, simple switch or even if will do the same i guess
Let the man cook!
Great talk! Type Erasure is not a void* tho
why after 2 slides they have to make it red black and arrows pointing here there, why not explain the concept in simple words?
Could you please provide accurate subtitles for this video?
Awesome sauce so proud of you!
Thanks! Wouldn't be here without you!!
In the final code, is this a good example of using Concepts to avoid the use of CRTP for implementing static polymorphism (to enforce that concrete types implement a specific interface)?
Could you please add the timestamp?
cpp people are working so hard to improve the language so much so, it is getting overwhelming ... "mental exercise" :)
I 100% agree with the "bold statement" - you don't need to use virtual, you don't need to use smart pointers. I used that methodology in my c++20 order book project, which I talk about on my channel
Ok, I came to this because my daytime job is mostly Java, where every non-static method is virtual, and I was trying to see how this would work out. I have to admit our class hierarchies potentially grow deep and wide. It is a lot of behavioral inheritance with abstract methods as placeholders for different variations of behavior depending on where in the hierarchy you are. Somehow I fail to make the connection with this talk. I am guessing your usage of class hierarchies is simply to achieve different goals than we do. So far you seem to say that "if you don't need virtual you can get quite far without it" as well as "concepts can do neat things." That last one is definitely a takeaway for me...
It sounds to me like your primary usage of virtual is primarily the first case on slide 2-1 - binding a specific interface. If that's correct, your takeaway that concepts can serve a very similar function is spot on!
Nice option to virtual.
This guy's name is Chad, not Jonathan
Is the source code of this video released?
No, since no compiler can build it 🤣
Aren't his interfaces missing a virtual destructor?
Edit: should have listened for five more seconds before posting xD
Dude can pick up a career in modeling if he ever gets bored with coding😁
I learned too much on concepts & the main subject. Thank🙏
If anyone who read this comment knows a reference to learn more about cpp concepts please let me know… (video or blog …)
Constructive criticism:
1) Use better code font and size
2) Don't clear your throat constantly in the microphone.
Otherwise thanks for the talk, very interesting.
Please, don't use dark theme
The sample code is completely unreadable. Why use such small fonts?
There's vast areas of white space to the left, right top and bottom. The margins are white, and the code is in dark theme. Unreadable.
What's the presenter thinking?
Code font is too small and so much wasted space around it, unwatchable :(
Great talk! A little peace of advice: Try to eliminate the ahs and ahms, otherwise great content.
Thanks for watching and thanks for the advice!
std::variant is not enough, there are many use cases when it is inapplicable or ineffective such as huge difference between sizeof's, required incomplete type (variant needs to know all types when created while type erasure can be used for recursive types etc)
For example:
struct expression {
expression* a;
expression* b;
};
Its impossible to do with variant.
And yes, virutal functions is bad, but solutions exist already...(cant post links here =)))
the variant will not be recursing much like how a virtual class can't inherit itself
if you are referring to how a class that is included in the variant can contain the variant, that is not possible with OOP and inheritance either, you do not store values of base classes, you store references to them; that is to say that a class satisfying the variant's concept should not be storing the variant but a pointer to it (and all pointers are sizeof(void*) so there's no recursion)
Yes, let me add that you must know that the type is OK for the framework developer, but it is really really bad for the framework user because the framework developer cannot know the type to be defined by the user in advance, which is very disgusting.
so, leaving the dynamic interface instead of static is still a good choice
When i wrote a compile. Storing function pointer with the expression was even better.
Unfortunately I couldn't read the text on the dark background so I stopped watching.
The constant "uh" and coughs etc make this unlistenable
Looking at this code makes me want to say, "C++ people, are you freaking kidding me?". C++ has become an overly complicated mess of a language, it has to go.
Don't worry, almost nobody uses template salad in real world code. C++ is expressive enough even without use of these "modern" features.
And to be really honest, language itself is very stagnant. Whole c++ philosophy these days is "we do that in std". And that led us to atrocities such as std::initializer_list. Or very non-obvious template rules.
Template rules are so stupid, that sometimes I feel like it's simpler to write a code generator in python. Only in most trivial cases they work as intended.
I just kept thinking "wow, I'm glad I'm not using C++ any more".
True. Modern C++ is the sort of thing that makes me think "Rustaceans" might be on to something...
It's a parody to programming. I wonder if these people develop anything useful but examples
Ok, so, name another language where a type can be generic over a *list* of other types. I know one, but you go first
For lists that don't need to be created as instances, I've found the multiton pattern to be very similar in usage, and a common base class is not needed en.m.wikipedia.org/wiki/Multiton_pattern
Very interesting - thanks for sharing!