Изменены правила для защищенных конструкторов в С++ 17?
У меня есть этот тестовый пример:
struct A{ protected: A(){} };
struct B: A{};
struct C: A{ C(){} };
struct D: A{ D() = default; };
int main(){
(void)B{};
(void)C{};
(void)D{};
}
Оба gcc и clang компилируют его в режиме С++ 11 и С++ 14. Оба не работают в режиме С++ 17:
$ 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, скомпилированный из главного ветки 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.
Является ли это изменение поведения частью С++ 17 или это ошибка в обоих компиляторах?
Ответы
Ответ 1
Определение aggregate изменилось с С++ 17.
До С++ 17
нет базовых классов
Так как С++ 17
no virtual, private, or protected (since C++17)
базовые классы
Это означает, что для B
и D
они не являются агрегатными типами до С++ 17, затем для B{}
и D{}
, инициализация значений будет выполнена, тогда будет вызываться дефолтный конструктор по умолчанию; что прекрасно, потому что конструктор protected
базового класса может быть вызван конструктором производного класса.
Так как С++ 17, B
и D
становятся агрегированными типами (потому что они имеют только базовый класс public
и отмечают, что для класса D
явно установленный по умолчанию конструктор по умолчанию разрешен для типа агрегата, поскольку С++ 11), то для B{}
и D{}
будет выполняться агрегатная инициализация,
Каждый элемент массива direct public base, (since C++17)
или нестатический член класса в порядке индекса/внешнего вида массива в определении класса инициализируется копией из соответствующего предложения списка инициализаторов.
Если количество предложений инициализатора меньше количества членов and bases (since C++17)
или список инициализаторов полностью пуст, остальные члены and bases (since C++17)
инициализируются by their default initializers, if provided in the class definition, and otherwise (since C++14)
пустыми списками в соответствии с обычными правилами инициализации списка (который выполняет инициализацию значений для типов неклассов и неагрегатных классов с конструкторами по умолчанию и агрегатной инициализацией для агрегатов). Если членом ссылочного типа является один из этих оставшихся членов, программа плохо сформирована.
Это означает, что подобъект базового класса будет инициализирован инициализацией значения, конструктор B
и D
обходит; но конструктор по умолчанию A
равен protected
, тогда код выходит из строя. (Обратите внимание, что A
не является агрегированным типом, потому что он имеет предоставленный пользователем конструктор.)
BTW: C
(с предоставленным пользователем конструктором) не является агрегированным типом до и после С++ 17, поэтому он отлично подходит для обоих случаев.
Ответ 2
В С++ 17 правила об агрегатах изменились.
Например, вы можете сделать это сейчас на С++ 17:
struct A { int a; };
struct B { B(int){} };
struct C : A {};
struct D : B {};
int main() {
(void) C{2};
(void) D{1};
}
Обратите внимание, что мы не наследуем конструктор. В С++ 17 C
и D
теперь являются агрегатами, даже если они имеют базовые классы.
С {}
, инициализация агрегата вступает в силу и не отправляет никаких параметров, будет интерпретироваться так же, как вызов родительского конструктора по умолчанию извне.
Например, инициализацию агрегата можно отключить, изменив класс D
на это:
struct B { protected: B(){} };
struct D : B {
int b;
private:
int c;
};
int main() {
(void) D{}; // works!
}
Это связано с тем, что инициализация агрегата не применяется при наличии членов с разными спецификаторами доступа.
Причина, по которой работает = default
, заключается в том, что она не является предоставленным пользователем конструктором. Больше информации на этот вопрос.