Perché l'inizializzazione aggregata non funziona più dal C++ 20 se un costruttore è esplicitamente predefinito o eliminato?




c++17 backwards-compatibility (2)

In realtà, MSDN ha risolto il problema nel documento seguente:

Specifica modificata di tipo aggregato

In Visual Studio 2019, in / std: c ++ latest, una classe con qualsiasi costruttore dichiarato dall'utente (ad esempio, incluso un costruttore dichiarato = default o = delete) non è un aggregato. In precedenza, solo i costruttori forniti dall'utente escludevano che una classe fosse un aggregato. Questa modifica pone ulteriori restrizioni su come inizializzare tali tipi.

Sto migrando un progetto Visual Studio C ++ da VS2017 a VS2019.

Ricevo un errore, che non si è mai verificato prima, che può essere riprodotto con queste poche righe di codice:

struct Foo
{
    Foo() = default;
    int bar;
};
auto test = Foo { 0 };

L'errore è

(6): errore C2440: 'inizializzazione': impossibile convertire da 'elenco inizializzatori' a 'Foo'

(6): nota: nessun costruttore poteva prendere il tipo di sorgente, oppure la risoluzione di sovraccarico del costruttore era ambigua

Il progetto è compilato con /std:c++latest flag. L'ho riprodotto su godbolt . Se lo cambio a /std:c++17 , si compila bene come prima.

Ho provato a compilare lo stesso codice con clang con -std=c++2a e ho -std=c++2a un errore simile. Inoltre, l'impostazione predefinita o l'eliminazione di altri costruttori genera questo errore.

Apparentemente, alcune nuove funzionalità di C ++ 20 sono state aggiunte in VS2019 e presumo che l'origine di questo problema sia descritta in https://en.cppreference.com/w/cpp/language/aggregate_initialization . Lì dice che un aggregato può essere una struttura che (tra gli altri criteri) ha

  • nessun costruttore fornito dall'utente, ereditato o esplicito (sono consentiti costruttori esplicitamente predefiniti o eliminati) (dal C ++ 17) (fino al C ++ 20)
  • nessun costruttore dichiarato dall'utente o ereditato (dal C ++ 20)

Si noti che la parte tra parentesi "sono consentiti costruttori esplicitamente predefiniti o eliminati" è stata eliminata e che "fornito dall'utente" è stato modificato in "dichiarato dall'utente".

Quindi la mia prima domanda è: ho ragione supponendo che questa modifica allo standard sia il motivo per cui il mio codice è stato compilato prima ma non lo fa più?

Naturalmente, è facile risolvere questo problema: basta rimuovere i costruttori esplicitamente predefiniti.

Tuttavia, ho esplicitamente fallito e cancellato molti costruttori in tutti i miei progetti perché ho trovato una buona abitudine rendere il codice molto più espressivo in questo modo perché si traduce semplicemente in meno sorprese rispetto a costruttori implicitamente inadempienti o cancellati. Con questo cambiamento, tuttavia, questa non sembra più una buona abitudine ...

Quindi la mia vera domanda è: qual è il ragionamento alla base di questa modifica da C ++ 17 a C ++ 20? Questa rottura della compatibilità con le versioni precedenti è stata fatta apposta? C'è stato qualche compromesso come "Ok, stiamo rompendo la compatibilità all'indietro qui, ma è per il bene più grande."? Cos'è questo bene più grande?


L'estratto di P1008 , la proposta che ha portato al cambiamento:

Il C ++ attualmente consente di inizializzare alcuni tipi con costruttori dichiarati dall'utente tramite l'inizializzazione aggregata, ignorando tali costruttori. Il risultato è un codice sorprendente, confuso e con errori. Questo documento propone una correzione che rende la semantica di inizializzazione in C ++ più sicura, più uniforme e più facile da insegnare. Discutiamo anche le modifiche di rottura introdotte da questa correzione.

Uno degli esempi che danno è il seguente.

struct X {
  int i{4};
  X() = default;
};

int main() {
  X x1(3); // ill-formed - no matching c’tor
  X x2{3}; // compiles!
}

Per me, è abbastanza chiaro che le modifiche proposte valgono l'incompatibilità all'indietro. E in effetti, non sembra più essere una buona pratica = default costruttori = default aggregati predefiniti.





c++20