Fora do código genérico (ou seja, modelos), você pode (e eu faço) usar colchetes em qualquer lugar . Uma vantagem é que funciona em qualquer lugar, por exemplo, até mesmo para inicialização em 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";
};
ou para argumentos de função:
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 variáveis que não presto muita atenção entre os estilos T t = { init };
ou T t { init };
, acho que a diferença é pequena e, na pior das hipóteses, só resultará em uma mensagem útil do compilador sobre o uso incorreto de um explicit
construtor.
Para tipos que aceitam, std::initializer_list
embora às vezes, obviamente, os não- std::initializer_list
construtores são necessários (sendo o exemplo clássico std::vector<int> twenty_answers(20, 42);
). Não há problema em não usar aparelho ortodôntico.
Quando se trata de código genérico (ou seja, em modelos), o último parágrafo deve ter gerado alguns avisos. Considere o seguinte:
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{ return std::unique_ptr<T> { new T { std::forward<Args>(args)... } }; }
Em seguida, auto p = make_unique<std::vector<T>>(20, T {});
cria um vetor de tamanho 2 se T
for eg int
, ou um vetor de tamanho 20 se T
for std::string
. Um sinal muito revelador de que há algo muito errado acontecendo aqui é que não há nenhum traço que pode salvá-lo aqui (por exemplo, com SFINAE): std::is_constructible
é em termos de inicialização direta, enquanto estamos usando a inicialização de chave que adia para dirigir inicialização se e somente se não houver nenhum construtor std::initializer_list
interferindo. Da mesma forma std::is_convertible
não ajuda em nada.
Eu investiguei se é de fato possível rolar à mão uma característica que pode consertar isso, mas não estou muito otimista sobre isso. Em todo caso, não acho que estaríamos perdendo muito, acho que o fato de make_unique<T>(foo, bar)
resultar em uma construção equivalente a T(foo, bar)
é muito intuitivo; especialmente porque make_unique<T>({ foo, bar })
é bastante diferente e só faz sentido se foo
e bar
tiver o mesmo tipo.
Portanto, para código genérico, eu só uso chaves para inicialização de valor (por exemplo, T t {};
ou T t = {};
), o que é muito conveniente e acho superior ao método C ++ 03 T t = T();
. Caso contrário, é a sintaxe de inicialização direta (ou seja T t(a0, a1, a2);
), ou às vezes a construção padrão ( T t; stream >> t;
sendo o único caso em que eu uso isso, eu acho).
Isso não significa que todas as chaves sejam ruins, considere o exemplo anterior com correções:
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{ return std::unique_ptr<T> { new T(std::forward<Args>(args)...) }; }
Isso ainda usa colchetes para construir o std::unique_ptr<T>
, embora o tipo real dependa do parâmetro do modelo T
.