Fuera del código genérico (es decir, plantillas), puede (y yo lo hago) usar llaves en todas partes . Una ventaja es que funciona en todas partes, por ejemplo, incluso para la inicialización en clase:
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 para argumentos de función:
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));
Para las variables a las que no presto mucha atención entre los estilos T t = { init };
o T t { init };
, encuentro que la diferencia es menor y, en el peor de los casos, solo resultará en un mensaje útil del compilador sobre el mal uso de un explicit
constructor.
Para tipos que aceptan, std::initializer_list
aunque obviamente, a veces std::initializer_list
se necesitan los no constructores (siendo el ejemplo clásico std::vector<int> twenty_answers(20, 42);
). Entonces está bien no usar frenillos.
Cuando se trata de código genérico (es decir, en plantillas), el último párrafo debería haber generado algunas advertencias. Considera lo siguiente:
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{ return std::unique_ptr<T> { new T { std::forward<Args>(args)... } }; }
Luego auto p = make_unique<std::vector<T>>(20, T {});
crea un vector de tamaño 2 si T
es int
, por ejemplo , o un vector de tamaño 20 si T
es std::string
. Una señal muy reveladora de que algo muy mal está sucediendo aquí es que no hay ningún rasgo que pueda salvarlo aquí (por ejemplo, con SFINAE): std::is_constructible
es en términos de inicialización directa, mientras que estamos usando inicialización de llaves, que difiere de la inicialización directa. inicialización si y solo si no hay ningún constructor que std::initializer_list
interfiera. Del mismo modo no std::is_convertible
es de ayuda.
He investigado si es posible aplicar un rasgo que pueda solucionarlo, pero no soy demasiado optimista al respecto. En cualquier caso no creo que nos falte mucho, creo que el hecho de que make_unique<T>(foo, bar)
resulte en una construcción equivalente a T(foo, bar)
es muy intuitivo; especialmente dado que make_unique<T>({ foo, bar })
es bastante diferente y solo tiene sentido si foo
y bar
tienen el mismo tipo.
Por lo tanto, para el código genérico, solo uso llaves para la inicialización de valores (por ejemplo, T t {};
o T t = {};
), lo cual es muy conveniente y creo que es superior a la forma C ++ 03 T t = T();
. De lo contrario, es una sintaxis de inicialización directa (es decir T t(a0, a1, a2);
) o, a veces, una construcción predeterminada ( T t; stream >> t;
creo que es el único caso en el que lo uso).
Sin embargo, eso no significa que todas las llaves sean malas, considere el ejemplo anterior con correcciones:
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{ return std::unique_ptr<T> { new T(std::forward<Args>(args)...) }; }
Esto todavía usa llaves para construir el std::unique_ptr<T>
, aunque el tipo real depende del parámetro de la plantilla T
.