Ошибка компиляции при использовании пустого конструктора инициализации списка в С++ 17
Я столкнулся со странной проблемой при попытке перейти на С++ 17. Проблема в том, что кое-что (и я не уверен, что) изменилось в С++ 17, что заставило инициализацию списка работать по-другому в случае конструктора по умолчанию. Я пытался найти https://en.cppreference.com/w/cpp/language/list_initialization для получения дополнительной информации, но я не нашел ничего, что выглядит уместным.
Кто-нибудь знает причину, по которой код ниже компилируется в С++ 14, но не в С++ 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
}
Ответы
Ответ 1
В С++ 14 определение агрегата было:
Агрегат - это массив или класс (Clause [class]) без предоставленных пользователем конструкторов ([class.ctor]), без закрытых или защищенных нестатических элементов данных (Clause [class.access]), без базовых классов ( Предложение [class.derived]), и нет виртуальных функций ([class.virtual]).
Следовательно, B
не является агрегатом. В результате B{}
, безусловно, не является агрегатной инициализацией, а B{}
и B()
означают одно и то же. Они оба просто вызывают конструктор B
умолчанию.
Однако в С++ 17 определение агрегата было изменено на:
Агрегат - это массив или класс с
- нет пользовательских, явных или унаследованных конструкторов ([class.ctor]),
- нет частных или защищенных нестатических членов данных (пункт [class.access]),
- нет виртуальных функций, и
- нет виртуальных, частных или защищенных базовых классов ([class.mi]).
[Примечание: Агрегированная инициализация не позволяет получить доступ к защищенным и закрытым членам или конструкторам базового класса. - конец примечания]
Ограничение больше не для каких-либо базовых классов, а только для виртуальных/частных/защищенных. Но у B
есть публичный базовый класс. Теперь это совокупность! И агрегатная инициализация С++ 17 позволяет инициализировать подобъекты базового класса.
В частности, B{}
является агрегатной инициализацией, где мы просто не предоставляем инициализатор для любого подобъекта. Но первым (и единственным) подобъектом является A
, который мы пытаемся инициализировать из {}
(во время инициализации агрегата любой подобъект без явного инициализатора инициализируется копией из {}
), чего мы не можем сделать, потому что A
Конструктор защищен, и мы не друг (см. также цитату).
Обратите внимание, что для интереса в С++ 20 определение агрегата снова изменится.
Ответ 2
Из моего понимания https://en.cppreference.com/w/cpp/language/value_initialization
B{}
выполняет агрегирование_инициализации,
а с С++ 17:
Эффекты инициализации агрегата:
- Каждая прямая общедоступная база (начиная с С++ 17) [..] инициализируется копией из соответствующего предложения списка инициализаторов.
и в нашем случае:
Если количество предложений инициализатора меньше количества членов и баз (начиная с С++ 17) или список инициализаторов полностью пуст, остальные члены и базы (начиная с С++ 17) инициализируются инициализаторами по умолчанию, если они предоставляются в определении класса, и в противном случае (начиная с С++ 14) пустыми списками, в соответствии с обычными правилами инициализации списков (которые выполняют инициализацию значений для не-классов типов и неагрегированных классов с конструкторами по умолчанию, и агрегированной инициализации для агрегатов). Если член ссылочного типа является одним из этих оставшихся членов, программа является некорректной.
Поэтому B{/*constructor of A*/}
должен построить базовый класс A, который защищен...
Ответ 3
Окончательный вариант 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()
.