Page 2 of 2

Posted: Tue Jan 15, 2008 12:04 pm
by Colonel Kernel
Solar wrote:Erm... which achieves what, exactly?
It avoids MI where MI might otherwise cause problems. To put it another way, it makes fine-grained re-use through inheritance feasible, safe, and efficient. MI does not (which is one of the reasons why the FAQ advises against reuse-through-inheritance).
I mean, aside from making a total mess of what should be an inheritance graph,
It's not a total mess -- it's perfectly linear, and completely documented right at the point where Derived is declared.

Derived => BarBase< FooBase <UltimateBase> > => FooBase< UltimateBase > => UltimateBase
scaring the majority of C++ coders (who didn't really grasp templates even after all those years) witless, and turning it into something butt-ugly? (Not that the rest of C++ actually smells of roses. I) )
It's a more flexible form of inheritance... I wouldn't call it butt-ugly. It is the basis for policy-based class design and is similar to the Curiously Recurring Template Pattern.

For example, you can use this technique to stitch together several "partial classes". Each mixin could implement one aspect of the overall class, like a set of related properties and their getters/setters. I've found this technique extremely useful for fine-grained code re-use in the past. Maintainers haven't had any trouble with it -- Like I said, once you understand how it works, it's completely self-documenting.
Most of the problems involved in a "diamond pattern" can be solved by a) proper design, and / or b) public virtual inheritance.
I disagree about "proper design". Some problems naturally lead to the multiple "is-a" relationship. Look at the example in the FAQ you linked to (e.g. -- GasPoweredLandVehicle). If you mean using an alternative like Bridge, that works if the conditions are right, but requires a lot of extra code and removes a certain amount of flexibility.

Regarding virtual inheritance, I suppose it works, but IMO it is more conceptually complicated than template-based mixins. Just look at all the guidelines for proper use and the rules regarding constructor/destructor calling order in the FAQ you linked to... Yuck. The mixin way just uses single inheritance, which is very easy to understand.

An aside: Must your tone be so belligerent when you discuss language issues?

Posted: Wed Jan 16, 2008 1:36 am
by Solar
First up, sorry if I sound belligerent. I will blame a combination of sleep deprivation, language barrier, bad habits picked up by years of being the resident C/C++ language lawyer, and a somewhat weird / sarcastic kind of humor. I growl, I don't bite. 8) But the criticism is accepted.

I have a deep, ingrained mistrust of mine against "tricks" being played with a language "for the greater good". I admit I still didn't quite grasped the benefit of your "mixin" pattern. Perhaps you could scetch up how the equivalent inheritance graph would look like if MI were used? I keep seeing only a linear inheritance there (Derived -> BarBase -> FooBase -> UltimateBase), so I don't see where this solves an MI issue.

What I meant with "proper design" is this: In Java, each class can only inherit from one base class, but can implement any number of interfaces. This is generally accepted to be a solution to the MI problem. Correct? And you wouldn't claim this puts too much of a dent into the expressiveness of the Java language, correct?

Now, a C++ pure virtual class is semantically equivalent to a Java interface. Consider naming all your virtual classes "SomethingInterface". Consider applying some self-discipline and never inheriting from more than one non-"Interface" class. Result, by "proper design" you solved the MI problem the same way as Java did.

It all boils down to the old argument of "language restraints" vs. "programmer discipline". Both have their pro's and con's, but C/C++ have openly and decidedly announced to be in the "programmer discipline" camp, so I don't think it's fair to fault them for sticking to their mantra. It's somewhat like blaming a farm horse being slow or a thoroughbred for not being able to pull a plough.

Posted: Wed Jan 16, 2008 3:26 am
by 01000101
hmmm... this seems like it could be a good idea. but why not just implement a struct and return that? I agree that pointers are the devil!
have you ever tried to create this? it would be a very interesting project that could very well benefit the devving community.

Posted: Wed Jan 16, 2008 12:52 pm
by Colonel Kernel
Solar wrote:I have a deep, ingrained mistrust of mine against "tricks" being played with a language "for the greater good".
Heh... While I agree with you in principle, I think it's too late to put that genie back in the bottle. The entire templates feature of C++ is a "trick" for the "greater good" IMO. ;)
I admit I still didn't quite grasped the benefit of your "mixin" pattern. Perhaps you could scetch up how the equivalent inheritance graph would look like if MI were used? I keep seeing only a linear inheritance there (Derived -> BarBase -> FooBase -> UltimateBase), so I don't see where this solves an MI issue.
Sorry, I didn't use a very realistic example. I was just trying to show the mechanism behind it. Here is a more realistic example:

Code: Select all

// This is from an SDK. The interfaces are consumed by one part
// of the system and are meant to be implemented by the customer
// (i.e. -- the developer using the SDK).

class IConnection { ... }; // Pure abstract base class -- no data, no method implementations.

class IVendorSpecificConnection : public IConnection { ... };

template <class Base>
class ConnectionBase : public Base { ... }; // Implements the common functionality of IConnection.

// These are customer classes (i.e. -- not part of the SDK).
class CompanyAbcConnection : public ConnectionBase<IConnection> { ... };

class CompanyAbcVendorSpecificConnection : public ConnectionBase<IVendorSpecificConnection> { ... };
What this avoids is more of a "triangle" than a diamond. The issue is, without using this technique, how would you create a partial implementation that can be shared by all specializations of IConnection? The Java answer would be to use delegation, which is possible but messy.

This is what I really want in C++, but I don't think it's possible:

Code: Select all

class ConnectionBase { ... }; // No particular inheritance relationship -- just implementation.

class CompanyXyzConnection : public ConnectionBase, public IVendorSpecificConnection { ... }; // ConnectionBase's methods implement the IConnection portion of IVendorSpecificConnection for me.
What I meant with "proper design" is this: In Java, each class can only inherit from one base class, but can implement any number of interfaces. This is generally accepted to be a solution to the MI problem. Correct? And you wouldn't claim this puts too much of a dent into the expressiveness of the Java language, correct?
To re-phrase what you said, multiple interface inheritance is ok, but multiple implementation inheritance is not. This is generally accepted in the community of static OO languages like Java, C#, and C++. My argument is that this is a technical limitation, not a logical one. The objection to using inheritance as a re-use mechanism is historical -- it is a result of the complexities of the MI implementation in C++. It is not inherently nonsensical to do. This template "trick" I've showed you (which is really equivalent to traits in Scala -- I suggest you take a look) makes implementation inheritance in C++ safe, easy, and self-documenting.