Virtual inheritance vs. non-default constructors - c++

This code is rejected by (at least) MSVC, ICC, and GCC:
class A {
public:
A( int ) { }
};
class B: virtual public A {
public:
//B(): A( -1 ) { } // uncomment to make it compilable
virtual void do_something() = 0;
};
class C: public B {
public:
C(): A( 1 ) { }
virtual void do_something() { }
};
int main() {
C c;
return 0;
}
on the basis of
error : no default constructor exists for class "A"
class B: virtual public A {
^
detected during implicit generation of "B::B()" at line 14
Questions:
If the code is indeed invalid, how exactly does this follow from
the standard? AFAICT, 10.4/2 and 1.8/4 taken together imply that B
cannot be a type of the most derived class, and therefore from
12.6.2/10 we have that B can never, ever call A's constructors.
(The section numbers are for C++11.)
If the code is valid, are compilers violating the standard by
requiring the presence of constructors they could not possibly call?
Note that not only they want to call A::A() from B::B(), but they
want to do it while compiling C::C() (double weird).
P.S. This was originally asked on the ICC forum, but posted here due to not being limited to this compiler (and no details forthcoming).

Clang shows the error as:
error: call to implicitly-deleted default constructor of 'B'
C(): A( 1 ) { }
^
12.1/5 says "A defaulted default constructor for class X is defined as deleted if [...] any [...] virtual base class [...] has class type M [...] and [...] M has no default constructor [...]."

I think you are trying to derive a "theorem" from the facts that can be found in the standard, and then you expect the standard to acknowledge the existence of that "theorem". The standard does not do that. It doesn't strive to find and incorporate all possible "theorems" that can be derived from the standard text.
Your "theorem" is perfectly valid (unless I'm missing something). You are right, since class B is abstract, this class can never be used as a most derived class. This immediately means that class B will never get a chance to construct its virtual base A. And that means that technically in B the compiler should not care about the availability, and/or accessibility of the appropriate constructors in A or in any other virtual bases.
But the standard simply does not make that connection and does not care to make it. It doesn't treat constructors of abstract classes in any special way. The requirements imposed on such constructors are the same as for non-abstract classes.
You can call try suggesting it as a possible improvement to the standard committee.

It looks like 12.6.2/4 prohibits this to me:
If a given nonstatic data member or base class is not named by a
mem-initializer-id (including the case where there is no
mem-initializer-list because the constructor has no ctor-initializer),
then
— If the entity is a nonstatic data member of (possibly
cv-qualified) class type (or array thereof) or a base class, and the
entity class is a non-POD class, the entity is default-initialized
(8.5)...
It looks to me like that regardless of the class being a virtual base, B's default constructor is still synthesized by the compiler, and such default constructor doesn't know how to construct its A base ("the entity is default-initialized (8.5)").

Related

Why doesn't a class having private constructor prevent inheriting from this class? How to control which classes can inherit from a certain base?

class B {
private:
friend class C;
B() = default;
};
class C : public B {};
class D : public B {};
int main() {
C {};
D {};
return 0;
}
I assumed that since only class C is a friend of B, and B's constructor is private, then only class C is valid and D is not allowed to instantiate B. But that's not how it works. Where am I wrong with my reasoning, and how to achieve this kind of control over which classes are allowed to subclass a certain base?
Update: as pointed out by others in the comments, the snippet above works as I initially expected under C++14, but not C++17. Changing the instantiation to C c; D d; in main() does work as expected in C++17 mode as well.
This is a new feature added to C++17. What is going on is C is now considered an aggregate. Since it is an aggregate, it doesn't need a constructor. If we look at [dcl.init.aggr]/1 we get that an aggregate is
An aggregate is an array or a class with
no user-provided, explicit, or inherited constructors ([class.ctor]),
no private or protected non-static data members (Clause [class.access]),
no virtual functions, and
no virtual, private, or protected base classes ([class.mi]).
[ Note: Aggregate initialization does not allow accessing protected and private base class' members or constructors.  — end note ]
And we check of all those bullet points. You don't have any constructors declared in C or D so there is bullet 1. You don't have any data members so the second bullet doesn't matter, and your base class is public so the third bullet is satisfied.
The change that happened between C++11/14 and C++17 that allows this is that aggregates can now have base classes. You can see the old wording here where it expressly stated that bases classes are not allowed.
We can confirm this by checking the trait std::is_aggregate_v like
int main()
{
std::cout << std::is_aggregate_v<C>;
}
which will print 1.
Do note that since C is a friend of B you can use
C c{};
C c1;
C c2 = C();
As valid ways to initialize a C. Since D is not a friend of B the only one that works is D d{}; as that is aggregate initialization. All of the other forms try to default initialize and that can't be done since D has a deleted default constructor.
From What is the default access of constructor in c++:
If there is no user-declared constructor for class X, a constructor having no parameters is implicitly declared as defaulted. An implicitly-declared default constructor is an inline public member of its class.
If the class definition does not explicitly declare a copy constructor, one is declared implicitly. [...] An implicitly-declared copy/move constructor is an inline public member of its class.
Constructors for classes C and D are generated internally by compiler.
BTW.: If you want to play with inheritance, please make sure you have virtual destructor defined.

"Middle classes" in diamond inheritance graph using non-default virtual base constructor: why is it not a compile error?

Consider a diamond inheritance graph (i.e., virtual base class). We know from previous
questions
that on construction the most derived class directly calls the default (0-arg) constructor of the (virtual) base.
But we also know from answers to the previous question (e.g., here
that if the "middle" classes in the diamond have constructors that are used by the most-derived class and those constructors "call" non-default constructors of their (virtual) base class (via the initialization list) then that is not respected … though the bodies of the "middle" classes' constructors are executed.
Why is that? I would have thought it should be a compile error. (Detected, of course, when the most-derived class is declared and the diamond is created.)
I'm looking for two things:
where in the standard is this specified?
does this kind of explicit-yet-ignored code happen anywhere else in the language?
Code sample of what I'm talking about follows its actual and expected outputs below:
B 0arg-ctor
Mf 0arg-ctor
Mt 0arg-ctor
useD
expected output:
ERROR: (line 19) struct `D` creates a diamond inheritance graph where an explicitly
written invocation of a virtual base class constructor is ignored (for base
classes `Mf`and `Mt` and ancestor virtual base class `B`
code:
#include <iostream>
using namespace std;
struct B {
B() noexcept { cout << "B 0arg-ctor" << endl; };
B(bool) noexcept { cout << "B 1arg-ctor" << endl; };
};
struct Mf : public virtual B
{
Mf() : B(false) { cout << "Mf 0arg-ctor" << endl; }
};
struct Mt : public virtual B
{
Mt() : B(true) { cout << "Mt 0arg-ctor" << endl; }
};
struct D : public Mf, public Mt { };
void useD(D) { cout << "useD" << endl; }
int main()
{
D d;
useD(d);
return 0;
}
The rules for initializing bases and members are specified in [class.base.init].
Specifically in p7:
A mem-initializer where the mem-initializer-id denotes a virtual base class is ignored during execution of a constructor of any class that is not the most derived class.
and its complement in p13:
First, and only for the constructor of the most derived class ([intro.object]), virtual base classes are initialized in the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes, where “left-to-right” is the order of appearance of the base classes in the derived class base-specifier-list.
Hence the initializers B(true) and B(false) are ignored when initializing Mf and Mt because they're not the most derived class, and the initialization of D leads with the initialization of B. No initializer for it is provided, so B() is used.
Making this fail to compile would be basically impossible? To start with, consider:
struct Mf : public virtual B { };
struct D : public Mf { };
That initializes B, but implicitly. Do you want that to be an error for Mf since its initialization would be ignored? I assume no - otherwise this language feature would be completely unusuable. Now, what about:
struct Mf : public virtual B { Mf() : B() { } };
struct D : public Mf { };
Is that an error? It basically means the same thing though. What if Mf had members that needed to be initialized and I, as matter of habit, just like listing the base classes?
struct Mf : public virtual B { Mf() : B(), i(42) { } int i; };
struct D : public Mf { };
Okay, you say, you only error if you actually provide arguments. Which is where a different misconception comes in:
We know from previous questions that on construction the most derived class directly calls the default (0-arg) constructor of the (virtual) base.
That's not true (and is not what those answers state). The most derived class initializes the virtual bases - but this initialization does not have to be default. We could've written:
struct D : public Mf, public Mt { D() : B(true) { } };
And really, there's not an interesting distinction between B() and B(true). Imagine the constructor were just B(bool = true), then does it matter whether or not the user provides the argument true? It would be strange if one were an error but not the other, right?
If you keep going down this rabbit hole, I think you'll find that making this an error would be either exceedingly narrow or exceedingly restrictive.
[class.mi]/7 - For an object of class AA, all virtual occurrences of base class B in the class lattice of AA correspond to a single B subobject within the object of type AA...
[class.base.init]/7 - ... A mem-initializer where the mem-initializer-id denotes a virtual base class is ignored during execution of a constructor of any class that is not the most derived class.
[intro.object]/6 - If a complete object, a data member, or an array element is of class type, its type is considered the most derived class, to distinguish it from the class type of any base class subobject; an object of a most derived class type or of a non-class type is called a most derived object.
Why is that?
Apart from the obvious; because the standard says so, one possible rationale is that since you only have one base class subobject it doesn't even make sense to allow middle bases to interact with the initialization of the virtual base. Otherwise, which middle base class would you expect to initialize the virtual base, Mt or Mf ?, because for B(false) and B(true) would mean two different way to initialize the same object.
Adding a new class to a codebase should not cause well-formed classes to suddenly become invalid. That would be a language disaster. If Derived initializes its virtual base Base, and it is correct code, then the existence of a further-derived class should have no impact on the validity of Derived. Your expectation would almost completely preclude inheritance from any class simply because it happens to use virtual inheritance somewhere, and make virtual inheritance unusable.
But for the citations you requested (from draft n4762):
10.9.2/13:
In a non-delegating constructor, initialization proceeds in the following order:
— First, and only for the constructor of the most derived class (6.6.2), virtual base classes are initialized in the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes, where “left-to-right” is the order of appearance of the base classes in the derived class base-specifier-list.
And the second part you asked about, the virtual base initializer in a non-most-derived class is described here, in 10.9.2/7:
A mem-initializer where the mem-initializer-id denotes a virtual base class is ignored during execution of a constructor of any class that is not the most derived class.
A virtual base is constructed by the most-derived class, but need not use a default constructor to do so, it can use any accessible constructor. Intermediate bases simply do not factor into the construction of a virtual base.

Why are C++ classes with virtual functions required to have a non-trivial copy constructor?

According to the C++ standard, a class that has virtual functions cannot have a trivial copy constructor:
A copy/move constructor for class X is trivial if it is not user-provided and if
— class X has no virtual functions (10.3) and no virtual base classes (10.1), and
— the constructor selected to copy/move each direct base class subobject is trivial, and
— for each non-static data member of X that is of class type (or array thereof), the constructor selected to copy/move that member is trivial;
otherwise the copy/move constructor is non-trivial.
Now, imagine a class hierarchy that satisfies all the mentioned conditions except the 'no virtual functions' condition:
struct I
{
virtual void f() = 0;
};
struct D final : I
{
void f() override
{
}
};
From an implementation's point of view those classes contain only a pointer to the virtual dispatch table. And the base class does not have any user-declared constructors, and the derived class is final, so the mentioned pointer can always have a constant value. Given all that, the copy constructor can be trivial, yet the standard explicitly forbids treating it as such.
On the other hand, the conditions of treating such classes as trivially destructible are met. The class does not declare a virtual destructor and uses implicitly-defined destructors.
Requiring triviality of destructors looks like mandating an optimization - implicitly defined destructors should not restore pointers to the virtual table in this case.
But such an optimization goes half-way in my opinion; classes with virtual functions still cannot be memcopied, even if they can be trivially destructed.
The questions:
Are there any reasons I did not think of from an implementation perspective why such classes should have non-trivial copy-constructors?
Are there any reasons the restrictions on triviality for copy-constructors cannot be relaxed in the standard?
Are there any reasons I did not think of from implementation perspective why such classes should have non-trivial copy-constructors?
There is quite obvious one: copy-constructor of I is not trivial. And it is not final, so there can be other derived classes. So it must be non-trivial and set virtual table pointer properly after memcpy, as there could be derived classes relying on it.
Are there any reasons the restrictions on triviality for copy-constructors cannot be relaxed in the standard?
1) Constructor triviality part was simply not revised with inclusion of final keyword.
2) People think that keywords like delete, final and overrride should help avoid most common errors, and clarify programmer intention, not change behavior of the program.
3) It complicates language:
A constructor is trivial, unless you have virtual function, then it is nontrivial, unless your class is final, then it is trivial again, unless something else, then it is not, unless...
4) Nobody though that it is worth writing formal paper for, proving usefulness of this addition to Committee and pushing this change into language.
The copies and moves of polymorphic classes cannot be trivial because it would break slicing copies and moves which copy a base type of an object's dynamic type. For example:
struct Base { virtual int f() { return 0; } };
struct D1 : Base { int f() override { return 1; } };
struct D2 : Base { int f() override { return 2; } };
int main() {
D1 d1;
D2 d2;
Base b = d1; // if this copy constructor were trivial, b would have D1's vtable
b.f(); // ...and this call would return 1 instead of 0.
b = d2; // Ditto: b would have D2's vtable
b.f(); // ...and this call would return 2 instead of 0.
}
Trivial constructors means that no additional effort is needed. So for copy c-tor/assigment operator it means simple memcpy. Virtual functions as you mentioned are creating vtable, which is table of pointers to functions. So if you'd try to copy memory of D object, you'll also copy its vtable. New object would have vtable with pointers pointing to old memory. This would crearly not an ideal situation.

Why derived constructor needs base destructor?

class A{
public:
A();
private:
~A();
};
class B:public A{
public:
B(){};
private:
~B();
};
int main()
{
return 0;
}
I've got a compile error like this:
test.cpp: In constructor 'B::B()':
test.cpp:5:4: error: 'A::~A()' is private
test.cpp:10:8: error: within this context
I know the derived constructor need to invoke the base destructor, hence I set A::A() as public.
However, why the compiler complains that it need public A::~A() either?
The closest specification I could find is here - 12.4.10 class.dtor:
... A program is ill-formed if an object of class type or array
thereof is declared and the destructor for the class is not accessible
at the point of the declaration.
class B is not able to access private: ~A(), but is implicitly declaring class A because it is a base class (it is almost the same as declaring first member variable - except empty base optimization).
I am not a language lawyer and not a native speaker, but the inaccessible base class destructor seems to be the problem and the above may explain why the error points to the constructor B() (the compiler may perform the checks when they are really needed, not before).
The destructor ~A() needs to be made at least protected or B friend of A.
The C++ standards committee core working group defect report 1424 (submitted by Daniel Krügler on 2011-12-07) says:
The current specification does not appear to say whether an implementation is permitted/required/forbidden to complain when a sub-object's destructor is inaccessible.
This is fixed in C++14 by the addition of the notion of a destructor being potentially invoked. The current draft standard section 12.6.2(10) says:
In a non-delegating constructor, the destructor for each direct or virtual base class and for each non-static data member of class type is potentially invoked (12.4).
[ Note: This provision ensures that destructors can be called for fully-constructed sub-objects in case an exception is thrown (15.2). —end note ]
And in 12.4(11):
A destructor is potentially invoked if it is invoked or as specified in 5.3.4 and 12.6.2. A program is ill-formed if a destructor that is potentially invoked is deleted or not accessible from the context of the invocation.
Declare constructor and destructor as public (base class constructor anddestructor may be protected, if you ensure that is called by the subclasses only). Subclasses when constructed have base class instance created as a cubobject, so either explicit or implicit call of constructor and destructor are done

MSVC9.0 bug or misunderstanding of virtual inheritance and friends?

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