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. Both do not work 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 the main branch on 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 a behavior change part of C ++ 17 or is it a bug in both compilers?

+53
c ++ protected constructor language-lawyer c ++ 1z
Dec 05 '17 at 14:33
source share
2 answers

The definition of aggregate has changed since C ++ 17.

Before C ++ 17

no base classes

Since C ++ 17

no virtual, private, or protected (since C++17) base classes

This means that for B and D they are not aggregate types until C ++ 17, then for B{} and D{} , the initialization of values will be performed, then the default default constructor will be called; which is fine, because the protected constructor of the base class can be called by the constructor of the derived class.

Since C ++ 17, B and D become aggregated types (because they have only the base class public and note that for class D explicit default constructor is enabled by default for the type of aggregate, since C ++ 11), then for B{} and D{} will be aggregated initialization ,

Each element of the direct public base, (since C++17) array direct public base, (since C++17) or a non-static member of the class in index / appearance order of the array in the class definition is initialized with a copy from the corresponding initializer list sentence.

If the number of initializer sentences is less than the number of and bases (since C++17) members or the list of initializers is completely empty, the remaining and bases (since C++17) members are initialized by their default initializers, if provided in the class definition, and otherwise (since C++14) empty lists in accordance with the usual rules for initializing a list (which performs value initialization for types of nonclasses and non-aggregate classes with default constructors and aggregate initialization for aggregates). If a member of the reference type is one of these remaining members, the program is poorly formed.

This means that the subobject of the base class will be initialized by initializing the value, the constructor B and D bypasses; but the default constructor A is protected , then the code fails. (Note that A not an 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 is great for both cases.

+49
Dec 05 '17 at 15:08
source share

In C ++ 17, the rules about aggregates have changed.

For example, you can do it now in C ++ 17:

 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 do not inherit the constructor. In C ++ 17, C and D are now aggregates, even if they have base classes.

With {} , the initialization of the aggregate takes effect and does not send any parameters, it will be interpreted in the same way as calling the default parent constructor from the outside.

For example, aggregate initialization can be disabled by changing class D to this:

 struct B { protected: B(){} }; struct D : B { int b; private: int c; }; int main() { (void) D{}; // works! } 

This is due to the fact that aggregation initialization is not applied when there are members with different access specifiers.

The reason that = default works is because it is not a user-provided constructor. More info on this .

+21
Dec 05 '17 at
source share



All Articles