Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Private base class and multiple inheritance

Tags:

c++

Consider :

struct A { int x;};
struct B : A {};
struct C : private A {};

Now, as expected, the code

struct D : C
{
    D () { C::x = 2; }
};

int main () { D d; }

does not compile:

test2.cc: In constructor ‘D::D()’:
test2.cc:1:16: error: ‘int A::x’ is inaccessible
test2.cc:7:12: error: within this context

Now, if I do

struct D : B, C
{
    D () { C::x = 2; }
};

int main () { D d; }

then the error disappear! Isn't A::x supposed to be inaccessible too? What is the explanation here?

I'm using gcc version 4.7.2 (GCC), linux x86_64, if this matters.

EDIT: It does not compile with Clang 3.2: clang 3.2

But it does with gcc 4.7.2: gcc 4.7.2

like image 602
Alexandre C. Avatar asked Mar 13 '13 23:03

Alexandre C.


People also ask

What happens when a base class is inherited in private mode?

Note: o When a base class is privately inherited by the derived class, public members of the base class becomes the private members of the derived class and therefore, the public members of the base class can only be accessed by the member functions of the derived class.

How many base classes are there in multiple inheritance?

Explanation: For the implementation of multiple inheritance, there must be at least 3 classes in a program. At least 2 base classes and one class to inherit those two classes. If lesser, it becomes single level inheritance.

Can private members of a base class are inherited?

Yes, Private members of base class are inherited in derived class..But objects of derived class have no access to use or modify it..

Does inheritance use multiple base classes?

You can derive a class from any number of base classes. Deriving a class from more than one direct base class is called multiple inheritance. The order of derivation is relevant only to determine the order of default initialization by constructors and cleanup by destructors.


2 Answers

This is most certainly a bug. There is no reason why inheriting from class B as well should change the accessibility of C's members.

Not even GCC 4.8.0 (beta) seems to have solved this problem. Clang 3.2 and ICC 13.0.1, on the other hand, correctly refuse to compile this code.

like image 175
Andy Prowl Avatar answered Oct 07 '22 15:10

Andy Prowl


The answer is clang is correct. However, the code could also fail as ambiguous according to the standard.

If you look at 11.2p5 it has a relevant note (yes, I know notes are non-normative):

[ Note: This class can be explicit, e.g., when a qualified-id is used, or implicit, e.g., when a class member access operator (5.2.5) is used (including cases where an implicit “this->” is added). If both a class member access operator and a qualified-id are used to name the member (as in p->T::m), the class naming the member is the class denoted by the nested-name-specifier of the qualified-id (that is, T). —end note ]

What this note means, is that if you add this-> to C::x = 2; then C is the class naming the member and gcc 4.7.2 correctly fails when this is the case.

Now the question is Who is the class naming the member for C::x? The naming class is specified by the same 11.2p5:

The access to a member is affected by the class in which the member is named. This naming class is the class in which the member name was looked up and found.

Now, name lookup for class members is specified in 10.2, and after reading all of it, I have concluded that x is the union of subobject sets as per:

Otherwise, the new S(f, C) is a lookup set with the shared set of declarations and the union of the subobject sets.

Which means that according to member lookup rules x can be either from B or A! This makes the code ill-formed as: Name lookup can result in an ambiguity, in which case the program is ill-formed. However, this ambiguity can be resolved as per 10.2p8:

Ambiguities can often be resolved by qualifying a name with its class name.

And from the Clang source, we can see that is what they chose to do:

// If the member was a qualified name and the qualified referred to a
// specific base subobject type, we'll cast to that intermediate type
// first and then to the object in which the member is declared. That allows
// one to resolve ambiguities in, e.g., a diamond-shaped hierarchy such as:
//
//   class Base { public: int x; };
//   class Derived1 : public Base { };
//   class Derived2 : public Base { };
//   class VeryDerived : public Derived1, public Derived2 { void f(); };
//   void VeryDerived::f() {
//     x = 17; // error: ambiguous base subobjects
//     Derived1::x = 17; // okay, pick the Base subobject of Derived1
//   }

However, note the can in the wording of the above quote: often can be resolved. This means that they do not necessarily have to be resolved. So, I think according to the standard the code should fail as ambiguous or as a private member access failure.

EDIT

There is some contention about the interpretation of can and as to whether a ambiguity occurs here. I found Defect report 39. Conflicting ambiguity rules talks about this issue.

like image 30
Jesse Good Avatar answered Oct 07 '22 15:10

Jesse Good