5 ms·
Yeah. Literally the only thing I can think of off the top of my head that you can probably remove from C++ without significant damage is virtual base classes, a
by mehrdadn 6y ago
Yeah. Literally the only thing I can think of off the top of my head that you can probably remove from C++ without significant damage is virtual base classes, and even that I'm not really sure about. (Curious if anyone here has had much of a use for them.) But anything else seems like it would just fundamentally destroy the language for various slices of its users.
- correct_horse 6y agoDigraphs and Trigraphs seem pretty silly to me. Multiple inheritance is bad, but idk how much code would break if it were removed.
- mehrdadn 6y agoMultiple inheritance is vital to C++. You wouldn't want to remove it any more than you'd remove interfaces from Java. It would break all kinds of things. With you on digraphs and trigraphs, though.
- pkolaczk 6y agoOnly if you write C++ like Java. But there are also coding styles heavily based on templates, and then the use of multiple, or any inheritance is rare. Rust and Go can do without inheritance at all.
- jcelerier 6y ago> Only if you write C++ like Java. But there are also coding styles heavily based on templates, and then the use of multiple, or any inheritance is rare. It's super common to implement mix-ins, which is as based on templates as it's possible to be, with multiple inheritance, e.g. struct featureA { int x; }; struct featureB { const char* foo{}; void print(); }; etc and then template<typename... Args> struct mixin: Args... { template<typename... CtorArgs> mixin(CtorArgs... args) : Args{args}... { } }; using foobar = mixin<featureA, featureB>; void f() { foobar{123, "foo"}.print(); }
- MaxBarraclough 6y ago> It's super common Curiously recurring, even! [0] Although what you're showing seems to be a variation on the curiously recurring template pattern, as the classes being mixed are listed 'symmetrically' without much regard for which will inherit from which. I found a blog post on 'mixin classes', which are like the CRTP but invert the inheritance relation. [1] What you've shown seems to be a third way. [0] https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern https://en.wikipedia.org/wiki/Curiously_recurring_template_p... [1] https://www.fluentcpp.com/2017/12/12/mixin-classes-yang-crtp/ https://www.fluentcpp.com/2017/12/12/mixin-classes-yang-crtp...
- jcelerier 6y agoyes, it's not proper CRTP but I did not want to spend too much time on that... just show that multiple inheritance has its uses even in fairly generic, non-OOP code :-)
- gpderetta 6y agoCRTP is a common use case. Also inheritance is often used to implement type level recursion (although this is significantly less needed now that we have argument packs).
- gpderetta 6y agoHeavy template base code uses inheritance all the time. But here is no expectation of is-a relationship there, it is purely for code reuse. In template code, the is-a is better expressed as modelling a concept (which is done mostly via structural typing, not nominal typing). It is unfortunate that there is no explicit way to distinguish OOP is-a from code reuse, other than the presence of virtual functions in base classes, but in my 15 years of professional use of c++ I can't remember it ever being an issue.
- ncmncm 6y agoRight. Sometimes language features support an architectural pattern, sometimes they are just mechanism. And sometimes the same feature is one, sometimes the other. There is no shame in it. Language features are there to be used.
- gumby 6y agoDigraphs and trigraphs were removed (in C++17 IIRC). Non-twos-complement arithmetic and non-IEEE-754 floating point have also been removed.
- humanrebar 6y agoTrigraphs were removed. Digraphs are still in the language.
- gumby 6y agoVirtual base classes are extremely important for certain cases such as extending library classes (that were designed for it). Virtual baseclasses are rarely important I admit, but provide functionality you can’t get any other way. I recently worked on a large system that had three virtual baseclasses. But trying to do it any other way would have involved a lot of painful head stands. I designed BFD using a vtable implemented manually in C. It was quite painful though there was no other choice. I’m glad I don’t have to deal with that any more.
- mehrdadn 6y agoInteresting, thanks for sharing! What would've happened if you had just used regular base classes in that case? Would you happen to have an illustrative example that could show what problems it would cause in practice/why it couldn't be designed differently? I mean besides just citing "multiple inheritance", which isn't a problem in itself—what matters is how you intend to use it and what might go wrong.
- gumby 6y agoThis did notrequire multiple inheritance.* It is for the serialization system for a complex semantic graph library. The library itself did no I/O. That was fine for writing various UIs and programs that used the library. But how to save and restore the graphs? They included a bunch of statistical and bookkeeping data not exposed in the UI. I wrote a “serializer/deserializer” package that did t actually save anything but could represent the data and swizzle it so the in-memory structures could use raw pointers. This could be validated as correct in CI. Then in a separate I/O library I wrote a module that could generate JSON and store it in a file, using specializations of these virtual baseclasses. When the underlying library changed representation client code didn’t have to written. My JSON thing was too slow so someone wrote one that backed into a SQL database. A lot faster, though versioning went out the window. Anyway someone could write a fast binary format if that matters and they should all continue to work. * I mostly gave up on MI in the mid 80s when it was clear it made encapsulation harder to understand (and this was in a language with explicit method combinators too). However I still find mixin classes quite valuable, though again not in widespread use.
- cordenr 6y agoVirtual inheritance changes the order that sub-objects are constructed and destructed. This might be used where a base class manages the lifetime of a resource and other types in the hierarchy operate on that - and so need the resource to be "alive" even during their destruction.
- mehrdadn 6y ago> This might be used where a base class manages the lifetime of a resource and other types in the hierarchy operate on that - and so need the resource to be "alive" even during their destruction. Confused, isn't that already the case normally? Base classes are already destroyed after derived classes, and owning pointers die before their targets.
- w0utert 6y agoMaybe I’m misunderstanding, but by virtual base classes you mean base classes with strictly pure abstract methods, that can be used with virtual inheritance? Because in that case there is a very straightforward and useful use case for them, which is composing interface classes using multiple inheritance. For example in some piece of graph-based code I’ve been working on recently, all types returned through public interfaces are pure abstract interfaces. There are different reasons for this, one of them being difficulty to automatically wrap the implementation classes themselves to Python and Java with SWIG, because they use CRTP and other template constructs that are fundamentally incompatible with automatic script interface generation. There is a base interface class INode with an implementation class template Node<T>, but there are also derived interface classes that add additional interfaces to INode, such as ICompositeNode for example. Without virtual base classes it is impossible to have an implementation class CompositeNode<T> that derives from both ICompositeNode (for the interface) and Node<T> (for the implementation), because it is ambiguous. Virtual inheritance solves this. I never considered this a very special or difficult use case, it seems pretty much any kind of component-bases programming using abstract interface classes would require this sooner or later?
- mehrdadn 6y agoCan't you just cast though? Like this: https://gcc.godbolt.org/z/Mq74Bo https://gcc.godbolt.org/z/Mq74Bo I get that it's a bit of an inconvenient workaround, but it doesn't seem impossible to handle? (At least as far as I've thought it through. I might be missing something though.)
- w0utert 6y agoI would have to go back to the code to see what the difference is with your example, but I think the problem was with overload resolution, where the base interface/implementation classes have a function by the same name as the derived interface/implementation classes but with a different signature. The compiler error was already at the declaration site, and not at the point the classes where instantiated. Maybe I'm not even fully grasping the problem myself, but in any case virtual inheritance on the interface classes resolved it ;-S