È possibile impedire l'omissione di membri di inizializzazione aggregati?

È possibile impedire l'omissione di membri di inizializzazione aggregati?

Ecco un trucco che attiva un errore del linker se manca un inizializzatore richiesto:

struct init_required_t {
    template <class T>
    operator T() const; // Left undefined
} static const init_required;

Utilizzo:

struct Foo {
    int bar = init_required;
};

int main() {
    Foo f;
}

Risultato:

/tmp/ccxwN7Pn.o: In function `Foo::Foo()':
prog.cc:(.text._ZN3FooC2Ev[_ZN3FooC5Ev]+0x12): undefined reference to `init_required_t::operator int<int>() const'
collect2: error: ld returned 1 exit status

Avvertenze:

  • Prima di C++14, questo previene Foo dall'essere un aggregato.
  • Questo tecnicamente si basa su un comportamento indefinito (violazione ODR), ma dovrebbe funzionare su qualsiasi piattaforma sana.

Per clang e gcc puoi compilare con -Werror=missing-field-initializers che trasforma l'avviso sugli inizializzatori di campo mancanti in errore. fulmine

Modifica: Per MSVC, sembra che non venga emesso alcun avviso anche al livello /Wall , quindi non penso che sia possibile avvisare sulla mancanza di inizializzatori con questo compilatore. fulmine


Non è una soluzione elegante e maneggevole, suppongo... ma dovrebbe funzionare anche con C++11 e dare un errore in fase di compilazione (non in fase di collegamento).

L'idea è di aggiungere nella tua struttura un membro aggiuntivo, nell'ultima posizione, di un tipo senza inizializzazione predefinita (e che non può essere inizializzato con un valore di tipo VariablePtr (o qualunque sia il tipo di valori precedenti)

Ad esempio

struct bar
 {
   bar () = delete;

   template <typename T> 
   bar (T const &) = delete;

   bar (int) 
    { }
 };

struct foo
 {
   char a;
   char b;
   char c;

   bar sentinel;
 };

In questo modo sei costretto ad aggiungere tutti gli elementi nell'elenco di inizializzazione aggregato, incluso il valore per inizializzare in modo esplicito l'ultimo valore (un numero intero per sentinel , nell'esempio) o viene visualizzato un errore "chiamata al costruttore eliminato di 'bar'".

Quindi

foo f1 {'a', 'b', 'c', 1};

compilare e

foo f2 {'a', 'b'};  // ERROR

no.

Purtroppo anche

foo f3 {'a', 'b', 'c'};  // ERROR

non compila.

-- MODIFICA --

Come indicato da MSalters (grazie) c'è un difetto (un altro difetto) nel mio esempio originale:un bar il valore può essere inizializzato con un char valore (che è convertibile in int ), quindi funziona la seguente inizializzazione

foo f4 {'a', 'b', 'c', 'd'};

e questo può creare molta confusione.

Per evitare questo problema, ho aggiunto il seguente costruttore di modelli eliminato

 template <typename T> 
 bar (T const &) = delete;

quindi il precedente f4 dichiarazione fornisce un errore di compilazione perché d il valore viene intercettato dal costruttore del modello che viene eliminato