Como emular a inicialização da matriz C “int arr [] = {e1, e2, e3,…}” com o comportamento std :: array?


137

(Nota: Esta questão é sobre não precisar especificar o número de elementos e ainda permitir que tipos aninhados sejam inicializados diretamente.)
Esta questão discute os usos restantes para uma matriz C como int arr[20];. Em sua resposta , @James Kanze mostra uma das últimas fortalezas das matrizes C, são características únicas de inicialização:

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

Não precisamos especificar o número de elementos, viva! Agora, itere-o com as funções C ++ 11 std::begine std::endcom <iterator>( ou suas próprias variantes ) e você nunca precisa nem pensar em seu tamanho.

Agora, existem formas (possivelmente TMP) de conseguir o mesmo std::array? Uso de macros permitidas para torná-lo mais agradável. :)

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

Edit : Versão intermediária, compilada a partir de várias respostas, tem a seguinte aparência:

#include <array>
#include <utility>

template<class T, class... Tail, class Elem = typename std::decay<T>::type>
std::array<Elem,1+sizeof...(Tail)> make_array(T&& head, Tail&&... values)
{
  return { std::forward<T>(head), std::forward<Tail>(values)... };
}

// in code
auto std_array = make_array(1,2,3,4,5);

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

  • Modelos Variadic
  • sizeof...
  • referências rvalue
  • encaminhamento perfeito
  • std::array, claro
  • inicialização uniforme
  • omitindo o tipo de retorno com inicialização uniforme
  • inferência de tipo ( auto)

E um exemplo pode ser encontrado aqui .

No entanto , como @Johannes aponta no comentário da resposta do @ Xaade, você não pode inicializar tipos aninhados com essa função. 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 função e modelo suportados pela implementação.


Método variável. Não é inicialização, é mais como atribuição, mas é o mais próximo que posso chegar. Para obter a inicialização, você precisa ter acesso direto à memória.
Lee Louviere

Aparentemente, o C ++ 0x suporta a sintaxe do inicializador. Impressionante. É como ficar mais parecido com C #, com suporte a idiomas para suporte mais complicado. Alguém sabe se temos suporte formal de linguagem para interfaces ???
Lee Louviere 24/05

10
@Downvoter: Razão?
Xeo

1
Desculpas, qual é o significado da TMPsua pergunta?
precisa saber é o seguinte

1
@kevinarpe TMP provavelmente significa metaprogramação de modelos .
BeeOnRope

Respostas:


63

O melhor que consigo pensar é:

template<class T, class... Tail>
auto make_array(T head, Tail... tail) -> std::array<T, 1 + sizeof...(Tail)>
{
     std::array<T, 1 + sizeof...(Tail)> a = { head, tail ... };
     return a;
}

auto a = make_array(1, 2, 3);

No entanto, isso requer que o compilador faça NRVO e também pule a cópia do valor retornado (que também é legal, mas não obrigatório). Na prática, eu esperaria que qualquer compilador C ++ fosse capaz de otimizar isso de forma que seja tão rápido quanto a inicialização direta.


O gcc 4.6.0 não permite que o segundo seja compilado, reclamando da restrição da conversão de double para value_type, mas o clang ++ 2.9 é bom para os dois!
Cubbi

20
É com respostas como essa que eu entendo mais o que Bjarne disse sobre sentir-se "como uma nova linguagem" :) Modelos variados, especificador de retorno tardio e dedução de tipo tudo em um!
Matthieu M.

@ Matthieu: Agora adicione refs rvalue, encaminhamento perfeito e inicialização uniforme a partir do código do @ DeadMG e você terá muitos novos recursos configurados. :>
Xeo 24/05

1
@ Cubbi: na verdade, o g ++ está aqui - conversões restritas não são permitidas na inicialização agregada em C ++ 0x (mas permitidas no C ++ 03 - uma mudança que eu não sabia!). Vou remover a segunda make_arrayligação.
Pavel Minaev 24/05

@Cubbi, sim, mas isso é uma conversão explícita - também permitiria downcasts silenciosos e outras coisas semelhantes. Isso ainda pode ser feito usando static_assertum TMP para detectar quando Tailnão é implicitamente conversível e T, em seguida T(tail)..., usando , mas que resta como um exercício para o leitor :)
Pavel Minaev

39

Eu esperaria um simples make_array.

template<typename ret, typename... T> std::array<ret, sizeof...(T)> make_array(T&&... refs) {
    // return std::array<ret, sizeof...(T)>{ { std::forward<T>(refs)... } };
    return { std::forward<T>(refs)... };
}

1
Remova o std::array<ret, sizeof...(T)>na returndeclaração. Isso inutilmente força a existência de um construtor de movimentação no tipo de matriz (em oposição a uma construção a partir de T&&) no C ++ 14 e C ++ 11.
Yakk - Adam Nevraumont

7
Eu amo como as pessoas C ++ chamar assim tão simples :-)
Ciro Santilli郝海东冠状病六四事件法轮功

20

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

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

Estranhamente, não é possível transformar o valor de retorno em uma referência de valor, 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 impressora bonita .)


Na verdade, vamos melhorar o tipo de segurança dessa construção. Definitivamente, precisamos que todos os tipos sejam iguais. Uma maneira é adicionar uma asserção estática, que eu editei acima. A outra maneira é ativar apenas make_arrayquando os tipos são os mesmos, da seguinte forma:

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

De qualquer forma, você precisará da all_same<Args...>característica do tipo variável . Aqui está, generalizar a partir de std::is_same<S, T>(note que em decomposição é importante para permitir a mistura de T, T&, T const &etc.):

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

Observe que make_array()retorna por cópia de temporário, que o compilador (com sinalizadores de otimização suficientes!) Pode tratar como um rvalue ou, de outro modo, otimizar, estd::array é um tipo agregado; portanto, o compilador pode escolher o melhor método de construção possível .

Por fim, observe que você não pode evitar copiar / mover a construção ao make_arrayconfigurar o inicializador. Portanto std::array<Foo,2> x{Foo(1), Foo(2)};, não possui cópia / movimentação, mas auto x = make_array(Foo(1), Foo(2));possui duas cópias / movimentação conforme os argumentos são encaminhados make_array. Eu não acho que você pode melhorar isso, porque você não pode passar uma lista de inicializadores variados lexicamente para o auxiliar e deduzir o tipo e tamanho - se o pré-processador tiver uma sizeof...função para argumentos variados, talvez isso possa ser feito, mas não dentro do idioma principal.


13

O uso da sintaxe de retorno à direita make_arraypode ser ainda mais simplificado

#include <array>
#include <type_traits>
#include <utility>

template <typename... T>
auto make_array(T&&... t)
  -> std::array<std::common_type_t<T...>, sizeof...(t)>
{
  return {std::forward<T>(t)...};
}

int main()
{
  auto arr = make_array(1, 2, 3, 4, 5);
  return 0;
}

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

/*
struct Foo
{
  int a, b;
}; */

auto arr = make_array(Foo{1, 2}, Foo{3, 4}, Foo{5, 6});

De fato, esta make_arrayimplementação está listada no tamanho ... operador


versão c ++ 17

Graças à dedução de argumento de modelo para a proposta de modelos de classe , podemos usar guias de dedução para se livrar do make_arrayauxiliar

#include <array>

namespace std
{
template <typename... T> array(T... t)
  -> array<std::common_type_t<T...>, sizeof...(t)>;
}

int main()
{
  std::array a{1, 2, 3, 4};
  return 0; 
}

Compilado com -std=c++1zsinalizador sob x86-64 gcc 7.0


6
C ++ 17 deve ter uma guia de dedução já para isso: en.cppreference.com/w/cpp/container/array/deduction_guides
underscore_d

6

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


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

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

return std::array<Type, …>{values};

nós deveríamos fazer:

return {{values}};

2. Faça make_arrayuma constexprfunção

Isso nos permite criar matrizes constantes em tempo de compilação.

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 de qualquer maneira, porque a inicialização da lista não permite o estreitamento. Em segundo lugar, mesmo se realmente decidirmos fazer nossas próprias static_assertcoisas (talvez para fornecer uma melhor mensagem de erro), ainda devemos provavelmente comparar os tipos decaídos dos argumentos em vez dos tipos brutos. Por exemplo,

volatile int a = 0;
const int& b = 1;
int&& c = 2;

auto arr = make_array<int>(a, b, c);  // Will this work?

Se estamos simplesmente static_assertdigitando isso a, be ctemos o mesmo tipo, essa verificação falhará, mas provavelmente não é o que esperávamos. Em vez disso, devemos comparar seus std::decay_t<T>tipos (que são todosint s)).

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

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

volatile int a = 0;
const int& b = 1;
int&& c = 2;

auto arr = make_array(a, b, c);  // Will this work?

Provavelmente queremos fazer um array<int, 3>, mas as implementações nas respostas existentes provavelmente todas falham em fazer isso. O que podemos fazer é, em vez de retornar a std::array<T, …>, retornar a std::array<std::decay_t<T>, …>.

Há uma desvantagem nessa abordagem: não podemos mais retornar um arraytipo de valor qualificado para cv. Mas na maioria das vezes, em vez de algo parecido com um array<const int, …>, usamos um de const array<int, …>qualquer maneira. Existe uma troca, mas acho razoável. O C ++ 17 std::make_optionaltambém adota essa abordagem:

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

Levando em consideração os pontos acima, uma implementação completa do make_arrayC ++ 14 se parece com:

#include <array>
#include <type_traits>
#include <utility>

template<typename T, typename... Ts>
constexpr std::array<std::decay_t<T>, 1 + sizeof... (Ts)>
make_array(T&& t, Ts&&... ts)
    noexcept(noexcept(std::is_nothrow_constructible<
                std::array<std::decay_t<T>, 1 + sizeof... (Ts)>, T&&, Ts&&...
             >::value))

{
    return {{std::forward<T>(t), std::forward<Ts>(ts)...}};
}

template<typename T>
constexpr std::array<std::decay<T>_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, "!");

6

O C ++ 11 oferecerá suporte a esse tipo de inicialização para (a maioria?) Contêineres std.


1
No entanto, acho que o OP não deseja especificar o tamanho da matriz, mas size é um parâmetro de modelo de std :: array. Então você precisa de algo como std :: array <unsigned int, 5> n = {1,2,3,4,5};
Juanchopanza

std::vector<>não precisa do número inteiro explícito e não sei por std::arrayque.
26411 Richard

@ Richard, porque std :: vector tem tamanho dinâmico e std :: array tem tamanho fixo. Veja isto: en.wikipedia.org/wiki/Array_(C%2B%2B)
juanchopanza

@juanchopanza, mas a {...}sintaxe implica extensão constante em tempo de compilação; portanto, o ctor deve poder deduzir a extensão.
26411 Richard

1
std::initializer_list::sizenão é uma constexprfunção e, portanto, não pode ser usado assim. No entanto, há planos do libstdc ++ (a implementação fornecida com o GCC) para ter sua versão constexpr.
Luc Danton

5

(Solução por @dyp)

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

#include <iostream>

// ---

#include <array>
#include <utility>

template <typename T>
using c_array = T[];

template<typename T, size_t N, size_t... Indices>
constexpr auto make_array(T (&&src)[N], std::index_sequence<Indices...>) {
    return std::array<T, N>{{ std::move(src[Indices])... }};
}

template<typename T, size_t N>
constexpr auto make_array(T (&&src)[N]) {
    return make_array(std::move(src), std::make_index_sequence<N>{});
}

// ---

struct Point { int x, y; };

std::ostream& operator<< (std::ostream& os, const Point& p) {
    return os << "(" << p.x << "," << p.y << ")";
}

int main() {
    auto xs = make_array(c_array<Point>{{1,2}, {3,4}, {5,6}, {7,8}});

    for (auto&& x : xs) {
        std::cout << x << std::endl;
    }

    return 0;
}

Eu negligenciei a inicialização padrão dos elementos std :: array. Atualmente procurando uma correção.
Gabriel Garcia

@dyp Atualizei a resposta com o seu código. Se você decidir escrever sua própria resposta, informe-me e eu derrubarei a minha. Obrigado.
Gabriel Garcia

1
Não, está bem. A ideia de vincular uma matriz temporária para deduzir o comprimento é sua, e eu não verifiquei se meu código compila. Eu acho que ainda é a sua solução, e responda, com algum refinamento;) Pode-se argumentar que não há benefício para uma variável make_arraycomo na resposta do Puppy.
Dyp

Certo. Além disso, os modelos não podem deduzir tipos das listas de inicializadores, que é um dos requisitos da pergunta (inicialização centralizada aninhada).
Gabriel Garcia

1

Implementação compacta do С ++ 17.

template <typename... T>
constexpr auto array_of(T&&... t) {
    return std::array{ static_cast<std::common_type_t<T...>>(t)... };
}


0

Crie um tipo de criador de matriz.

Sobrecarrega operator,para gerar um modelo de expressão encadeando cada elemento ao anterior por meio de referências.

Adicione uma finishfunção livre que leve o criador da matriz e gere uma matriz diretamente da cadeia de referências.

A sintaxe deve ser algo como isto:

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

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

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

ou

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

Nada disso parece uma boa solução.

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

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

O que faço é escrever meu código para que ele consuma ambos os dados T[]e agnosticamente - ele não se importa com o que eu os alimento. Às vezes, isso significa que meu código de encaminhamento precisa transformar cuidadosamente matrizes em s de forma transparente.std::array[]std::array


1
"Estas não parecem boas soluções." É o que eu diria também: p
caps
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.