Como emular a boot do array C “int arr = {e1, e2, e3,…}” comportamento com std :: array?

(Nota: Esta questão é sobre não ter que especificar o número de elementos e ainda permitir que tipos nesteds sejam inicializados diretamente.)
Esta questão discute os usos deixados por um array C como int arr[20]; . Na sua resposta , @James Kanze mostra uma das últimas fortalezas dos arrays C, é uma característica única de boot:

 int arr[] = { 1, 3, 3, 7, 0, 4, 2, 0, 3, 1, 4, 1, 5, 9 }; 

Nós não temos que especificar o número de elementos, hooray! Agora iterar sobre ele com as funções de C ++ 11 std::begin e std::end de ( ou suas próprias variantes ) e você nunca precisa sequer pensar em seu tamanho.

Agora, existem algumas maneiras (possivelmente TMP) de conseguir o mesmo com std::array ? O uso de macros permitiu torná-lo mais bonito. 🙂

 ??? std_array = { "here", "be", "elements" }; 

Edit : Versão intermediária, compilada a partir de várias respostas, se parece com isso:

 #include  #include  template<class T, class... Tail, class Elem = typename std::decay::type> std::array make_array(T&& head, Tail&&... values) { return { std::forward(head), std::forward(values)... }; } // in code auto std_array = make_array(1,2,3,4,5); 

E emprega todo o tipo de coisas legais do C ++ 11:

  • Modelos Variadic
  • sizeof...
  • referências de valor
  • encaminhamento perfeito
  • std::array , claro
  • boot uniforme
  • omitindo o tipo de retorno com boot uniforme
  • inferência de tipos ( auto )

E um exemplo pode ser encontrado aqui .

No entanto , como @Johannes aponta no comentário sobre a resposta de @ Xaade, você não pode inicializar tipos nesteds com essa function. Exemplo:

 struct A{ int a; int b; }; // C syntax A arr[] = { {1,2}, {3,4} }; // using std::array ??? std_array = { {1,2}, {3,4} }; 

Além disso, o número de inicializadores é limitado ao número de argumentos de function e modelo suportados pela implementação.

O melhor que posso pensar é:

 template auto make_array(T head, Tail... tail) -> std::array { std::array a = { head, tail ... }; return a; } auto a = make_array(1, 2, 3); 

No entanto, isso requer o compilador para fazer NRVO e, em seguida, também ignorar a cópia do valor retornado (que também é legal, mas não obrigatório). Na prática, eu esperaria que qualquer compilador C ++ pudesse otimizar isso de forma que fosse tão rápido quanto a boot direta.

Eu esperaria um make_array simples.

 template std::array make_array(T&&... refs) { return std::array{ { std::forward(refs)... } }; } 

Combinando algumas idéias de postagens anteriores, aqui está uma solução que funciona mesmo para construções aninhadas (testadas no GCC4.6):

 template  std::array make_array(T && t, Args &&... args) { static_assert(all_same::value, "make_array() requires all arguments to be of the same type."); // edited in return std::array{ std::forward(t), std::forward(args)...}; } 

Estranhamente, o can não pode tornar o valor de retorno uma referência rvalue, que não funcionaria para construções aninhadas. Enfim, aqui está um teste:

 auto q = make_array(make_array(make_array(std::string("Cat1"), std::string("Dog1")), make_array(std::string("Mouse1"), std::string("Rat1"))), make_array(make_array(std::string("Cat2"), std::string("Dog2")), make_array(std::string("Mouse2"), std::string("Rat2"))), make_array(make_array(std::string("Cat3"), std::string("Dog3")), make_array(std::string("Mouse3"), std::string("Rat3"))), make_array(make_array(std::string("Cat4"), std::string("Dog4")), make_array(std::string("Mouse4"), std::string("Rat4"))) ); std::cout < < q << std::endl; // produces: [[[Cat1, Dog1], [Mouse1, Rat1]], [[Cat2, Dog2], [Mouse2, Rat2]], [[Cat3, Dog3], [Mouse3, Rat3]], [[Cat4, Dog4], [Mouse4, Rat4]]] 

(Para a última saída, estou usando minha pretty-printer .)


Na verdade, vamos melhorar o tipo de segurança desta construção. Nós definitivamente precisamos que todos os tipos sejam iguais. Uma maneira é adicionar uma afirmação estática, que eu editei acima. A outra maneira é apenas ativar make_array quando os tipos são os mesmos, assim:

 template  typename std::enable_if::value, std::array>::type make_array(T && t, Args &&... args) { return std::array { std::forward(t), std::forward(args)...}; } 

De qualquer forma, você precisará da característica do tipo all_same . Aqui está, generalizando de std::is_same (note que decaying é importante para permitir a mistura de T , T& , T const & etc.):

 template  struct all_same { static const bool value = false; }; template  struct all_same { static const bool value = std::is_same::type, typename std::decay::type>::value && all_same::value; }; template  struct all_same { static const bool value = std::is_same::type, typename std::decay::type>::value; }; template  struct all_same { static const bool value = true; }; 

Note que make_array() retorna por copy-of-temporary, que o compilador (com sinalizadores de otimização suficientes!) make_array() permissão para tratar como um rvalue ou otimizar afastado, e std::array é um tipo agregado, então o compilador está livre para escolher o melhor método de construção possível.

Por fim, observe que não é possível evitar a construção de cópia / movimentação quando make_array configura o inicializador. Então std::array x{Foo(1), Foo(2)}; não tem copy / move, mas auto x = make_array(Foo(1), Foo(2)); tem dois copiar / move como os argumentos são encaminhados para make_array . Eu não acho que você pode melhorar isso, porque você não pode passar uma lista de inicializadores variádicos lexicamente para o auxiliar e deduzir o tipo e tamanho - se o pré-processador tivesse uma function sizeof... para argumentos variadicos, talvez isso pudesse ser feito, mas não dentro da linguagem principal.

Usando a syntax de retorno à direita make_array pode ser ainda mais simplificada

 #include  #include  #include  template  auto make_array(T&&... t) -> std::array, sizeof...(t)> { return {std::forward(t)...}; } int main() { auto arr = make_array(1, 2, 3, 4, 5); return 0; } 

Infelizmente para classs agregadas requer especificação de tipo explícita

 /* struct Foo { int a, b; }; */ auto arr = make_array(Foo{1, 2}, Foo{3, 4}, Foo{5, 6}); 

Na verdade, esta implementação make_array está listada no operador sizeof …


versão c + + 17

Graças à dedução de argumento de modelo para a proposta de modelos de class , podemos usar guias de dedução para nos livrarmos do make_array helper

 #include  namespace std { template  array(T... t) -> array, sizeof...(t)>; } int main() { std::array a{1, 2, 3, 4}; return 0; } 

Compilado com -std=c++1z em x86-64 gcc 7.0

O C ++ 11 suportará essa maneira de boot para contêineres (a maioria?) Padrão.

(Solução por @dyp)

Nota: requer C ++ 14 ( std::index_sequence ). Embora se possa implementar std::index_sequence em C ++ 11.

 #include  // --- #include  #include  template  using c_array = T[]; template constexpr auto make_array(T (&&src)[N], std::index_sequence) { return std::array{{ std::move(src[Indices])... }}; } template constexpr auto make_array(T (&&src)[N]) { return make_array(std::move(src), std::make_index_sequence{}); } // --- struct Point { int x, y; }; std::ostream& operator< < (std::ostream& os, const Point& p) { return os << "(" << px << "," << py << ")"; } int main() { auto xs = make_array(c_array{{1,2}, {3,4}, {5,6}, {7,8}}); for (auto&& x : xs) { std::cout < < x << std::endl; } return 0; } 

Eu sei que já faz algum tempo desde que esta pergunta foi feita, mas eu sinto que as respostas existentes ainda têm algumas falhas, então eu gostaria de propor a minha versão ligeiramente modificada. A seguir estão os pontos que eu acho que algumas respostas existentes estão faltando.


1. Não há necessidade de confiar na RVO

Algumas respostas mencionam que precisamos confiar no RVO para retornar o array construído. Isso não é verdade; podemos usar a boot da lista de cópias para garantir que nunca haverá temporários criados. Então, ao invés de:

 return std::array{values}; 

nós deveríamos fazer:

 return {{values}}; 

2. Make make_array uma function constexpr

Isso nos permite criar matrizes constantes em tempo de compilation.

3. Não é necessário verificar se todos os argumentos são do mesmo tipo

Primeiro, se não estiverem, o compilador emitirá um aviso ou erro, porque a boot da lista não permite o estreitamento. Em segundo lugar, mesmo se realmente decidirmos fazer nossa própria coisa static_assert (talvez para fornecer uma mensagem de erro melhor), ainda devemos provavelmente comparar os tipos decompostos dos argumentos em vez de tipos brutos. Por exemplo,

 volatile int a = 0; const int& b = 1; int&& c = 2; auto arr = make_array(a, b, c); // Will this work? 

Se estamos simplesmente static_assert ing que a , b têm o mesmo tipo, então esta verificação falhará, mas provavelmente não é o que esperamos. Em vez disso, devemos comparar seus tipos std::decay_t (que são todos int s)).

4. Deduza o tipo de valor da matriz diminuindo os argumentos encaminhados

Isso é semelhante ao ponto 3. Usando o mesmo snippet de código, mas não especifique o tipo de valor explicitamente desta vez:

 volatile int a = 0; const int& b = 1; int&& c = 2; auto arr = make_array(a, b, c); // Will this work? 

Nós provavelmente queremos fazer uma array , mas as implementações nas respostas existentes provavelmente não conseguem fazer isso. O que podemos fazer é, ao invés de retornar um std::array , retornar um std::array, …> .

Há uma desvantagem nessa abordagem: não podemos mais retornar uma array de tipo de valor qualificado para cv. Mas na maioria das vezes, em vez de algo como um array , nós const array um const array qualquer maneira. Existe um trade-off, mas acho razoável. O C ++ 17 std::make_optional também usa essa abordagem:

 template< class T > constexpr std::optional> make_optional( T&& value ); 

Levando em conta os pontos acima, uma implementação completa do make_array em C ++ 14 se parece com isso:

 #include  #include  #include  template constexpr std::array, 1 + sizeof... (Ts)> make_array(T&& t, Ts&&... ts) noexcept(noexcept(std::is_nothrow_constructible< std::array, 1 + sizeof... (Ts)>, T&&, Ts&&... >::value)) { return {{std::forward(t), std::forward(ts)...}}; } template constexpr std::array_t, 0> make_array() noexcept { return {}; } 

Uso:

 constexpr auto arr = make_array(make_array(1, 2), make_array(3, 4)); static_assert(arr[1][1] == 4, "!"); 

Se std :: array não for uma restrição e se você tiver Boost, então dê uma olhada em list_of() . Isso não é exatamente como a boot de matriz do tipo C que você deseja. Mas perto.

Crie um tipo de criador de matriz.

Ele sobrecarrega o operator, para gerar um modelo de expressão encadeando cada elemento para as referências de via anteriores.

Adicione uma function livre de finish que leva o criador de matriz e gera uma matriz diretamente da cadeia de referências.

A syntax deve ser algo como isto:

 auto arr = finish( make_array->* 1,2,3,4,5 ); 

Não permite {} construção baseada, como somente operator= does. Se você estiver disposto a usar = podemos fazê-lo funcionar:

 auto arr = finish( make_array= {1}={2}={3}={4}={5} ); 

ou

 auto arr = finish( make_array[{1}][{2}[]{3}][{4}][{5}] ); 

Nenhum destes parece boas soluções.

O uso de variamdics limita o limite imposto pelo compilador ao número de varargs e bloqueia o uso recursivo de {} para subestruturas.

No final, não há realmente uma boa solução.

O que eu faço é escrever meu código para consumir os dados T[] e std::array agnosticamente – não importa qual eu o alimente. Às vezes, isso significa que meu código de encaminhamento precisa transformar cuidadosamente [] matrizes em std::array transparente.