Multiple inheritance + virtual function mess
Asked Answered
P

6

19

I have a diamond multiple inheritance scenario like this:

    A
  /   \
 B     C
  \   /
    D

The common parent, A, defines a virtual function fn().
Is it possible for both B and C to define fn()?
If it is, then the next question is - can D access both B and C's fn() without disambiguation? I'm assuming there is some syntax for this..
And is it possible for D to do that without knowing specifically who are B and C? B and C can be replaces by some other classes and I want the code in D to be generic.

What I'm trying to do is to have D somehow enumerate all of the instances of fn() it has in its ancestry. Is this possible in some other means that virtual functions?

Propagation answered 5/3, 2009 at 19:59 Comment(0)
J
21

Unless you overwrite fn again in D, no it is not possible. Because there is no final overrider in a D object: Both C and B override A::fn. You have several options:

  • Drop either C::fn or B::fn. Then, the one that still overrides A::fn has the final overrider.
  • Place a final overrider in D. Then, that one overrides A::fn aswell as fn in C and B.

For example the following results in a compile time error:

#include <iostream>

class A {
public:
    virtual void fn() { }
};

class B : public virtual A {
public:
    virtual void fn() { }
};

class C : public virtual A {
public:
    virtual void fn() { }
};

// does not override fn!!
class D : public B, public C {
public:
    virtual void doit() {
        B::fn();
        C::fn();
    }
};

int main(int argc, char **argv) {
  D d;
  d.doit();
  return 0;
}

You can, however derive non-virtual from A in C and B, but then you have no diamond inheritance anymore. That is, each data-member in A appears twice in B and C because you have two A base-class sub-objects in an D object. I would recommend you to rethink that design. Try to eliminate double-objects like that that require virtual inheritance. It often cause such kind of conflicting situations.

A case very similar to this is when you want to override a specific function. Imagine you have a virtual function with the same name in B and C (now without a common base A). And in D you want to override each function but give different behavior to each. Depending whether you call the function with a B pointer or C pointer, you have the different behavior. Multiple Inheritance Part III by Herb Sutter describes a good way of doing that. It might help you decide on your design.

Janniejanos answered 5/3, 2009 at 20:48 Comment(3)
I think that's not what shoosh wants to do. He stated "What I'm trying to do is to have D somehow enumerate all of the instances of fn() it has in its ancestry.". He doesn't want to override fn() in class D.Alpenstock
that is the reason i stated it is not possible. if both C and B override A::fn, then he cannot define D without overriding fn in DJanniejanos
What did you mean by "Drop"?Accountable
A
5

First question, yes, B and C can define fn() as a virtual function. Second, D can of course access B::fn() and C::fn() by using the scope operator :: Third question: D must at least know B and C, since you have to define them on the inheritance list. You can use templates to let the types of B and C open:

class A
{
public:
   virtual ~A() {}
   virtual void fn() = 0;
};

class B: public A
{
public:
   virtual ~B() {}
   virtual void fn(){ std::cout << "B::fn()" << std::endl; }
};

class C: public A
{
public:
   virtual ~C() {}
   virtual void fn(){ std::cout << "C::fn()" << std::endl; }
};

template <typename TypeB, typename TypeC>
class D: public TypeB, public TypeC
{
public:
   void Do()
   {
      static_cast<TypeB*>(this)->fn();
      static_cast<TypeC*>(this)->fn();
   }
};

typedef D<B, C> DInst;

DInst d;
d.Do();

About the wish to automatically enumerate all fn() functions of all classes that D inherits from: I'm not sure if that is possible without resorting to MPL. At least you can extend my example above with versions that deal with 3 and more template parameters, but I guess there is an upper (internal compiler-)limit of number of class template parameters.

Alpenstock answered 5/3, 2009 at 20:8 Comment(30)
I was going to post the same. There is a slight mistake as D must inherit from both B and C and that is not in the code above. Another syntax (simpler than the cast) would be: TypeB::fn() and TypeA::fn() that seem more natural.Along
I'm not sure if a TypeB::fn() call does the right thing with respect to the virtual function call. With the static cast you're sure you have an object of type B. I guess I have to try that out. Thanks for the note about the correction!Alpenstock
This is probably the closest solution to what I need. Unfortunatly in my case the number of classes in the inheritance (B,C) is also variable. guess this will have to wait for C++0x's variable template arguments.Propagation
does that really work? i believe it is not valid because now both C and B override A's fn. the compiler does not know what version is the "final overrider" thus emitting an error message.Janniejanos
litb, where do you expect an error message? For the compiler there's no ambiguity, since I'm forcing it to TypeB or TypeC. A plain fn() call would error, though.Alpenstock
imagine you have this code: A *a = some_d_object; a->fn(); what version of fn will be called?Janniejanos
it forbids that situation already at the time you define D, because in objects of it, there is no final overrider of fn that overrides A::fn the most dominant.Janniejanos
@litb is correct. fn() is virtual and can (1) be overridden by D, and (2) if it is not overridden by D, it is overridden by B or C (but not both). Thus both your calls in Do would do the same thing, and the same thing as casting to A or D.Marva
-1. @vividos: Assuming you change D to override fn() (needed to get the code to compile), static_cast<TypeB*>(this)->fn() will still call the most-derived version (i.e. D's version) of fn(), because virtual functions ignore the static type. You need to say TypeB::fn() to do what you want.Hardened
j_random_hacker: The original question poster doesn't want to override fn() in D, he wants to enumerate and call all fn() functions of the classes in his inheritance list. And you don't have to override fn() in D to get it compile. Try it out.Alpenstock
-1. Litb is right: not overriding fn() in D generates a compiler error (I just tried). Your code doesn't compile due to ambiguous inheritance of 'void A::fn()'.Primula
I completed the code with classes A, B and C to show what I mean. This code compiles using Visual C++ 2005 and outputs "B::fn()" and "C::fn()" correctly. With what compiler did you try it?Alpenstock
@vividos: Interesting. According to section 10.3.3 of the C++ standard, each virtual function in a class must have a unique final overrider to be "well-formed" -- meaning a compiler must emit an error message if that's not the case. But every compiler I tried (MSVC++9, MinGW, g++ 4.1.2 on Linux)...Hardened
... did allow your code to compile and run without problems. (I even tried Comeau C++, which is one of the stricter ones, and it worked too!) Compiler errors were produced only if an attempt was made to actually call the ambiguous function fn(). So technically speaking, all 4 compilers have bugs...Hardened
@Luc: Which compiler are you using that actually correctly diagnoses this as an error?Hardened
@vividos: Although I stand by my position of "the standard is always Right, compilers be damned!" I've decided to drop my -1, since if 3 separate implementations can get it wrong it's a bit unfair to expect us peasants to get it right... :)Hardened
The reason for this is maybe that the derived classes are template parameters. The code from litb gives a compiler error, too.Alpenstock
@vividos: Yes I thought that was it too -- but I tried it with a regular non-template class and everything still compiled and worked! :/Hardened
@Hardened There is no virtual base here.Bambino
@JohannesSchaub-litb "now both C and B override A's fn." Did you read a virtual keyword that actually isn't there? ;)Bambino
@curiousguy: I don't understand -- do you think something I've said assumes the existence of a virtual base class somewhere? If so could you say what it is that I got wrong please? Thanks! (Sidenote: I said "section 10.3.3 of the C++ standard" but it seems I meant "section 10.3.2".)Hardened
@Hardened "something I've said assumes the existence of a virtual base class somewhere?" That: "each virtual function in a class must have a unique final overrider to be "well-formed"" then "So technically speaking, all 4 compilers have bugs..."Bambino
@Hardened "what it is that I got wrong" You assumed that there is one base class A in D (try A& = d;), so there is one member function A::fn() (try d.A::fn();). There are two bases A (instrument the c|dtors to see their addresses), so two members A::fn() in d: d.B::A::fn() and d.C::A::fn(). Each of them has indeed a final overrider.Bambino
You must not confuse the real diamond shaped virtual inheritance with simple repeated normal inheritance: normal inheritance, as a member variable (exclusive ownership), means that in every derived object D there is a base object Bi (for every direct base Bi of D), and D initialise each Bi just like a data member; virtual inheritance, as another object referred to with a const shared_ptr<VBi> (for every virtual base VBi of D). The added flexibility of the indirection comes with a lack of control.Bambino
@curiousguy: After spending way too much time looking into this, I've found a single line of code, ap->f();, in an example in 10.3/9 that confirms that your interpretation is indeed the one that the authors of the standard intended to be correct. What tripped me up is the wording of 10.3/2: "... in any well-formed class, for each virtual function declared in that class or any of its direct or indirect base classes there is a unique final overrider that overrides that function and every other overrider of that function."...Hardened
... For this statement to be consistent with the example in 10.3/9, it should say "base class subobjects" instead of "base classes". As it's currently written, I maintain that it mandates the behaviour that myself and others have argued for so far -- that the compiler should flag the instantiation of D<B, C> as an error. I accept it makes sense to let distinct base class subobjects have different final overriders, and that this would mean that vividos's code compiles and runs fine; in other words I think we should imagine that the standard is really worded differently to allow this.Hardened
@Hardened From N3242: "In a derived class, if a virtual member function of a base class subobject has more than one final overrider the program is ill-formed."Bambino
@curiousguy: Looks like they fixed it :)Hardened
@Hardened You have to understand that implementers know C++ intent very well, they have a good C++ culture, are part of the committee discussions, etc.; so for most parts they do not need to check what the standard says on matters that have no changed since the times of cfront. It means that for some stuff, the implementers do not follow the standard, the standard follow the implementers!Bambino
I don*t know, if anybody noticed: In the sample code, B and C inherit normally (non-virtual) from A, so the final D has two instances of A, and B::fn() and C::fn() work on different instances. An assignment from D& to A& will be ambigous, though, and thus ill-formed.Storied
H
2

You cannot enumerate the definitions of fn() in the ancestry. C++ lacks reflection. The only way I can imagine is a giant loop testing the typeid's of all possible ancestors. And it hurts to imagine that.

Hour answered 5/3, 2009 at 20:9 Comment(0)
G
2

You might want to look at Loki TypeLists if you really need to be able to track ancestry and enumerate through types. I'm not sure if what you are asking for is really possible without a bunch of work. Make sure that you aren't over-engineering here.

On a slightly different note, if you are going to use MI in this manner (i.e., the dreaded diamond), then you should be very explicit about which virtual member you want. I can't think of a good case where you want to choose the semantics of B::fn() over C::fn() without explicitly making a decision when writing D. You will probably pick one over the other (or even both) based on what the individual method does. Once you have made a decision, the requirement is that inherited changes do not change the expectations or semantic interface.

If you are really worried about swapping in a new class, say E in place of say B where E does not descend from B but offers the same interface, then you should really use the template approach though I'm not sure why there is a static_cast<> in there...

struct A {
    virtual ~A() {}
    virtual void f() = 0;
};
struct B: A {
    virtual void f() { std::cout << "B::f()" << std::endl; }
};
struct C: A {
    virtual void f() { std::cout << "C::f()" << std::endl; }
};

template <typename Base1, typename Base2>
struct D: Base1, Base2 {
    void g() { Base1::f(); Base2::f(); }
};

int main() {
    D<B,C> d1;
    D<C,B> d2;
    d1.g();
    d2.g();
    return 0;
}

// Outputs:
//   B::f()
//   C::f()
//   C::f()
//   B::f()

works fine and seems a little easier to look at.

Gers answered 5/3, 2009 at 21:1 Comment(0)
R
0

Vividos has already answered the main part of the post. Even if I would use the scope operator instead of the more cumbersome static_cast<> + dereference operator.

Depending on the task at hand, maybe you can change the inheritance relationship from D to B and C for a less coupling composition (plus possibly inheritance from A). This is assuming that you don't need D to be used polimorphically as either B or C, and that you don't really require B and C sharing the same base instance.

If you opt for composition, you can receive the B and C as arguments to your constructor as references/pointers of type A, making D completely unaware of the types B and C. At that point, you can use a container to hold as many A derived objects. Your own implementation of fn() (if you so decide) or any other method.

Rhizogenic answered 5/3, 2009 at 20:48 Comment(0)
J
-1

There are already several questions that deal with this. Seems like we're running out of questions to ask. Maybe the search box should be bigger than the Ask Question button.

See

Jovial answered 5/3, 2009 at 20:6 Comment(2)
I couldn't find a question that answers this specific issue. can you?Propagation
It's not because it's about multiple inheritance that you can guess it was already addressed in other posts. He's asked 'What I'm trying to do is to have D somehow enumerate all of the instances of fn() it has in its ancestry. Is this possible in some other means that virtual functions?'. Though I think it was a somewhat naive question, none of the questions you've linked here talk about such a thing. I think he was pretty specific and unique in his questioning. -1.Sharondasharos

© 2022 - 2024 — McMap. All rights reserved.