2

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:

  1. It's complex, arbitrary, or outrageous.
  2. There are alternative solutions that don't require language modification.
  3. The required standard/compiler changes would involve too much effort/work.

My replies:

  1. No it's not; it's the simplest and most logical solution, in my opinion.
  2. 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> and Agent<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?).
  3. 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];
}
bgoldst
  • 34,190
  • 6
  • 38
  • 64
  • Because "is-a" and "is-two-of-a" is not the same thing, those are two different relationships, and C++ only have direct support for the first. – Some programmer dude Feb 26 '15 at 14:44
  • Well if this were possible you would have the problem of a cast of a `DoubleAgent*` to an `Agent*`, because this would be ambiguous. – stefan Feb 26 '15 at 14:44
  • It really sounds like you got it down with your first solution. Would it work in a real system? depends on why/how do you want to use it. – imreal Feb 26 '15 at 14:49
  • "**is**-two-of-a" sounds like "is-a-and-**has**-two-roles"... But that's a subjective opinion and not an objective statement – Christophe Feb 27 '15 at 18:55
  • `typedef Agent TripleAgent[3];` would simplify your example code even further ;) – Sander De Dycker Mar 04 '15 at 10:01
  • @SanderDeDycker, What if you wanted to inherit from other classes as well? I suppose you could make the array a class attribute, but then that's *composition*, which loses some of the benefits of inheritance, such as the ability to override virtual methods (actually you're losing that anyway just by using the typedef). And I've seen it argued that you should never typedef arrays due to lack of clarity; e.g. see http://stackoverflow.com/questions/4523497/typedef-fixed-length-array. – bgoldst Mar 04 '15 at 16:46
  • @bgoldst : my comment was not intended to be taken seriously (indicated by the wink smiley at the end), because I couldn't tell if you were being serious about your example code being simple - I guess you were. – Sander De Dycker Mar 04 '15 at 17:10

4 Answers4

7

Why isn't this supported by C++ ?

The reason why your compiler doesn't accept this kind of multiple inheritance, is because it's explicitely forbidden in the C++ standard:

Section 10.1 point 3: A class shall not be specified as a direct base class of a derived class more than once. [ Note: A class can be an indirect base class more than once and can be a direct and an indirect base class.(...) ]

So regardless of the philosophical justification of such an inheritance, this is invalid C++ :

struct DoubleAgent : public Agent, public Agent {};  // direct base class more than once !!!

The reason is purely syntactical. Supose you'd have the following definition of agent :

struct Agent {  double salary; }; 

Would the double inheritance be valid, you wouldn't have any way do disambiguate which of the two inherited salary you want to access.

How shall this be correctly done in C++ ?

The (existing) standard solution is to use an empty class to disambiguate the multiple role that your base class would have:

struct AgentFromSouth : public Agent {}; 
struct AgentFromNorth : public Agent {}; 
struct DoubleAgent : public AgentFromSouth, public AgentFromNorth {};  // valid

Diambiguation is done as follows:

DoubleAgent spy; 
spy.AgentFromSouth::salary = 10000.0; 
spy.AgentFromNorth::salary = 800.0;

This is not a workaround ! The intermediary classes are not a simple workaround. They offer a consistent subtyping semantic:

  • If a DoubleAgentinherits twice from the same class, this same class has de facto two distinct roles. You'd like to keep these roles anonymous, but the standard forces you to give them a name. It contributes hence to a better understanding of the code by human readers.
  • If a DoubleAgent inherits from (i.e. "is a") AgentFromNorth, then indepenently of the multiple inheritance, I should be allowed to write: AgentFromNorth *p = &spy; Any alternative syntax that you'd propose would need to comply to this principle.

Technically, with a modern compiler such an empty class doesn't generate significant code overhead either (the assembly code generated by my compiler for inheriting via an empty class show is exactly the same as with direct inheritance.)

Further discussion about alternative approaches

The template based solution that you propose is certainly an idea. But both proposed alternatives are just syntactic sugar to give two different names ( Agent<1> or Primary<Agent>) for the same base class. It's exactly what is achieved by creating an intermediate empty "alias" class, with the standard of today. But with more problems.

In fact such a syntax would cause a dependency issue. While for all classes, forward declaration rules, and class visibility are clearly defined for existing construct, this would not be completely the case with your alternative approach.

For example, to define a pointer to a base class, I wouldn't be able to write:

DoubleAgent *pspy = ....; 
Agent *p = pspy;  //  ambiguous:  which role should I use ?  Agent<1> or Agent<2>  ? 

Now you would certainly say that I could very well write something like:

Agent<1> *p = pspy;   

But the meaning of Agent<1> is only relevant in the context of DoubleAgent. It's not a type with it's own independent dfinition. And how would you interpret:

class QuadrupleAgent : DoubleAgent, DoubleAgent {...};  
QuadrupleAgend *pmasterspy;
Agent<1> *p = pmasterspy; // Ambiguous: is it DoubleAgent<1>::Agent<1> or DoubleAgent<2>::Agent<1> ? 

All these issues are perfectly well handled and in a consistent manner by the standard. Your short-circuit syntax would raise more questions than it solves. What would then be the benefit of creating such new and cumbersome language constructs to provide alternative for existing simple constructs ?

Community
  • 1
  • 1
Christophe
  • 68,716
  • 7
  • 72
  • 138
  • 2
    Right. C++ already lets you do what the OP wants. It just requires that you disambiguate accesses to your multiply inherited base class by explicitly naming them yourself through "pass through" classes from which you inherit instead. – jschultz410 Mar 02 '15 at 06:01
  • I guess I would argue that you shouldn't *have* to create "pass through" classes to multiply inherit from one base class; that should be achievable directly through built-in syntax. – bgoldst Mar 03 '15 at 04:07
  • 1
    There shall be an inherent consistency between language constructs. What is needed to address the problem is to give an alias (eg "public Agent as AgntOfNorth..."). But all other constructs giving alias require a distinct statement. In this regards using pass-throuh classes uses built-in syntax and is consistent, without endangering rxisting code or reusing in amiguous way other existing syntax such as <1> or [1] or {1} or anything else having already a different semantic – Christophe Mar 03 '15 at 06:40
  • 1
    @bgoldst That's not an argument, it's just a preference. I daresay that most other people have a different preference -- that the language not be bloated with special syntax to handle such a rare case when existing syntax works very well. And there's a very long queue of other "built-in syntax" that has been proposed that would be of considerably more value. – Jim Balter Mar 08 '15 at 07:39
4

I disagree with that statement "... since there would be no way to qualify which superclass to use".

When they say "there would be no way to qualify which superclass to use" they mean "there would be no way to qualify which superclass to use without inventing an additional syntax". Your way of "qualifying" it by pretending it's an array is one such invented syntax; one could invent countless other syntaxes that do not look like other syntactic constructs, for example

Animal@::method()  // @ means "first"
Animal@@::method() // @@ means "second"

My syntax does look entirely arbitrary, but so does yours.

Of course there could be other, less outrageous, ways of working around this issue by making the language even more complex. The key question, however, what benefit would you achieve by making the language more complex? There is no obvious answer to this question, because you could potentially realize nearly all benefits of double inheritance through composition without making any changes to the language.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Thanks for your answer, however: The language is already quite complex, isn't it? I don't think this would add any significant complexity. And I reiterate that I think the syntax `Animal[1]::method()` is quite simple and logical (no offense, but it's more logical than your `Animal@::method()` syntax). Why would you describe it as outrageous? And what are these "less outrageous" ways of working around this issue by making the language more complex? – bgoldst Feb 26 '15 at 17:15
  • 1
    @bgoldst The fact that the language is already complex is not a license to add more complexity to it. The syntax `[1]` creates more problems than solutions. Would you allow the index to be a non-const expression? What would happen if index were outside the range? What would you do if `Animal` had virtual functions? How would you access `Animal`'s member variables from member functions of `Dog`? What's the use case that you are trying to solve? Could you not solve it by introducing another class in the middle? – Sergey Kalinichenko Feb 26 '15 at 17:36
  • Answers to your questions: 1. No. 2. Compilation error. 3. The same thing you would do if multiple distinct base classes had identical virtual functions. 4. The same way you would access distinct base class member variables from derived class methods. 5. I don't have a real-life use case, but I think my `DoubleAgent` example is a reasonable example of how it could make sense. 6. Yes, but that would be a workaround, not a justification for lack of support for this potentially useful feature. – bgoldst Feb 26 '15 at 18:22
  • 2
    @bgoldst Your `DoubleAgent` example lacks functionality to imply how you would want it to make sense. When you add a feature to a language, saying that it is "potentially useful" is not enough to justify the effort of multiple people who implement standard-compliant C++ compilers. You need a very compelling reason to do it. You need to be sure that the feature is worth the effort, because the effort is not free ([take a look at this post by a guy from MS compiler team](http://blogs.msdn.com/b/ericlippert/archive/2003/10/28/how-many-microsoft-employees-does-it-take-to-change-a-lightbulb.aspx)). – Sergey Kalinichenko Feb 26 '15 at 18:46
  • @bgoldst "I think my DoubleAgent example is a reasonable example of how it could make sense." -- You think wrong. *Having* an Agent and also an Agent makes sense and is useful ... they are different Agents. *Being* an Agent and also an Agent makes no sense and is useless. If the goal is to inherit two copies of Agent's members, then you're doing it wrong -- use composition. "not a justification for lack of support for this potentially useful feature." -- the justification is that it would be stupid to bloat C++ with such nonsense. – Jim Balter Mar 08 '15 at 07:50
1

Because you can't talk about a particular base class.

Double agent has two agent bodies. Let's call them Agent1 and Agent2. Suppose each Agent has a member named loyality. If you execute the following code:

loyalty = "GoodGuys";

Does that change Agent1.loyality or Agent2.loyality.

The following IS legal:

class Agent{... std::string loyality;};
class PrimaryRole : public Agent {};
class SecondaryRole : public Agent{};

class DoubleAgent : public PrimaryRole, public SecondaryRole
{
   void setLoyalities()
   {
       PrimaryRole::loyality = "GoodGuys";
       SecondaryRole::loyality = "BadGuys";
   }
};

Note for language lawyers: This, too, would be legal:

class DoubleAgent : public Agent, public SecondaryRole {...};

but fairly useless because referring to Agent::loyality would be ambiguous.

Dale Wilson
  • 9,166
  • 3
  • 34
  • 52
  • The code is valid, but havig `loyality` in `Agent`, and virtual inheritance there is only one `loyality` in `DoubleAgent`. Thus both addressed loyalities refer to the same one. Hence the last value "BadGuys" would remain the final one. – Christophe Feb 27 '15 at 18:52
  • Good point, christophe. I'll edit the code to avoid being misleading. – Dale Wilson Mar 02 '15 at 14:57
1

The reason a compiler does not permit direct inheritance from a class more than one is because the C++ standard says so.

The reasons the standard says so would be a combination of things.

Firstly, it is not technically necessary, as your "possible solution" demonstrates it is possible to achieve the effect you desire relatively easily, just a little less directly.

Second, permitting such inheritance would require a cascade of other changes to language syntax and semantics so that a member function of a class can unambiguously refer to members of the multiply inherited bases. For example;

struct Agent
{
     int height;
};

struct DoubleAgent: Agent, Agent
{

};

int main()
{
      DoubleAgent a;
      a.height = 42;     //  this would be ambiguous
}

To work with the two inherited height members, it would be necessary to allow them to be referred to unambiguously. Within rules of the language this would be something like

 a.Agent::height = 42;

That works fine within current rules, since direct inheritance from Agent is not permitted. But it would still be ambiguous if the member is directly inherited twice (would it refer to the left one or the right one?).

Somehow, to resolve this, it would be necessary to introduce new syntax and associated rules so the syntax works.

Such modifications to the language would also need to work cleanly for a situations like

struct TripleAgent: Agent, Agent, Agent
{

};

struct QuadrupleAgent: Agent, Agent, Agent, Agent
{

};

// etc etc

After all, if it was possible to directly inherit from the same struct/class twice, why not 3,4,5, 0r 50 times? The list of complications goes on. The rules would need to make sense to programmers, and compilers would need to be even more complicated than they are to make sense of the new syntax and semantics. Even if "more than twice" was forbidden.

All this complicated stuff for a feature that few people would actually need and for which (as you have shown) there are viable alternatives.

I simply doubt that the C++ committee has enough of a hankering for unnecessary complexity to allow such things.

Rob
  • 1,966
  • 9
  • 13