News for hackers. Stuff that matters.
Re:Goals and Priorities for C++ by mehrdadn on Thursday May 21, 2020 @11:29PM (#23268408)
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.
Re:Goals and Priorities for C++ by gumby on Friday May 22, 2020 @01:37AM (#23268962)
Virtual 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.

Re:Goals and Priorities for C++ by mehrdadn on Friday May 22, 2020 @01:47AM (#23269013)
Interesting, 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.
Re:Goals and Priorities for C++ by gumby on Friday May 22, 2020 @02:07AM (#23269107)
This 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.

Re:Goals and Priorities for C++ by mehrdadn on Friday May 22, 2020 @02:23AM (#23269189)
Oh wow. So it sounds like what you got out of virtual inheritance here is avoiding some kind of ABI breakage? That's interesting, I've never come across this use case before, but I'll be thinking more about it and looking out for it in the future. Thanks!
Re:Goals and Priorities for C++ by gumby on Friday May 22, 2020 @03:29AM (#23269531)
No, it was about encapsulation. Consider the save dialogue: if wants to have an array of the json saver, sql saver etc and perhaps display them in a pull down list. Virtual inheritance let’s you make the std::vector of the base class. Otherwise what could you use? A pointer, and manypually reimplement the vtable?

Also this means changes to the underlying representation don’t always require changes to the dependent classes, which I suppose you could consider an ABI change.

Maybe an easier way to characterize it is to consider the virtual classes a way to implement plug ins.

Re:Goals and Priorities for C++ by maxlybbert on Friday May 22, 2020 @09:51AM (#23272034)
One annoying thing about C++ is that certain terms and keywords are overloaded. So, while having virtual functions in a base class is a very common design approach, it isn’t “virtual inheritance” ( https://en.wikipedia.org/wiki/Virtual_inheritance ).

Many people consider virtual inheritance a complexity of the language. Why is it possible for a class to inherit from a base class multiple times through multiple paths (the standard case) or to have the compiler coalesce those paths so that the child class only inherits once from the parent/grandparent? The thing is, once you have multiple inheritance, somebody is going to try to inherit from the same base class multiple times. The language can either support it or declare it an error. It’s not clear to me why it’s wrong to for the language to allow it, even though everywhere I’ve worked with a C++ coding standard has said “if your design requires virtual inheritance, discuss your design with other experienced programmers to see if that can be changed.”

Re:Goals and Priorities for C++ by gumby on Friday May 22, 2020 @12:13PM (#23273641)
> One annoying thing about C++ is that certain terms and keywords are overloaded.

It's a no-win choice, though I agree C++ chose the wrong path: trying as much as possible to avoid adding new keywords. It leads to the problem you describe, as well as some confusion in this very thread (which could be my fault, but honestly I don't know -- that's the problem!).

The committee seems to have moved away from this over the years which has lead to complaints of too many keywords and identifiers. Which is why I call it no win.

The function virtual specifier in the base clause is reasonable: if you store something into parent_class.instannce_var and there are two copies of parent_class in your structure which one is stored to and is the choice consistent in all compilation units? This specifier guarantees that the answer is "yes". I don't know why it isn't the default though.

More generally, back in the day I used to use a bunch of different method combinators with flavors (later CLOS). Apart from the useful :before and :after, they generally lead to undebuggable code.

Re:Goals and Priorities for C++ by maxlybbert on Friday May 22, 2020 @01:35PM (#23274730)
I understand why they try to reuse keywords and symbols, and why they intentionally pick really ugly keywords when they add new ones. I can’t say they’re wrong, but I still find it annoying.
Re:Goals and Priorities for C++ by gray_-_wolf on Sunday May 24, 2020 @05:40AM (#23290382)
They could just go the C-way and introduce new keywords in reserved namespace (`_Static_assert`) and provide opt-in header to make them less ugly (`assert.h` and `static_assert` macro).
Re:Goals and Priorities for C++ by mehrdadn on Friday May 22, 2020 @03:35AM (#23269564)
Okay then I'm lost. Why can't you have JsonSaver, SqlSaver, etc. inherit from a Saver class the normal way, and store instances of them in a std::vector<std::unique_ptr<Saver>>? Then you'd just need struct Saver { virtual ~Saver() { } virtual void save(...) { ... } } along with struct JsonSaver : Saver { void save(...) override { ... } } and it should work, right? I'm confused why you need a virtual base class on top of that. Are you by any chance confusing "base classes with virtual methods" with "virtual base classes" (which may or may not contain virtual methods)?
Re:Goals and Priorities for C++ by gumby on Friday May 22, 2020 @12:02PM (#23273502)
Yes, I mean literally base classes with vtables not the "virtual" specifier of a superclass ("base class" in C++). Apologies for the confusion!

I do think it's a useful feature, except as I think most people finally agree, MI is more often than not a cause for confusion.

It's funny that of all the different method combinators (another source of confusion) only that one was chosen.

BTW I don't think a unique_ptr is needed in the case I described.

Re:Goals and Priorities for C++ by w0utert on Friday May 22, 2020 @06:35AM (#23270579)
Maybe 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?

Re:Goals and Priorities for C++ by scott_s on Friday May 22, 2020 @10:29AM (#23272457)
> Maybe I’m misunderstanding, but by virtual base classes you mean base classes with strictly pure abstract methods, that can be used with virtual inheritance?

No. Virtual base classes come up in multiple inheritance.

Consider you have a class Circle. It derives from both Shape and Savable. But Shape and Savable both derive from a parent base class Base. Let's say that Base has some member function, print. Also assume that neither Circle, Shape or Savable have an implementation of print. So when you have a Circle object, and you call print, does it go to Shape::print or Savable::print? If Base is a normal (non-virtual) base class, it's ambiguous. The compiler can't resolve it, as the Circle object literally has two Base instances inside of it: one from Shape and one from Drawable. The inheritance hierarchy has two roots, both of which are an instance of Base.

But if Base is a virtual base class, then Circle will only have one instance of Base. The inheritance hierarchy has only one root, which is Base.

This is generally called "the diamond problem": https://stackoverflow.com/questions/2659116/how-does-virtual...

Re:Goals and Priorities for C++ by mehrdadn on Friday May 22, 2020 @06:53AM (#23270689)
Can't you just cast though? Like this: 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.)

Re:Goals and Priorities for C++ by w0utert on Friday May 22, 2020 @08:24AM (#23271317)
I 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

Re:Goals and Priorities for C++ by cordenr on Friday May 22, 2020 @04:16AM (#23269764)
Virtual 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.
Re:Goals and Priorities for C++ by mehrdadn on Friday May 22, 2020 @04:20AM (#23269789)
> 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.

Re:Goals and Priorities for C++ by correct_horse on Friday May 22, 2020 @12:23AM (#23268606)
Digraphs and Trigraphs seem pretty silly to me. Multiple inheritance is bad, but idk how much code would break if it were removed.
Re:Goals and Priorities for C++ by mehrdadn on Friday May 22, 2020 @12:28AM (#23268630)
Multiple 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.
Re:Goals and Priorities for C++ by pkolaczk on Friday May 22, 2020 @01:18AM (#23268869)
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. Rust and Go can do without inheritance at all.
Re:Goals and Priorities for C++ by jcelerier on Friday May 22, 2020 @02:37AM (#23269257)
> 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(); }
Re:Goals and Priorities for C++ by MaxBarraclough on Friday May 22, 2020 @04:57AM (#23269990)
> 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_p...

[1] https://www.fluentcpp.com/2017/12/12/mixin-classes-yang-crtp...

Re:Goals and Priorities for C++ by jcelerier on Friday May 22, 2020 @05:18AM (#23270114)
yes, 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 :-)
Re:Goals and Priorities for C++ by gpderetta on Friday May 22, 2020 @09:08AM (#23271603)
CRTP 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).
Re:Goals and Priorities for C++ by gpderetta on Friday May 22, 2020 @09:06AM (#23271584)
Heavy 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.

Re:Goals and Priorities for C++ by ncmncm on Friday May 22, 2020 @05:47PM (#23277429)
Right. 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.

Re:Goals and Priorities for C++ by gumby on Friday May 22, 2020 @01:31AM (#23268929)
Digraphs and trigraphs were removed (in C++17 IIRC). Non-twos-complement arithmetic and non-IEEE-754 floating point have also been removed.
Re:Goals and Priorities for C++ by humanrebar on Friday May 22, 2020 @07:55AM (#23271116)
Trigraphs were removed. Digraphs are still in the language.