This code is invalid:
struct Agent {};
struct DoubleAgent : public Agent, public Agent {};
Because:
> g++ -c DoubleAgent.cpp
DoubleAgent.cpp:2:43: error: duplicate base type ‘Agent’ invalid
struct DoubleAgent : public Agent, public Agent {};
^
Why?
I don't think this violates the concept of inheritance; if a class can have an is-a relationship with a base class, then shouldn't it be able to have an is-two-of-a relationship with a base class? Especially considering that, since C++ supports multiple inheritance, a class can already be multiple different base types.
Furthermore, this is already sort of supported indirectly by the so-called diamond problem, where multiple (distinct) base classes can inherit from a common base class.
I found a couple of quotes from Wikipedia that seem to imply that this is a common but not universal constraint in OO languages, may only be a result of fears of syntactic complexity, and is an undesirable constraint:
Wikipedia: Inheritance (object-oriented programming)#Design constraints
Design constraints
Using inheritance extensively in designing a program imposes certain constraints.
For example, consider a class Person that contains a person's name, date of birth, address and phone number. We can define a subclass of Person called Student that contains the person's grade point average and classes taken, and another subclass of Person called Employee that contains the person's job-title, employer, and salary.
In defining this inheritance hierarchy we have already defined certain restrictions, not all of which are desirable:
- Singleness: using single inheritance, a subclass can inherit from only one superclass. Continuing the example given above, Person can be either a Student or an Employee, but not both. Using multiple inheritance partially solves this problem, as one can then define a StudentEmployee class that inherits from both Student and Employee. However, in most implementations, it can still inherit from each superclass only once, and thus, does not support cases in which a student has two jobs or attends two institutions. The inheritance model available in Eiffel makes this possible through support for repeated inheritance.
Wikipedia: Multiple Inheritance#Mitigation
C++ does not support explicit repeated inheritance since there would be no way to qualify which superclass to use (i.e. having a class appear more than once in a single derivation list [class Dog : public Animal, Animal]).
I disagree with that statement "... since there would be no way to qualify which superclass to use". Couldn't the compiler just allow some kind of indexing of the base qualifier, e.g. Animal[1]::method()
(and, in the same vein, perhaps allow collapsing the repetition in the class definition to struct DoubleAgent : public Agent[2] {};
)? I don't see any problem with that, and isn't it quite a simple solution, syntactically and conceptually?
I believe I've found a couple of possible workarounds, although I can't say for certain whether they would actually work in a real system, or whether they would cause insurmountable problems:
1: Disambiguating template non-type parameter on the base class.
template<int N> struct Agent {};
struct DoubleAgent : public Agent<1>, public Agent<2> {};
2: Disambiguating intermediate base classes (diamond inheritance).
(Note: I didn't have to use template classes with CRTP here, but I think that would aid in generality and reduction of duplicate code, if this pattern (anti-pattern?) were to be used repeatedly.)
template<typename T> struct Primary : public T {};
template<typename T> struct Secondary : public T {};
struct Agent {};
struct DoubleAgent : public Primary<Agent>, public Secondary<Agent> {};
Please comment on:
- the general concept of inheritance / multiple inheritance / repeated inheritance,
- the rationale behind non-support for repeated inheritance in C++ (or other languages),
- the rationale behind the apparent support for repeated inheritance in Eiffel (or other languages), and
- my possible workarounds shown above.
Edit: I'd like to emphasize my suggested syntax for repeated inheritance in C++, since I feel that its simplicity and comprehensiveness are being overlooked or underappreciated by the answers.
The following code demonstrates the idea, covering both casting (which was commented on by @stefan) and member qualification:
struct Agent { int id; };
struct TripleAgent : public Agent[3] {};
int main() {
TripleAgent tripleAgent;
tripleAgent.Agent[0]::id = 1;
tripleAgent.Agent[1]::id = 2;
tripleAgent.Agent[2]::id = 3;
Agent* agent0 = (Agent[0]*)tripleAgent;
Agent* agent1 = (Agent[1]*)tripleAgent;
Agent* agent2 = (Agent[2]*)tripleAgent;
}
As you can see, Agent[N]
basically becomes a type name in its own right, but is only valid when qualifying or casting a type that possesses N+1
or more instances of the underlying type. This adds very little complexity to the language, is very intuitive (in my opinion) since it mirrors array indexing which programmers are already familiar with, and I believe is all-encompassing (i.e. doesn't leave any ambiguities).
The main difficulties answerers seem to be having with it are as follows:
- It's complex, arbitrary, or outrageous.
- There are alternative solutions that don't require language modification.
- The required standard/compiler changes would involve too much effort/work.
My replies:
- No it's not; it's the simplest and most logical solution, in my opinion.
- Those are workarounds, not solutions. You shouldn't have to create an empty class or have the compiler generate a redundant template variation to disambiguate the parent classes. And, for the template workaround, I believe this would involve redundancy in the generated binary, as the compiler would generate separate implementations of each variation (e.g.
Agent<1>
andAgent<2>
), when that should not be necessary (please correct me if I'm wrong; is the compiler smart enough to know that the template parameter is not used in the class definition, so it won't generate separate code for each distinct argument to that parameter?). - I suppose this is the best argument; the usefulness of the feature may not justify the effort to make it happen. But this is not an argument against the idea itself. When I asked this question, I was hoping for a more theoretical, academic discussion of the idea, not a "we don't have time for this" reaction.
Edit: Here's another possible syntax, which I actually like better. It allows a supertype name to be accessed as a member, which it kind of is:
struct Person { int height; };
struct Agent { int id; };
struct TripleAgent : public Person, public Agent[3] {};
int main() {
TripleAgent tripleAgent;
tripleAgent.Person.height = 42;
tripleAgent.Agent[0].id = 1;
tripleAgent.Agent[1].id = 2;
tripleAgent.Agent[2].id = 3;
Person& person = tripleAgent.Person;
Agent& agent0 = tripleAgent.Agent[0];
Agent& agent1 = tripleAgent.Agent[1];
Agent& agent2 = tripleAgent.Agent[2];
}