Perché la mia classe non è costruibile per difetto?


28

Ho quelle classi:

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

Durante la compilazione, a_mnon è costruibile di default ma lo aè.

Quando si cambia Cin:

struct C {
      int i;
   };

va tutto bene.

Testato con Clang 9.0.0.


3
GCC 8.3 - OK, GCC 9.1 / 9.2 - Fallito.
Evg

2
Con C() {}esso funziona anche.
Evg

3
Questo puzza di buggy per me. Nessuna corrispondenza immediatamente evidente su Bugzilla.
Razze di leggerezza in orbita

2
Interessante: l'in static_assertin Afallisce, ma se invece di default costruisci un Tinterno di A(es. Metti un membro T t;lì), funziona tutto bene. Un'incoerenza tra ciò che ti dice la caratteristica del tipo e ciò che è effettivamente possibile ...
sebrockm

2
@Nicolas Vero, ma è a causa di alcuni casi limite, nessuno dei quali si applica qui (in particolare, come dice la stessa frase su cppreference, const int x;non è valido senza un inizializzatore, semplicemente a causa constdel comportamento di inizializzazione e di alcuni tipi incorporati e alcuni storia)
Razze di leggerezza in orbita

Risposte:


9

Ciò è vietato sia dal testo dello standard sia da diverse importanti implementazioni, come osservato nei commenti, ma per ragioni completamente indipendenti.

In primo luogo, la ragione "per libro": il punto di istanza di A<C>è, secondo lo standard, immediatamente prima della definizione diB , e il punto di istanza di std::is_default_constructible<C>è immediatamente prima di quello:

Per una specializzazione di modello di classe, [...] se la specializzazione è istanziata in modo implicito perché fa riferimento all'interno di un'altra specializzazione di modello, se il contesto da cui si fa riferimento alla specializzazione dipende da un parametro di modello e se la specializzazione non è istanziata in precedenza all'istanza del modello di inclusione, il punto di istanza è immediatamente prima del punto di istanza del modello di inclusione. In caso contrario, il punto di istanza per tale specializzazione precede immediatamente la dichiarazione o la definizione dell'ambito dello spazio dei nomi che si riferisce alla specializzazione.

Poiché Ca quel punto è chiaramente incompleto, il comportamento dell'istanza std::is_default_constructible<C>non è definito. Tuttavia, vedi il problema principale 287 , che cambierebbe questa regola.


In realtà, questo ha a che fare con NSDMI.

  • Le NSDMI sono strane perché ottengono un'analisi ritardata o, in termini standard, sono un "contesto di classe completa".
  • Pertanto, ciò = 0potrebbe in linea di principio riferirsi a cose Bnon ancora dichiarate, quindi l'implementazione non può davvero provare ad analizzarlo fino a quando non sarà terminato B.
  • Il completamento di una classe richiede la dichiarazione implicita di funzioni membro speciali, in particolare il costruttore predefinito, poiché Cnon è stato dichiarato un costruttore.
  • Parti di tale dichiarazione (competenza, noexcept-ness) dipendono dalle proprietà del NSDMI.
  • Pertanto, se il compilatore non può analizzare NSDMI, non può completare la classe.
  • Di conseguenza, nel momento in cui istanzia A<C>, pensa che Csia incompleto.

L'intera area relativa alle regioni ritardate analizzate è tristemente sottostimata, con una divergenza di attuazione che accompagna. Potrebbe volerci un po 'prima che venga ripulito.


0

Comportamento indefinito è:

Se un'istanza di un modello sopra dipende, direttamente o indirettamente, da un tipo incompleto e tale istanza potrebbe produrre un risultato diverso se quel tipo fosse ipoteticamente completato, il comportamento non è definito.


7
Perché C è incompleto?
Interjay

1
@interjay, Cè completo, ma Bnon lo è. E B::Cdipende indirettamente da B.
Evg

1
@Evg Il testo "Dipende direttamente o indirettamente" appare solo su cppreference.com. Lo standard dice solo che il tipo T deve essere completo.
Interjay


2
@interjay Ho scritto la maggior parte di questa formulazione. Quello che stiamo cercando di dire è che 1) se si crea un'istanza di un tratto in un modo che potrebbe provocare una violazione ODR in seguito quando viene completato un tipo incompleto, ciò non è definito; e 2) non è definito anche se in realtà non causi una violazione ODR nel tuo programma, quindi le implementazioni di librerie standard possono scegliere di diagnosticare che nel punto in cui il tratto viene utilizzato se lo desiderano. Se Cha un modello di costruzione predefinito con qualche SFINAE strano che può cambiare le risposte se Bviene completato in modo diverso, allora certo, il tratto dipende da esso.
TC
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.