Warum ist meine Klasse nicht standardmäßig konstruierbar?

28

Ich habe diese Klassen:

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

Ist beim Kompilieren a_mnicht standardmäßig konstruierbar, aist es aber .

Beim Wechsel Czu:

struct C {
      int i;
   };

alles ist gut.

Getestet mit Clang 9.0.0.

Nicolas
quelle
3
GCC 8.3 - OK, GCC 9.1 / 9.2 - Fehler.
Evg
2
Damit C() {}funktioniert es auch.
Evg
3
Das riecht für mich nach Buggy. Keine sofort offensichtliche Übereinstimmung mit Bugzilla.
Leichtigkeitsrennen im Orbit
2
Interessant: Das static_assertIn Aschlägt fehl, aber wenn Sie stattdessen standardmäßig ein TInside von erstellen A(z. B. ein Mitglied T t;dort platzieren), funktioniert alles einwandfrei. Eine Inkonsistenz zwischen dem, was das
Typmerkmal
2
@Nicolas Stimmt, aber das liegt an einigen Randfällen, von denen keiner hier zutrifft (insbesondere, wie der gleiche Satz auf cppreference sagt, const int x;ist ohne Initialisierer ungültig, allein aufgrund des constund des Initialisierungsverhaltens von eingebauten Typen und einigen Geschichte)
Leichtigkeitsrennen im Orbit

Antworten:

9

Dies wird sowohl durch den Text des Standards als auch durch mehrere wichtige Implementierungen, wie in den Kommentaren angegeben, nicht zugelassen, jedoch aus völlig unabhängigen Gründen.

Erstens der Grund "nach dem Buch": Der Punkt der Instanziierung von A<C>liegt nach dem Standard unmittelbar vor der Definition vonB und der Punkt der Instanziierung von std::is_default_constructible<C>liegt unmittelbar davor:

Für eine Klassenvorlagenspezialisierung [...], wenn die Spezialisierung implizit instanziiert wird, weil sie aus einer anderen Vorlagenspezialisierung heraus referenziert wird, wenn der Kontext, aus dem auf die Spezialisierung verwiesen wird, von einem Vorlagenparameter abhängt und wenn die Spezialisierung nicht zuvor instanziiert wurde Für die Instanziierung der einschließenden Vorlage liegt der Instanziierungspunkt unmittelbar vor dem Instanziierungspunkt der einschließenden Vorlage. Andernfalls steht der Instanziierungspunkt für eine solche Spezialisierung unmittelbar vor der Namespace-Bereichsdeklaration oder -definition, die sich auf die Spezialisierung bezieht.

Da Cdies zu diesem Zeitpunkt eindeutig unvollständig ist, ist das Verhalten der Instanziierung std::is_default_constructible<C>undefiniert. Siehe jedoch Kernproblem 287 , das diese Regel ändern würde.


In Wirklichkeit hat dies mit dem NSDMI zu tun.

  • NSDMIs sind seltsam, weil sie verzögert analysiert werden - oder im Standard-Sprachgebrauch sind sie ein "Kontext der vollständigen Klasse".
  • Dies = 0könnte sich also im Prinzip auf Dinge beziehen, die Bnoch nicht deklariert sind, sodass die Implementierung nicht wirklich versuchen kann, sie zu analysieren, bis sie fertig ist B.
  • Das Abschließen einer Klasse erfordert die implizite Deklaration spezieller Elementfunktionen, insbesondere des Standardkonstruktors, da Ckein Konstruktor deklariert ist.
  • Teile dieser Erklärung (Konstanz, Nichtausnahme) hängen von den Eigenschaften des NSDMI ab.
  • Wenn der Compiler das NSDMI nicht analysieren kann, kann er die Klasse nicht vervollständigen.
  • Infolgedessen hält es zu dem Zeitpunkt, an dem es instanziiert wird A<C>, dies für Cunvollständig.

Dieser gesamte Bereich, der sich mit Regionen mit verzögerter Analyse befasst, ist stark unterbestimmt, mit der damit einhergehenden Divergenz bei der Umsetzung. Es kann eine Weile dauern, bis es aufgeräumt wird.

TC
quelle
0

Undefiniertes Verhalten ist es:

Wenn eine Instanziierung einer obigen Vorlage direkt oder indirekt von einem unvollständigen Typ abhängt und diese Instanziierung zu einem anderen Ergebnis führen könnte, wenn dieser Typ hypothetisch abgeschlossen wäre, ist das Verhalten undefiniert.

Vergessenheit
quelle
7
Warum ist C unvollständig?
Zwischenzeit
1
@interjay, Cist vollständig, aber Bnicht. Und B::Chängt indirekt davon ab B.
Evg
1
@Evg Der Text "Direkt oder indirekt abhängig" wird nur auf cppreference.com angezeigt. Der Standard sagt nur, dass der Typ T vollständig sein muss.
Zwischenzeit
2
@interjay Ich habe den größten Teil dieser Formulierung geschrieben. Wir versuchen zu sagen, dass 1) wenn Sie ein Merkmal auf eine Weise instanziieren, die später, wenn ein unvollständiger Typ abgeschlossen ist, eine ODR-Verletzung hervorrufen könnte, dies nicht definiert ist; und 2) es ist nicht definiert , auch wenn Sie nicht wirklich in Ihrem Programm Ursache eine ODR Verletzung, so dass Standardbibliothek Implementierungen können wählen , zu diagnostizieren , dass der Zug an der Stelle verwendet wird , wenn sie dies wünscht. Wenn Ces eine Standardkonstruktorvorlage mit einer seltsamen SFINAE gibt, die Antworten ändern kann, wenn Bsie anders ausgeführt wird, hängt das Merkmal davon ab.
TC