Al di fuori del codice generico (cioè i modelli), puoi (e lo faccio) usare le parentesi graffe ovunque . Un vantaggio è che funziona ovunque, ad esempio anche per l'inizializzazione in classe:
struct foo {
// Ok
std::string a = { "foo" };
// Also ok
std::string b { "bar" };
// Not possible
std::string c("qux");
// For completeness this is possible
std::string d = "baz";
};
o per argomenti di funzione:
void foo(std::pair<int, double*>);
foo({ 42, nullptr });
// Not possible with parentheses without spelling out the type:
foo(std::pair<int, double*>(42, nullptr));
Per le variabili non faccio molta attenzione tra gli stili T t = { init };
o T t { init };
, trovo che la differenza sia minore e nel peggiore dei casi si tradurrà solo in un utile messaggio del compilatore sull'uso improprio di un explicit
costruttore.
Per i tipi che accettano std::initializer_list
anche se ovviamente a volte std::initializer_list
sono necessari i non costruttori (l'esempio classico è std::vector<int> twenty_answers(20, 42);
). Allora va bene non usare l'apparecchio.
Quando si tratta di codice generico (cioè nei modelli), l'ultimo paragrafo dovrebbe aver sollevato alcuni avvertimenti. Considera quanto segue:
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{ return std::unique_ptr<T> { new T { std::forward<Args>(args)... } }; }
Quindi auto p = make_unique<std::vector<T>>(20, T {});
crea un vettore di dimensione 2 se T
è eg int
, o un vettore di dimensione 20 se T
è std::string
. Un segno molto rivelatore che sta succedendo qualcosa di molto sbagliato qui è che non c'è nessun tratto che può salvarti qui (ad esempio con SFINAE): std::is_constructible
è in termini di inizializzazione diretta, mentre stiamo usando l'inizializzazione di parentesi graffa che rimanda a dirigere- inizializzazione se e solo se non c'è nessun costruttore che prende std::initializer_list
interferenze. Allo stesso modo non std::is_convertible
è di alcun aiuto.
Ho studiato se è effettivamente possibile tirare a mano un tratto che può risolverlo, ma non sono eccessivamente ottimista al riguardo. In ogni caso non credo che ci mancherebbe molto, credo che il fatto che si make_unique<T>(foo, bar)
traduca in una costruzione equivalente a T(foo, bar)
sia molto intuitivo; soprattutto dato che make_unique<T>({ foo, bar })
è abbastanza dissimile e ha senso solo se foo
e bar
hanno lo stesso tipo.
Quindi per il codice generico uso solo le parentesi graffe per l'inizializzazione del valore (ad esempio T t {};
o T t = {};
), che è molto conveniente e penso superiore al modo C ++ 03 T t = T();
. Altrimenti è la sintassi di inizializzazione diretta (cioè T t(a0, a1, a2);
), o talvolta la costruzione predefinita ( T t; stream >> t;
essendo l'unico caso in cui lo uso credo).
Ciò non significa che tutte le parentesi graffe siano cattive, considera l'esempio precedente con le correzioni:
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{ return std::unique_ptr<T> { new T(std::forward<Args>(args)...) }; }
Questo utilizza ancora le parentesi graffe per costruire il std::unique_ptr<T>
, anche se il tipo effettivo dipende dal parametro del modello T
.