Question

consider the following code:

class A
{
    friend class B;
    friend class C;
};

class B: virtual private A
{
};

class C: private B
{
};

int main()
{
    C x; //OK default constructor generated by compiler
    C y = x; //compiler error: copy-constructor unavailable in C
    y = x; //compiler error: assignment operator unavailable in C 
}

The MSVC9.0 (the C++ compiler of Visual Studio 2008) does generate the default constructor but is unable to generate copy and assignment operators for C although C is a friend of A. Is this the expected behavior or is this a Microsoft bug? I think the latter is the case, and if I am right, can anyone point to an article/forum/... where this issue is discussed or where microsoft has reacted to this bug. Thank you in advance.

P.S. Incidentally, if BOTH private inheritances are changed to protected, everything works

P.P.S. I need a proof, that the above code is legal OR illegal. It was indeed intended that a class with a virtual private base could not be derived from, as I understand. But they seem to have missed the friend part. So... here it goes, my first bounty :)

Was it helpful?

Solution

Your code compiles fine with Comeau Online, and also with MinGW g++ 4.4.1.

I'm mentioning that just an "authority argument".

From a standards POV access is orthogonal to virtual inheritance. The only problem with virtual inheritance is that it's the most derived class that initializes the virtually-derived-from class further up the inheritance chain (or "as if"). In your case the most derived class has the required access to do that, and so the code should compile.

MSVC also has some other problems with virtual inheritance.

So, yes,

  • the code is valid, and
  • it's an MSVC compiler bug.

FYI, that bug is still present in MSVC 10.0. I found a bug report about a similar bug, confirmed by Microsoft. However, with just some cursory googling I couldn't find this particular bug.

OTHER TIPS

The way I interpret the Standard, the sample code is well-formed. (And yes, the friend declarations make a big difference from the thing @Steve Townsend quoted.)

11.2p1: If a class is declared to be a base class for another class using the private access specifier, the public and protected members of the base class are accessible as private members of the derived class.

11.2p4: A member m is accessible when named in class N if

  • m as a member of N is public, or
  • m as a member of N is private, and the reference occurs in a member or friend of class N, or
  • m as a member of N is protected, and the reference occurs in a member or friend of class N, or in a member or friend of a class P derived from N, where m as a member of P is private or protected, or
  • there exists a base class B of N that is accessible at the point of reference, and m is accessible when named in class B.

11.4p1: A friend of a class is a function or class that is not a member of the class but is permitted to use the private and protected member names from the class.

There are no statements in Clause 11 (Member access control) which imply that a friend of a class ever has fewer access permissions than the class which befriended it. Note that "accessible" is only defined in the context of a specific class. Although we sometimes talk about a member or base class being "accessible" or "inaccessible" in general, it would be more accurate to talk about whether it is "accessible in all contexts" or "accessible in all classes" (as is the case when only public is used).

Now for the parts which describe checks on access control in automatically defined methods.

12.1p7: An implicitly-declared default constructor for a class is implicitly defined when it is used to create an object of its class type (1.8). The implicitly-defined default constructor performs the set of initializations of the class that would be performed by a user-written default constructor for that class with an empty mem-initializer-list (12.6.2) and an empty function body. If that user-written default constructor would be ill-formed, the program is ill-formed.

12.6.2p6: All sub-objects representing virtual base classes are initialized by the constructor of the most derived class (1.8). If the constructor of the most derived class does not specify a mem-initializer for a virtual base class V, then V's default constructor is called to initialize the virtual base class subobject. If V does not have an accessible default constructor, the initialization is ill-formed.

12.4p5: An implicitly-declared destructor is implicitly defined when it is used to destroy an object of its class type (3.7). A program is ill-formed if the class for which a destructor is implicitly defined has:

  • a non-static data member of class type (or array thereof) with an inaccessible destructor, or
  • a base class with an inaccessible destructor.

12.8p7: An implicitly-declared copy constructor is implicitly defined if it is used to initialize an object of its class type from a copy of an object of its class type or of a class type derived from its class type. [Note: the copy constructor is implicitly defined even if the implementation elided its use (12.2).] A program is ill-formed if the class for which a copy constructor is implicitly defined has:

  • a nonstatic data member of class type (or array thereof) with an inaccessible or ambiguous copy constructor, or
  • a base class with an inaccessible or ambiguous copy constructor.

12.8p12: A program is ill-formed if the class for which a copy assignment operator is implicitly defined has:

  • a nonstatic data member of const type, or
  • a nonstatic data member of reference type, or
  • a nonstatic data member of class type (or array thereof) with an inaccessible copy assignment operator, or
  • a base class with an inaccessible copy assignment operator.

All these requirements mentioning "inaccessible" or "accessible" must be interpreted in the context of some class, and the only class that makes sense is the one for which a member function is implicitly defined.

In the original example, class A implicitly has public default constructor, destructor, copy constructor, and copy assignment operator. By 11.2p4, since class C is a friend of class A, all those members are accessible when named in class C. Therefore, access checks on those members of class A do not cause the implicit definitions of class C's default constructor, destructor, copy constructor, or copy assignment operator to be ill-formed.

Classes with virtual private base should not be derived from, per this at the C++ SWG. The compiler is doing the right thing (up to a point). The issue is not with the visibility of A from C, it's that C should not be allowed to be instantiated at all, implying that the bug is in the first (default) construction rather than the other lines.

  1. Can a class with a private virtual base class be derived from?

Section: 11.2 [class.access.base]
Status: NAD Submitter: Jason Merrill Date: unknown

class Foo { public: Foo() {}  ~Foo() {} };
class A : virtual private Foo { public: A() {}  ~A() {} };
class Bar : public A { public: Bar() {}  ~Bar() {} }; 

~Bar() calls ~Foo(), which is ill-formed due to access violation, right? (Bar's constructor has the same problem since it needs to call Foo's constructor.) There seems to be some disagreement among compilers. Sun, IBM and g++ reject the testcase, EDG and HP accept it. Perhaps this case should be clarified by a note in the draft. In short, it looks like a class with a virtual private base can't be derived from.

Rationale: This is what was intended.

btw the Visual C++ v10 compiler behaviour is the same as noted in the question. Removing virtual from the inheritance of A in B fixes the problem.

I think that it's not a bug. Just C has to be a friend of B to be able to copy it's virtual base class A.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top