'Inaccessible direct base' caused by multiple inheritance
Asked Answered
C

2

26

Spoiler alert: Maybe a stupid question. :)

#include <iostream>

using namespace std;

class Base
{
    public:
        virtual void YourMethod(int) const = 0;
};

class Intermediate : private Base
{
    public:
        virtual void YourMethod(int i) const
        {
            cout << "Calling from Intermediate" << i << "\n";
        }
};

class Derived : private Intermediate, public Base
{
    public:
        void YourMethod(int i) const
        {
            cout << "Calling from Derived : " << i << "\n";
        }
};

int main()
{
}

Can someone Explain to me why this throws the compiler warning:

main.cpp:21: warning: direct base ‘Base’ inaccessible in ‘Derived’ due to ambiguity

Now, I understand that there is no way this code will work. I want to know why. Base is private to Intermediate so it should not be visible to Derived through Intermediate. So where does the ambiguity come from? In constructor?

Cowitch answered 7/11, 2010 at 15:40 Comment(5)
Its not a compiler error its just a warning.Romie
I would imagine that calling Derived->YourMethod(5) should be fine... after all, in both Base and Intermediate YuorMethod is virtual, so why can't you define your own implimentation. You are not calling the base functions any way, so the public private tihng should not matter?Wyon
Interesting in VS2010 I get this compiler warning instead... warning C4584: 'Derived' : base-class 'Base' is already a base-class of 'Intermediate'Lieutenancy
@Kyle C : yes, my bad. I saw it as an error.Cowitch
One common reason I found for why this scenario occurs is by having pure-virtual base classes that act like interfaces. Secondly by re-stating the inheritance of 'Base' in a 3rd/4th generation derived class you reinforce that derived class must follow a specific pattern gaining code clarity. I recommend adding 'virtual' the inheritance of an Interface type class.Rascality
H
35

This has nothing to do with overriding functions. It has to do with conversions. It really doesn't have to do with accessibility (i.e "private" or such) directly either. Here is a simpler example

struct A { int a; };
struct B : A { };
struct C : B, A { }; // direct A can't be referred to!

You can refer to the indirect A object by first converting to B and then to A:

B *b = &somec;
A *a = b;

You cannot do such with the direct A object. If you try to directly convert to A, it will have two possibilities. It follows that it is impossible to refer to the non-static data members of the direct A object given a Derived object.

Notice that accessibility is orthogonal to visibility. Something can be accessible even tho it's not visible (for example by refering to it by a qualified name), and something can be visible even though it's not accessible. Even if all the above derivations would be declared private, the problem would still show up: Access is checked last - it won't influence name lookup or conversion rules.

Also, anyone can cast to an unambiguous private base class with defined behavior (the C++ Standard makes an exception for this) using a C-style cast, even if normally access wouldn't be granted to do so. And then there are still friends and the class itself that could freely convert.

Heterogenetic answered 7/11, 2010 at 15:52 Comment(4)
How can referring to something by a qualified name be an example for something being accessible even if it is not visible?Cowitch
@Cowitch class A { int a; void f() { int a; /* outer a is not visible */ } }; but you can refer to it by A::a. After the local a in f, the class member was accessible (because it's the same class), but its name was hidden by the local a.Heterogenetic
Does your analogy hold down inheritance trees? If I privately inherit B from A and publicly inherit C from B, then A should not be visible to C (protected would make it possible). Therefore, if I inherit C from A also, C should ideally only see its A not Bs A. But, I agree this is not the case.Cowitch
@Cowitch technically that might make sense (I think in Java, private member names aren't inherited, contrary to C++), but the C++ rules aren't such: Accessibility doesn't matter for anything - it's only checked once everything else is established. I don't know the rationale of this - maybe it's done for simplicity reasons (name lookup for friends could potentially be entirely different than for non-friends otherwise).Heterogenetic
S
11

Johannes' answer covers the basic facts. But there's a little more to it. So, consider

struct Base
{
    Base( int ) {}
    void foo() const {}
};

struct Intermediate: Base
{
    Intermediate( int x )
        : Base( x )
    {}
};

struct Derived: Intermediate, Base
{
    Derived( int x )
        : Intermediate( x )
        , Base( x )         // OK
    {}
};

int main()
{
    Derived o( 667 );
    o.foo();                // !Oops, ambiguous.
    o.Base::foo();          // !Oops, still ambiguous.
}

When I compile I get, as by now (after Johannes' answer) you'll expect,

C:\test> gnuc x.cpp
x.cpp:15: warning: direct base 'Base' inaccessible in 'Derived' due to ambiguity
x.cpp: In function 'int main()':
x.cpp:25: error: request for member 'foo' is ambiguous
x.cpp:4: error: candidates are: void Base::foo() const
x.cpp:4: error:                 void Base::foo() const
x.cpp:26: error: 'Base' is an ambiguous base of 'Derived'

C:\test> msvc x.cpp
x.cpp
x.cpp(15) : warning C4584: 'Derived' : base-class 'Base' is already a base-class of 'Intermediate'
        x.cpp(2) : see declaration of 'Base'
        x.cpp(7) : see declaration of 'Intermediate'
x.cpp(25) : error C2385: ambiguous access of 'foo'
        could be the 'foo' in base 'Base'
        or could be the 'foo' in base 'Base'
x.cpp(25) : error C3861: 'foo': identifier not found

C:\test> _

How to resolve depends on whether it's all right with a single sub-object of class Base (as is the case when Base is a pure interface), or Intermediate really requires its own Base sub-object.

The latter case, two Base sub-objects, is probably not what you want, but if you want that then then one cure is to introduce yet another intermediate class, say, ResolvableBase.

Like:

struct Base
{
    Base( int ) {}
    void foo() const {}
};

struct Intermediate: Base
{
    Intermediate( int x )
        : Base( x )
    {}
};

struct ResolvableBase: Base
{
    ResolvableBase( int x ): Base( x ) {}
};

struct Derived: Intermediate, ResolvableBase
{
    Derived( int x )
        : Intermediate( x )
        , ResolvableBase( x )
    {}
};

int main()
{
    Derived o( 667 );
    o.ResolvableBase::foo();    // OK.
}

In the first case, where e.g. Base is an interface and only one Base sub-object is needed, you can use virtual inheritance.

Virtual inheritance generally adds some runtime overhead, and Visual C++ is not too fond of it.

But it lets you "inherit in" an implementation of an interface, like in Java and C#:

struct Base
{
    Base( int ) {}
    virtual void foo() const = 0;
};

struct Intermediate: virtual Base
{
    Intermediate( int x )
        : Base( x )
    {}
    void foo() const {}     // An implementation of Base::foo
};

struct Derived: virtual Base, Intermediate
{
    Derived( int x )
        : Base( x )
        , Intermediate( x )
    {}
};

int main()
{
    Derived o( 667 );
    o.foo();    // OK.
}

Subtlety: I changed the inheritance list order in order to avoid g++ sillywarnings about initialization order.

Annoyance: Visual C++ issues sillywarning C4250 about inheritance (of implementation) via dominance. It's like "warning: you're using a standard main function". Oh well, just turn it off.

Cheers & hth.,

Sokoto answered 7/11, 2010 at 17:2 Comment(3)
My problem is, rather than using Derived on its own, when you use it through a handle of type Intermediate (In my question) there should be no resolution problems because Intermediate derives privately from Base.Cowitch
@nakiya: assuming by "a handle" you mean "a reference", well I fail to see that in your question, but it's just not a problem then. The compiler may still warn about the Derived definition though. But why are you inheriting directly from Base in Derived? Cheers,Sokoto
By "a handle" I mean "a reference or a pointer". See this question (And my answer) as the answer to your second question : #4118038Cowitch

© 2022 - 2024 — McMap. All rights reserved.