Ошибка компиляции при использовании пустого конструктора инициализации списка в 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(),

Другие вопросы по тегам