Can you use explicit constructors with designated initializers? - c++

In the code below, is the initialization of member b legal?
class B {
public:
explicit B(int) {}
};
struct A {
B b;
};
class C {
public:
C() : a{.b{33}} {}
A a;
};
Compiling with the latest version of gcc gives this error (wandbox)
prog.cc: In constructor 'C::C()':
prog.cc:12:11: error: converting to 'B' from initializer list would use explicit constructor 'B::B(int)'
12 | C() : a{.b{33}} {}
| ^~~~~~~~~
But the latest version of clang compiles the code fine (wandbox)
Which compiler is correct?

This is a gcc bug (submitted 99566).
The rule, from [dcl.init.aggr]/4.2, is:
Otherwise, the element is copy-initialized from the corresponding initializer-clause or is initialized with the brace-or-equal-initializer of the corresponding designated-initializer-clause.
The element (b) should be initialized from the brace-or-equal-initializer ({33}). That's totally fine, that's not copy-initialization. gcc accepts B b{33}, the same kind of thing should happen here.

Related

seeking clarification on constexpr function

Consider the following code segment:
#include <iostream>
using std::cout;
using std::endl;
class A
{
public:
//constexpr A (){i = 0;}
constexpr A ():i(0){}
void show (void){cout << i << endl; return;}
private:
int i;
};
class B
{
public:
constexpr B(A a){this->a = a;}
//constexpr B(A a):a(a){}
void show (void){a.show(); return;}
private:
A a;
};
int main (void)
{
A a;
B b(a);
b.show();
return (0);
}
Inside the definition of class A, if the current constructor definition is replaced by the definition commented out:
//constexpr A (){i = 0;}
the following compilation error ensues (note that line numbers correspond to original code):
g++ -ggdb -std=c++17 -Wall -Werror=pedantic -Wextra -c code.cpp
code.cpp: In constructor ‘constexpr A::A()’:
code.cpp:8:30: error: member ‘A::i’ must be initialized by mem-initializer in ‘constexpr’ constructor
constexpr A (){i = 0;}
^
code.cpp:12:13: note: declared here
int i;
^
make: *** [makefile:20: code.o] Error 1
However, the code compiles perfectly with either definition for the constructor of class B (the current as well as the definition commented out in the source code reproduced.)
I have looked at the following pages with the objective of understanding what is going on here:
constexpr specifier (since C++11)
Constant expressions
I must admit that I am not able to figure out why the member initializer list is mandated in the case of the constructor for A, and not in the case of B.
Appreciate your thoughts.
A has a default constructor (which is constexpr by the way). The relevant requirements for a constexpr constructor that you are citing are as follows:
for the constructor of a class or struct, ... every non-variant
non-static data member must be initialized.
The only requirement that it is "initialized". Not "explicitly initialized". A default constructor will meet the initialization requirement. A has a default constructor. So your B's a class member gets initialized by its default constructor, meeting this requirement, so you don't have to explicitly initialize it in B's constructor's initialization list.
On the other hand, your garden variety int does not have a default constructor. For that reason, A's constexpr constructor must initialize it explicitly.

Changed rules for protected constructors in C++17?

I have this test case:
struct A{ protected: A(){} };
struct B: A{};
struct C: A{ C(){} };
struct D: A{ D() = default; };
int main(){
(void)B{};
(void)C{};
(void)D{};
}
Both gcc and clang compile it in C++11 and C++14 mode. Both fail in C++17 mode:
$ clang++ -std=c++17 main.cpp
main.cpp:7:10: error: base class 'A' has protected default constructor
(void)B{};
^
main.cpp:1:22: note: declared protected here
struct A{ protected: A(){} };
^
main.cpp:9:10: error: base class 'A' has protected default constructor
(void)D{};
^
main.cpp:1:22: note: declared protected here
struct A{ protected: A(){} };
^
2 errors generated.
$ clang++ --version
clang version 6.0.0 (http://llvm.org/git/clang.git 96c9689f478d292390b76efcea35d87cbad3f44d) (http://llvm.org/git/llvm.git 360f53a441902d19ce27d070ad028005bc323e61)
Target: x86_64-unknown-linux-gnu
Thread model: posix
(clang compiled from master Branch 2017-12-05.)
$ g++ -std=c++17 main.cpp
main.cpp: In function 'int main()':
main.cpp:7:10: error: 'A::A()' is protected within this context
(void)B{};
^
main.cpp:1:22: note: declared protected here
struct A{ protected: A(){} };
^
main.cpp:9:10: error: 'A::A()' is protected within this context
(void)D{};
^
main.cpp:1:22: note: declared protected here
struct A{ protected: A(){} };
^
$ g++ --version
g++ (GCC) 8.0.0 20171201 (experimental)
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Is this change of behavior part of C++17 or is it a bug in both compilers?
The definition of aggregate changed since C++17.
Before C++17
no base classes
Since C++17
no virtual, private, or protected (since C++17) base classes
That means, for B and D, they're not aggregate type before C++17, then for B{} and D{}, value-initialization will be performed, then the defaulted default constructor will be called; which is fine, because the protected constructor of base class could be called by derived class's constructor.
Since C++17, B and D become aggregate type (because they have only public base class, and note that for class D, the explicitly defaulted default constructor is allowed for aggregate type since C++11), then for B{} and D{}, aggregate-initialization will be performed,
Each direct public base, (since C++17) array element, or non-static class member, in order of array subscript/appearance in the class definition, is copy-initialized from the corresponding clause of the initializer list.
If the number of initializer clauses is less than the number of members and bases (since C++17) or initializer list is completely empty, the remaining members and bases (since C++17) are initialized by their default initializers, if provided in the class definition, and otherwise (since C++14) by empty lists, in accordance with the usual list-initialization rules (which performs value-initialization for non-class types and non-aggregate classes with default constructors, and aggregate initialization for aggregates). If a member of a reference type is one of these remaining members, the program is ill-formed.
That means the base class subobject will be value-initialized directly, the constructor of B and D are bypassed; but the default constructor of A is protected, then the code fails. (Note that A is not aggregate type because it has a user-provided constructor.)
BTW: C (with a user-provided constructor) is not an aggregate type before and after C++17, so it's fine for both cases.
In C++17, rules about aggregates has changed.
For example, you can do this in C++17 now:
struct A { int a; };
struct B { B(int){} };
struct C : A {};
struct D : B {};
int main() {
(void) C{2};
(void) D{1};
}
Note that we're not inheriting constructor. In C++17, C and D are now aggregates even if they have base classes.
With {}, aggregate initialization kicks in, and sending no parameters will be interpreted the same as calling the parent's default constructor from the outside.
For example, aggregate initialization can be disabled by changing the class D to this:
struct B { protected: B(){} };
struct D : B {
int b;
private:
int c;
};
int main() {
(void) D{}; // works!
}
This is because aggregate initialization don't apply when having members with different access specifiers.
The reason why with = default works is because it's not a user provided constructor. More information at this question.

Virtual inheritance and uniform initialization in C++

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.

Is this undefined behavior or a false positive warning?

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.

Uniform initialization doesn't work for initialization parent object in ctor

Suppose I have a struct:
struct A
{
int i;
};
I can init it as:
A a{1};
Now I have a derived class:
struct B : public A
{
B(int _i) : A{_i} {};
};
But I have a compilation error in B struct ctor:
error: no matching function for call to ‘A(<brace-enclosed initializer list>)’
Compiler is gcc-4.6 (SUSE Linux) 4.6.3
There is nothing wrong with your code; it is simply that your compiler doesn't support all of the required C++11 features.
Your constructor compiles fine with gcc 4.7.0.