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 explicitcostruttore.
Per i tipi che accettano std::initializer_listanche se ovviamente a volte std::initializer_listsono 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_listinterferenze. 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 fooe barhanno 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.