Following up on this question about multiple (virtual) inheritance, I'd like to inquire about a simple MWE that makes g++ 5.2.0 upset whereas clang++ 3.6.2 handles it just fine, with no complaints at all, even with -Wall and -Wextra set. So here's the MWE:
class Z {};
class A : virtual Z { protected: A() {} };
class B : virtual Z { protected: B() {} };
class C : A, B { public: C() : A{}, B{} {} };
int main() { C c{}; return 0; }
Unlike clang++, g++ complains like this:
gccodd.c++: In constructor ‘C::C()’:
gccodd.c++:2:34: error: ‘A::A()’ is protected
class A : virtual Z { protected: A() {} };
^
gccodd.c++:4:39: error: within this context
class C : A, B { public: C() : A{}, B{} {} };
^
gccodd.c++:3:34: error: ‘B::B()’ is protected
class B : virtual Z { protected: B() {} };
^
gccodd.c++:4:39: error: within this context
class C : A, B { public: C() : A{}, B{} {} };
^
Replacing the uniform initialization in C's constructor with the old form works fine though and both clang++ and g++ are happy with the following:
class C : A, B { public: C() : A(), B() {} };
This yields the two obvious options:
The code violates the standard in some way, making the outcome undefined (i.e., any outcome would be acceptable).
One of the two compilers has a bug related to uniform initialization and multiple + virtual inheritance.
If it were a matter of voting, (1) might win, because icpc 15.0.0 says the following:
gccodd.c++(4): error #453: protected function "A::A()" (declared at line 2) is not accessible through a "A" pointer or object
class C : public virtual A, public virtual B { public: C() : A{}, B{} {} };
^
gccodd.c++(4): error #453: protected function "B::B()" (declared at line 3) is not accessible through a "B" pointer or object
class C : public virtual A, public virtual B { public: C() : A{}, B{} {} };
^
So, is it (1) or (2)? And if it's the former case, then what's wrong with my MWE?
List-initialization of an object or reference of type T is defined as
follows: (3.1) — If T is a class type and the initializer list has a
single element of type cv U [..] (3.2) — Otherwise, if T is a
character array [..] (3.3) — Otherwise, if T is an aggregate,
aggregate initialization is performed (8.5.1). (3.4) — Otherwise, if
the initializer list has no elements and T is a class type with a
default constructor, the object is value-initialized.
A and B both have base classes and hence aren't aggregates. So the fourth bullet point applies. And thus we have the exact same effect as if we had used ():
An object whose initializer is an empty set of parentheses, i.e.,
(), shall be value-initialized.
Any compiler yielding different results with those initializers cannot be conforming.
§11.4, which handles access to protected members, does not mention anything related to the form of initialization. However, concerning initialization of bases in a mem-initializer in the constructor, §11.4 is defective at the moment as mentioned by CWG issue #1883.
Related
The following code compiles with Visual Studio 2019, but not with gcc and clang. Defining the variable b causes an error with the latter two compilers.
#include <iostream>
class base
{
public:
constexpr base(int i) : m_i(i) {}
constexpr int i() const { return m_i; }
protected:
~base() = default; // Forbid polymorphic deletion
private:
const int m_i;
};
class derived final : public base
{
public:
constexpr derived() : base(42) {}
};
// Defining b compiles with Visual Studio 2019,
// but does not with gcc and clang.
// gcc 11.2: error: 'base::~base()' is protected within this context
// clang 13.0: error: variable of type 'const base' has protected destructor
constexpr base b(41);
// Defining d compiles with all 3 compilers.
constexpr derived d;
int main()
{
std::cout << b.i() << std::endl;
std::cout << d.i() << std::endl;
}
Which of the compilers are right? Microsoft, which compiles the program, or gcc and clang, which do not?
And if gcc and clang are right, why does the error show up for b, but not for d?
Update:
FWIW, if I change base as below, Visual Studio 2019 does not compile the program either:
class base
{
public:
constexpr base(int i) : m_i(i) {}
constexpr int i() const { return m_i; }
protected:
//~base() = default; // Forbid polymorphic deletion
// This does not compile with Visual Studio 2019 either.
~base() {}
private:
const int m_i;
};
But as I understand, there shouldn't be a difference between the defaulted and the manually implemented destructor, so yes, a bug in the MS compiler it is then, I guess.
A destructor is a member-function, so it cannot be called in context where you would not be able to call other member functions.
The context of the call is the context of the construction of the object (see below), so in your case you cannot call ~base() outside of base (or a class derived from base), which is why you get the error with gcc and clang (which are correct).
[class.dtor#15] [...] In each case, the context of the invocation is the context of the construction of the object. [...]
The error shows up for b and not for d because the implicitly declared destructor of derived is public:
[class.dtor#2] If a class has no user-declared prospective destructor, a prospective destructor is implicitly declared as defaulted ([dcl.fct.def]).
An implicitly-declared prospective destructor is an inline public member of its class.
Making the destructor of base protected or private does not make the destructor of child classes protected or private by default, you simply get the implicitly defined destructor, which is public.
Consider:
class A
{
protected:
A(int) {}
void f(int) {}
public:
A() {}
};
class B : public A
{
public:
using A::A;
using A::f;
};
int main()
{
B().f(1); // ok
B(1); // error: 'A::A(int)' is protected within this context
}
Why can't an inherited protected constructor be made public, while an inherited protected member function can?
Unlike other members, the accessibility of the using-declaration that introduced the inherited constructor is ignored.
[namespace.udecl]/19,
(emphasis mine)
A synonym created by a using-declaration has the usual accessibility for a member-declaration. A using-declarator that names a constructor does not create a synonym; instead, the additional constructors are accessible if they would be accessible when used to construct an object of the corresponding base class, and the accessibility of the using-declaration is ignored.
Actually, the inherited constructor can be made public, but not just the way you wrote it. You can define your B class as follows:
class B : public A {
public:
B() {}
B(int x) : A(x) {} // instead of using A::A(int)
using A::f;
};
(see it on GodBolt)
Perhaps the standard committee thought that saying using A::A would be a bit ambiguous, since a constructor of the base class is not exactly the same thing as a constructor of the subclass.
Consider the following code:
class A {
private:
int a;
public:
A(int a) : a(a) { }
};
class B : public A {
private:
int b;
bool init() {
b = 0;
return true;
}
public:
// init() is a hack to initialize b before A()
// B() : b(0), A(b) {} yields -Wreorder
// B() : A((b = 0)) {} no warning (but this one doesn't work so well with non-pod (pointer) types)
B() : A(init() ? b : 0) {}
};
Now trying to compile this code with clang...
$ clang++ test.cpp -fsyntax-only
test.cpp:19:20: warning: field 'b' is uninitialized when used here [-Wuninitialized]
B() : A(init() ? b : 0) {}
^
1 warning generated.
GCC does not print any warnings, not even with -Wall -Wextra and -pedantic.
It's undefined behavior. According to [class.base.init]:
In a non-delegating constructor, initialization proceeds in the following order:
— First, and only for the constructor of the most derived class (1.8), virtual base classes ...
— Then, direct base classes are initialized in declaration order as they appear in the base-specifier-list
(regardless of the order of the mem-initializers).
— Then, non-static data members are initialized in the order they were declared in the class definition
(again regardless of the order of the mem-initializers).
b won't have been initialized by the time that the A base class is initialized. The assignment b = 0 is itself undefined behavior for the same reason - b hadn't been initialized yet when that is called. Its default constructor would still be called after A's constructor.
If you want ensure that b is initialized first, the typical approach is the base-from-member idiom:
struct B_member {
int b;
B_member() : b(0) { }
};
class B : public B_member, public A
{
public:
B() : A(b) // B_member gets initialized first, which initializes b
// then A gets initialized using 'b'. No UB here.
{ };
};
In either case, calling a member function before base classes are initialized invokes undefined behavior. §12.6.2/16:
Member functions (including virtual member functions, 10.3) can be
called for an object under construction. Similarly, an object under
construction can be the operand of the typeid operator (5.2.8) or of a
dynamic_cast (5.2.7). However, if these operations are performed in a
ctor-initializer (or in a function called directly or indirectly from a ctor-initializer) before all the mem-initializers for
base classes have completed, the result of the operation is
undefined. [ Example:
class A {
public:
A(int);
};
class B : public A {
int j;
public:
int f();
B() : A(f()), // undefined: calls member function
// but base A not yet initialized
j(f()) { } // well-defined: bases are all initialized
};
However, the access of and assignment to b itself is fine, since it has vacuous initialization and its lifetime starts as soon as storage is acquired for it (which happened long before the constructor call started). Hence
class B : public A {
private:
int b;
public:
B() : A(b=0) {}
};
is well-defined.
Im' trying to initialize my "cl" class with that :
class Base
{
Base(int x){}
private :
Base(){}
};
class Test
{
public:
Base cl(12);
};
But I get (with gcc) error: expected identifier before numeric constant
And I don't want to set the "Base" default constructor public
Any "workaround" ?
This is parsed as a function declaration, resulting in an error because the parameter should be a type:
Base cl(12);
You probably want a Base data member, initialized with the value 12:
Base cl{12}; // requires C++11
If you don't have C++11 support, then you can initialize cl in Test's constructor initialization list:
class Test
{
public:
Test() : cl(12) {}
Base cl;
};
Data-members cannot be initialized directly in class through a constructor call. Because of the Most Vexing Parse1, your c1 member is parsed as a function declaration. An incorrect declaration, albeit.
In C++03 and before, you would need to initialize your class through the initializer-list of the constructor:
class Test
{
public:
Test() : cl(21)
// ^^^^^^^^
{};
Base cl;
};
In C++11, this can be easily done through uniform-initialization:
class Test
{
public:
Base cl{21};
// ^^^^^^^
};
Footnote:
1: The most vexing parse is a specific form of syntactic ambiguity resolution in the C++ programming language.... ~ Wikipedia
When I try to compile the following code:
class A {
public:
A(int v) : virt(v) { }
int virt;
int getVirt(void) const { return virt; }
};
class B : private virtual A {
protected:
B(int v) : A(v) { }
using A::getVirt;
};
class C : public B, private virtual A {
protected:
C(int v) : A(v), B(v) { }
using A::getVirt;
};
class D : public C {
public:
D(void) : C(3) { }
using C::getVirt;
};
#include <iostream>
int main(int argc, char *argv[]) {
D d;
std::cout << "The number is: " << d.getVirt() << std::endl;
return 0;
}
I get an error about D not instantiating A; is that correct? If a virtual base is embedded in the hierarchy do all derived classes also need to derive from that base, virtually, so they can call the parametric constructor of the virtual base?
BTW, here are the errors produced by G++:
Main.cpp: In constructor ‘D::D()’:
Main.cpp:22:18: error: no matching function for call to ‘A::A()’
Main.cpp:22:18: note: candidates are:
Main.cpp:3:5: note: A::A(int)
Main.cpp:3:5: note: candidate expects 1 argument, 0 provided
Main.cpp:1:7: note: A::A(const A&)
Main.cpp:1:7: note: candidate expects 1 argument, 0 provided
That has nothing to do with access control (at least not primarily). Rather, you have to understand how virtual bases work: The virtual base subobject is initialized by the most derived class. Since you don't mention A in the constructor initializer list of D, the default constructor is tried, but doesn't exist.
To fix this, initalize A properly in D:
D() : A(3), C(3) { }
When you say A(3), name lookup is performed according to 12.6.2/2:
In a mem-initializer-id an initial unqualified identifier is looked up in the scope of the constructor’s class and, if not found in that scope, it is looked up in the scope containing the constructor’s definition.
As Drew Dorman rightly points out, you can force a direct path to the virtual base class by calling it ::A and thus obtaining the desired access.
As Kerrek SB mentions, you need to initialize A in the constructor for D.
However, you must also explicitly tell the compiler that you are not accessing A from its (privately) derived context by using the scope operator.
class D : public C {
public:
D(void) : ::A(3), C(3) { }
// ^^ Access this constructor from a global context
using C::getVirt;
};
This also means that your constructor must be public, as is already the case with your code.