Ошибка компиляции при использовании пустого конструктора инициализации списка в C++17
Я столкнулся со странной проблемой при попытке перейти на C++17. Проблема в том, что кое-что (и я не уверен, что) изменилось в C++ 17, что заставило инициализацию списка работать по-другому в случае конструктора по умолчанию. Я пытался найти https://en.cppreference.com/w/cpp/language/list_initialization для получения дополнительной информации, но я не нашел ничего, что выглядит уместным.
Кто-нибудь знает причину кода ниже компилируется в C++14, но не в C++ 17 при вызове B{}
вместо B()
?
(Я попробовал это и в gcc 8.2 и 7.3 и в icc 19)
struct A{
protected:
A() {}
};
struct B : public A {};
B f(){
return B(); //compilation OK
//return B{}; //compilation error
}
3 ответа
В C++14 определение агрегата было:
Агрегат - это массив или класс (Clause [class]) без предоставленных пользователем конструкторов ([class.ctor]), без закрытых или защищенных нестатических элементов данных (Clause [class.access]), без базовых классов (Предложение [class.derived]), и нет виртуальных функций ([class.virtual]).
Следовательно, B
это не совокупность. В следствии B{}
это конечно не агрегатная инициализация, а B{}
а также B()
в конечном итоге означает то же самое. Они оба просто вызывают B
конструктор по умолчанию.
Однако в C++17 определение агрегата было изменено на:
Агрегат - это массив или класс с
- нет пользовательских, явных или унаследованных конструкторов ([class.ctor]),
- нет частных или защищенных нестатических членов данных (пункт [class.access]),
- нет виртуальных функций, и
- нет виртуальных, частных или защищенных базовых классов ([class.mi]).
[Примечание: Агрегированная инициализация не позволяет получить доступ к защищенным и закрытым членам или конструкторам базового класса. - конец примечания]
Ограничение больше не для каких-либо базовых классов, а только для виртуальных / частных / защищенных. Но B
имеет публичный базовый класс. Теперь это совокупность! И агрегатная инициализация C++17 позволяет инициализировать подобъекты базового класса.
Особенно, B{}
это агрегатная инициализация, где мы просто не предоставляем инициализатор для любого подобъекта. Но первый (и единственный) подобъект A
, который мы пытаемся инициализировать из {}
(во время инициализации агрегата любой подобъект без явного инициализатора инициализируется {}
), что мы не можем сделать, потому что A
Конструктор защищен, и мы не друзья (см. также цитату).
Обратите внимание, что для интереса в C++20 определение агрегата снова изменится.
Из моего понимания https://en.cppreference.com/w/cpp/language/value_initialization
B{}
выполняет агрегатную инициализацию,
а с C++17:
Эффекты инициализации агрегата:
- Каждая прямая общедоступная база (начиная с C++17) [..] инициализируется копией из соответствующего предложения списка инициализаторов.
и в нашем случае:
Если количество предложений инициализатора меньше количества членов и баз (начиная с C++ 17) или список инициализаторов полностью пуст, остальные члены и базы (начиная с C++ 17) инициализируются инициализаторами по умолчанию, если они предоставляются в определении класса, и в противном случае (начиная с C++14) пустыми списками, в соответствии с обычными правилами инициализации списков (которые выполняют инициализацию значений для не-классов типов и неагрегированных классов с конструкторами по умолчанию, и агрегированной инициализации для агрегатов). Если член ссылочного типа является одним из этих оставшихся членов, программа является некорректной.
Так B{/*constructor of A*/}
нужно построить базовый класс А, который защищен...
Окончательный вариант C++17 n4659 имеет раздел совместимости, в котором содержатся изменения по сравнению с предыдущими версиями.
C.4.4 Пункт 11: деклараторы [diff.cpp14.decl]
11.6.1
Изменение: определение агрегата расширено для применения к пользовательским типам с базовыми классами.
Обоснование: повысить удобство инициализации агрегата.
Влияние на исходную функцию: действующий код C++ 2014 может не скомпилироваться или привести к другим результатам в этом международном стандарте; инициализация из пустого списка инициализаторов выполнит агрегатную инициализацию вместо вызова конструктора по умолчанию для затронутых типов:struct derived; struct base { friend struct derived; private: base(); }; struct derived : base {}; derived d1{}; // Error. The code was well-formed before. derived d2; // still OK
Я скомпилировал приведенный выше пример кода с -std=c++14
и он скомпилирован, но не скомпилирован с -std=c++17
,
Я считаю, что это может быть причиной того, что код в OP не работает с B{}
но удается с B()
,