views:

111

answers:

4

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 :)

A: 

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.

ybungalobill
@ybungalobill: I disagree with you: my argument is the following - if what you say is correct then it shouldn't be able to generate a default constructor either, which it somehow does. Other argument is that the A subobject is initialized/copied/assigned by C, and not by the B subobject. That's how virtual inheritance works, isn't it?
Armen Tsirunyan
@Armen: I agree with your argument. So I'm going to dive into the standard.
ybungalobill
@ybungalobill: Good luck with that... I tried myself but in vain. Hope you have more luck than me :)
Armen Tsirunyan
+1  A: 

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.

Steve Townsend
@Steve: Thanks. But first of all, removing virtual does NOT fix the problem, because the problem is not to make this thing compile - the problem is to understand what's going on. And second, the quote from the closed issue you mentioned does NOT imply that a class with a virtual base cannot be derived from, at least not clearly enough. I mean in the sentence "This is what what was intended" I can't see what 'this' refers to. But if you are right, and it cannot be derived from, then C x; should fail to comile as well, but it doesn't. So this is still a bug, and my question remains open.
Armen Tsirunyan
@Armen - I don't know, seems pretty categorical to me. 'In short, it looks like a class with a virtual private base can't be derived from. Rationale: This is what was intended.' The info also states that compilers vary in how they cope with this, so it's not surprising that Visual C++ is inconsistent as well.
Steve Townsend
@Steve: I do appreciate that without friends, it is obvious that a class with a virtual private base cannot be derived from. If we consider friends a special case, then special wording is needed in the standard, otherwise with default definitions of friend and virtual inheritance, I suppose that the above code was MEANT to compile by the standard. If not, this case should have been explicitly mentioned. An in ANY case, either ALL three lines in main should fail, or all succeed. Otherwise, I cannot call the phenomenon anything but a bug. Has anyone tried this code on other compilers btw?
Armen Tsirunyan
@Armen - agree that all should fail or all should succeed. Getting VS2010 C++ Express to try that version.
Steve Townsend
@Steve: the issue you're quoting is not about `private virtual` classes not supporting derivation. [EDIT: I bungled the explanation, giving a correct one now] It's about whether the most derived class needs access to the relevant constructor(s) and destructor of the virtual base. And yes, it does, and in the OP's code it does have that access.
Alf P. Steinbach
+2  A: 

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.

Alf P. Steinbach
+2  A: 

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.

aschepler
@aschepler: Bravo, great research. I mean it. You just forgot to write QED in the end :)
Armen Tsirunyan