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 explicitconstrutor.
Para tipos que aceitam, std::initializer_listembora às vezes, obviamente, os não- std::initializer_listconstrutores 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 Tfor eg int, ou um vetor de tamanho 20 se Tfor 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_listinterferindo. Da mesma forma std::is_convertiblenã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 fooe bartiver 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.