Dlaczego mojej klasy nie da się zbudować domyślnie?

28

Mam te zajęcia:

#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;
}

Podczas kompilacji a_mnie jest domyślnie możliwy do zbudowania, ale ajest.

Przy zmianie Cna:

struct C {
      int i;
   };

wszystko w porządku.

Testowane z Clang 9.0.0.

Nicolas
źródło
3
GCC 8.3 - OK, GCC 9.1 / 9.2 - Fail.
Ewg.
2
Z C() {}tym też działa.
Ewg.
3
Pachnie mi to. Brak natychmiastowego oczywistego meczu na Bugzilli.
Wyścigi lekkości na orbicie
2
Ciekawe: static_assertw Anie, ale jeśli zamiast domyślnego skonstruować Twewnątrz A(np umieścić człon T t;tam), to działa wszystko jest w porządku. Niespójność między tym, co mówi ci cecha typu, a tym, co jest faktycznie możliwe ...
sebrockm
2
@Nicolas Prawda, ale dzieje się tak z powodu niektórych przypadków skrajnych, z których żaden nie ma tutaj zastosowania (w szczególności, jak mówi const int x;to samo zdanie na temat preferencji, jest niepoprawny bez inicjatora, wyłącznie ze względu na constzachowanie inicjalizacji wbudowanych typów i niektórych historia)
Wyścigi lekkości na orbicie

Odpowiedzi:

9

Jest to niedozwolone zarówno w tekście standardu, jak i w kilku głównych implementacjach, jak zauważono w komentarzach, ale z zupełnie niezwiązanych powodów.

Po pierwsze, powód „z książki”: moment tworzenia instancji A<C>jest, zgodnie ze standardem, bezpośrednio przed definicjąB , a moment tworzenia instancji std::is_default_constructible<C>znajduje się bezpośrednio przed tym:

W przypadku specjalizacji szablonu klasy, [...] jeśli specjalizacja jest domyślnie utworzona instancja, ponieważ jest do niej odwoływana z innej specjalizacji szablonu, jeżeli kontekst, z którego odwołuje się specjalizacja, zależy od parametru szablonu, a jeśli specjalizacja nie jest utworzona poprzednia do instancji załączającego szablonu, punkt tworzenia instancji znajduje się bezpośrednio przed punktem tworzenia instancji obejmującego szablonu. W przeciwnym razie punkt wystąpienia takiej specjalizacji bezpośrednio poprzedza deklarację zakresu nazw lub definicję odnoszącą się do specjalizacji.

Ponieważ Cw tym momencie jest on wyraźnie niekompletny, zachowanie tworzenia instancji std::is_default_constructible<C>jest niezdefiniowane. Patrz jednak główny problem 287 , który zmieniłby tę zasadę.


W rzeczywistości ma to związek z NSDMI.

  • NSDMI są dziwne, ponieważ mają opóźnioną analizę - lub w standardowym języku są „kontekstem pełnej klasy”.
  • Zatem = 0może to w zasadzie odnosić się do rzeczy, które Bnie zostały jeszcze zadeklarowane, więc implementacja nie może tak naprawdę próbować go przeanalizować, dopóki się nie zakończy B.
  • Ukończenie klasy wymaga niejawnej deklaracji specjalnych funkcji składowych, w szczególności domyślnego konstruktora, ponieważ Cnie ma deklarowanego konstruktora.
  • Części tej deklaracji (constexpr-ness, noexcept-ness) zależą od właściwości NSDMI.
  • Zatem, jeśli kompilator nie może przeanalizować NSDMI, nie może ukończyć klasy.
  • W związku z tym w momencie tworzenia wystąpienia A<C>wydaje się, że Cjest niekompletny.

Cały ten obszar zajmujący się regionami z opóźnioną analizą jest żałośnie nieokreślony, a towarzyszy temu rozbieżność w zakresie wdrażania. Może minąć trochę czasu, zanim zostanie oczyszczony.

TC
źródło
0

Niezdefiniowane zachowanie to:

Jeśli instancja powyższego szablonu zależy, bezpośrednio lub pośrednio, od niekompletnego typu, a instancja ta może dać inny wynik, jeśli ten typ zostałby hipotetycznie zakończony, zachowanie jest niezdefiniowane.

Zapomnienie
źródło
7
Dlaczego C jest niekompletny?
interjay
1
@interjay Cjest kompletny, ale Bnie jest. I B::Czależy pośrednio od B.
Evg,
1
@Evg Tekst „Zależy bezpośrednio lub pośrednio” pojawia się tylko na cppreference.com. Norma mówi tylko, że typ T musi być kompletny.
interjay
2
@interjay Napisałem większość tego sformułowania. Próbujemy powiedzieć, że 1) jeśli utworzysz instancję cechy w sposób, który może wywołać naruszenie ODR później, gdy jakiś niekompletny typ zostanie ukończony, jest to niezdefiniowane; oraz 2) jest niezdefiniowany, nawet jeśli w rzeczywistości nie powoduje to naruszenia ODR w twoim programie, tak aby standardowe implementacje bibliotek mogły zdiagnozować, że w danym momencie cecha jest używana, jeśli sobie tego życzą. Jeśli Cma domyślny szablon konstruktora z jakimś dziwnym SFINAE, który może zmieniać odpowiedzi, jeśli Bzostanie wypełniony inaczej, to na pewno cecha zależy od niego.
TC