Почему мой класс не конструируемый по умолчанию?

28

У меня есть эти классы:

#include <type_traits>

template <typename T>
class A {
public:
    static_assert(std::is_default_constructible_v<T>);

};

struct B {
   struct C {
      int i = 0;
   };

    A<C> a_m;
};

int main() {
    A<B::C> a;
}

При компиляции a_mне является конструктивным по умолчанию, но aесть.

При переходе Cна:

struct C {
      int i;
   };

все хорошо.

Протестировано с Clang 9.0.0.

никола
источник
3
GCC 8.3 - OK, GCC 9.1 / 9.2 - Fail.
Evg
2
С C() {}этим тоже работает.
Evg
3
Это пахнет глючит для меня. Нет сразу очевидного матча на Bugzilla.
Гонки
2
Интересно: static_assertin in Aтерпит неудачу, но если вы вместо этого по умолчанию создаете Tвнутреннюю часть A(например, помещаете туда член T t;), все работает нормально. Несоответствие между тем, что
типичная
2
@Nicolas Верно, но это из-за некоторых крайних случаев, ни один из которых не применим здесь (в частности, как говорится в том же предложении о cppreference, const int x;недопустимо без инициализатора, просто из- constза поведения и инициализации встроенных типов и некоторых история)
Гонки

Ответы:

9

Это запрещено как текстом стандарта, так и несколькими основными реализациями, как отмечено в комментариях, но по совершенно не связанным причинам.

Во-первых, причина «по книге»: точка инстанцирования A<C>, согласно стандарту, находится непосредственно перед определениемB , а точка инстанцирования std::is_default_constructible<C>находится непосредственно перед этим:

Для специализации шаблона класса, [...] если специализация создается неявно, потому что на нее ссылаются из другой специализации шаблона, если контекст, на который ссылается специализация, зависит от параметра шаблона, и если специализация не была создана предыдущая до создания экземпляра вмещающего шаблона точка создания экземпляра находится непосредственно перед точкой создания вмещающего шаблона. В противном случае точка создания такой специализации непосредственно предшествует объявлению или определению области пространства имен, относящемуся к специализации.

Поскольку Cв этот момент он явно неполон, поведение создания экземпляров std::is_default_constructible<C>не определено. Однако, смотрите основную проблему 287 , которая изменит это правило.


На самом деле это связано с NSDMI.

  • NSDMI являются странными, потому что они получают задержанный анализ - или, в стандартном языке, они представляют собой «полный классовый контекст».
  • Таким образом, = 0в принципе это может относиться к вещам, которые Bеще не объявлены, поэтому реализация не может попытаться проанализировать их до тех пор, пока они не закончатся B.
  • Завершение класса требует неявного объявления специальных функций-членов, в частности конструктора по умолчанию, так как Cконструктор не объявлен.
  • Части этого объявления (constexpr-ness, noexcept-ness) зависят от свойств NSDMI.
  • Таким образом, если компилятор не может проанализировать NSDMI, он не может завершить класс.
  • В результате, в момент, когда он создает экземпляр A<C>, он думает, что Cон неполон.

Вся эта область, имеющая дело с областями с анализом с задержкой, крайне не указана, что сопровождается расхождением в реализации. Это может занять некоторое время, прежде чем он будет очищен.

TC
источник
0

Неопределенное поведение это:

Если создание шаблона выше, прямо или косвенно, зависит от неполного типа, и это создание может привести к другому результату, если этот тип был гипотетически завершен, поведение не определено.

забвение
источник
7
Почему C неполный?
междневное
1
@interjay, Cзавершено, но Bнет. И B::Cкосвенно зависит от B.
Evg
1
@Evg Текст «Зависит прямо или косвенно» появляется только на cppreference.com. Стандарт просто говорит, что тип Т должен быть завершенным.
междневное
2
@interjay Я написал большую часть этой формулировки. Мы пытаемся сказать, что 1) если вы создаете экземпляр свойства таким образом, что это может спровоцировать нарушение ODR позже, когда завершится какой-то неполный тип, это не определено; и 2) он не определен, даже если вы на самом деле не вызываете нарушения ODR в своей программе, поэтому реализации стандартной библиотеки могут выбрать диагностику в той точке, в которой эта черта используется, если они того пожелают. Если у Cнего есть шаблон конструктора по умолчанию с каким-то странным SFINAE, который может изменить ответы, если Bон выполнен по-другому, то, конечно, от этого зависит черта.
TC