c++ - biblioteca - Cómo emular la inicialización del array C "int arr[]={e1, e2, e3,...}" comportamiento con std:: array?
dynamic array c++ (9)
(Nota: Esta pregunta se trata de no tener que especificar el número de elementos y aún así permitir que los tipos anidados se inicialicen directamente).
Esta pregunta discute los usos que quedan para una matriz C como int arr[20];
. En su respuesta , @James Kanze muestra uno de los últimos bastiones de las matrices en C, sus características únicas de inicialización:
int arr[] = { 1, 3, 3, 7, 0, 4, 2, 0, 3, 1, 4, 1, 5, 9 };
No tenemos que especificar la cantidad de elementos, ¡hurra! Ahora itere sobre él con las funciones de C ++ 11 std::begin
y std::end
de <iterator>
( o sus propias variantes ) y nunca necesitará siquiera pensar en su tamaño.
Ahora, ¿hay alguna forma (posiblemente TMP) de lograr lo mismo con std::array
? El uso de macros permite que se vea mejor. :)
??? std_array = { "here", "be", "elements" };
Editar : versión intermedia, compilada a partir de varias respuestas, se ve así:
#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);
Y emplea todo tipo de cosas geniales de C ++ 11:
- Plantillas variables
-
sizeof...
- referencias rvalue
- reenvío perfecto
-
std::array
, por supuesto - Inicialización uniforme
- omitiendo el tipo de retorno con inicialización uniforme
- escriba inferencia (
auto
)
Y un ejemplo se puede encontrar here .
Sin embargo , como señala @Johannes en el comentario sobre la respuesta de @ Xaade, no puede inicializar tipos anidados con dicha función. Ejemplo:
struct A{ int a; int b; };
// C syntax
A arr[] = { {1,2}, {3,4} };
// using std::array
??? std_array = { {1,2}, {3,4} };
Además, el número de inicializadores se limita al número de argumentos de función y plantilla admitidos por la implementación.
(Solución por @dyp)
Nota: requiere C ++ 14 ( std::index_sequence
). Aunque se podría implementar std::index_sequence
en 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;
}
C ++ 11 apoyará esta manera de inicialización para (la mayoría?) Contenedores estándar.
Combinando algunas ideas de publicaciones anteriores, aquí hay una solución que funciona incluso para construcciones anidadas (probado en 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)...};
}
Extrañamente, can no puede hacer que el valor de retorno sea una referencia de valor razonable, eso no funcionaría para las construcciones anidadas. De todos modos, aquí hay una prueba:
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 el último resultado estoy usando mi pretty-printer ).
En realidad, permítanos mejorar el tipo de seguridad de esta construcción. Definitivamente necesitamos que todos los tipos sean iguales. Una forma es agregar una aserción estática, que he editado anteriormente. La otra forma es solo habilitar make_array
cuando los tipos son los mismos, así:
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 cualquier manera, necesitará el rasgo de all_same<Args...>
variado all_same<Args...>
. Aquí está, generalizando desde std::is_same<S, T>
(tenga en cuenta que la descomposición es importante para permitir la mezcla 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; };
Tenga en cuenta que make_array()
devuelve por copy-of-temporary, que el compilador (con suficientes indicadores de optimización!) Puede tratar como un valor r u optimizar de otro modo, y std::array
es un tipo agregado, por lo que el compilador es libre para elegir el mejor método de construcción posible.
Finalmente, tenga en cuenta que no puede evitar la construcción de copiar / mover cuando make_array
configura el inicializador. Entonces std::array<Foo,2> x{Foo(1), Foo(2)};
no tiene copia / movimiento, pero auto x = make_array(Foo(1), Foo(2));
tiene dos copias / movimientos a medida que los argumentos se envían a make_array
. No creo que pueda mejorar eso, porque no puede pasar una lista de inicializadores variados léxicamente al auxiliar y deducir el tipo y el tamaño: si el preprocesador tenía una función de tamaño de sizeof...
para argumentos variados, quizás eso podría ser hecho, pero no dentro del lenguaje central.
Crear un tipo de creador de matriz.
Sobrecarga al operator,
para generar una plantilla de expresión encadenando cada elemento a las referencias de vía previas.
Agregue una función de finish
libre que tome el creador de la matriz y genere una matriz directamente desde la cadena de referencias.
La sintaxis debería verse más o menos así:
auto arr = finish( make_array<T>->* 1,2,3,4,5 );
No permite la construcción basada en {}
, ya que solo operator=
does. Si está dispuesto a usar =
podemos hacer que funcione:
auto arr = finish( make_array<T>= {1}={2}={3}={4}={5} );
o
auto arr = finish( make_array<T>[{1}][{2}[]{3}][{4}][{5}] );
Ninguno de estos parece una buena solución.
El uso de los canalesdics lo limita a su límite impuesto por el compilador sobre el número de varargs y bloquea el uso recursivo de {}
para las subestructuras.
Al final, realmente no hay una buena solución.
Lo que hago es escribir mi código para que consuma ambos datos T[]
y std::array
de forma agnóstica , no importa qué lo alimente. A veces esto significa que mi código de reenvío tiene que convertir cuidadosamente arrays []
en std::array
s de forma transparente.
El uso de la sintaxis de retorno final make_array
se puede simplificar aún más
#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;
}
Desafortunadamente para clases agregadas requiere especificación de tipo explícita
/*
struct Foo
{
int a, b;
}; */
auto arr = make_array(Foo{1, 2}, Foo{3, 4}, Foo{5, 6});
De hecho, esta implementación de make_array
se enumera en sizeof ... operador
versión c ++ 17
Gracias a la deducción del argumento de la plantilla para la propuesta de plantillas de clase , podemos usar guías de deducción para deshacerse de make_array
helper
#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 con el -std=c++1z
bajo x86-64 gcc 7.0
Esperaría un simple 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)... } };
}
Lo mejor que puedo pensar es:
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);
Sin embargo, esto requiere que el compilador haga NRVO y luego omita la copia del valor devuelto (que también es legal pero no obligatorio). En la práctica, esperaría que cualquier compilador de C ++ pueda optimizar eso de modo que sea tan rápido como la inicialización directa.
Sé que ha pasado bastante tiempo desde que se hizo esta pregunta, pero creo que las respuestas existentes todavía tienen algunas deficiencias, por lo que me gustaría proponer mi versión ligeramente modificada. Los siguientes son los puntos que creo que faltan algunas respuestas existentes.
1. No necesita confiar en RVO
Algunas respuestas mencionan que debemos confiar en RVO para devolver la array
construida. Eso no es verdad; podemos hacer uso de copy-list-initialization de copy-list-initialization para garantizar que nunca se crearán los temporales. Entonces, en lugar de:
return std::array<Type, …>{values};
deberiamos:
return {{values}};
2. Hacer make_array
una función constexpr
Esto nos permite crear matrices constantes en tiempo de compilación.
3. No es necesario verificar que todos los argumentos sean del mismo tipo
En primer lugar, si no lo son, el compilador emitirá una advertencia o error de todos modos porque la inicialización de la lista no permite el estrechamiento. En segundo lugar, incluso si realmente decidimos hacer nuestra propia static_assert
(tal vez para proporcionar un mejor mensaje de error), aún deberíamos comparar los tipos decaídos de los argumentos en lugar de los tipos sin procesar. Por ejemplo,
volatile int a = 0;
const int& b = 1;
int&& c = 2;
auto arr = make_array<int>(a, b, c); // Will this work?
Si simplemente static_assert
que a
, c
tienen el mismo tipo, entonces esta comprobación fallará, pero probablemente no sea la esperada. En su lugar, debemos comparar sus tipos std::decay_t<T>
(que son todos int
s)).
4. Deduzca el tipo de valor de matriz decayendo los argumentos reenviados
Esto es similar al punto 3. Usar el mismo fragmento de código, pero no especifique el tipo de valor explícitamente esta vez:
volatile int a = 0;
const int& b = 1;
int&& c = 2;
auto arr = make_array(a, b, c); // Will this work?
Es probable que deseemos hacer una array<int, 3>
, pero las implementaciones en las respuestas existentes probablemente no lo hagan. Lo que podemos hacer es, en lugar de devolver un std::array<T, …>
, devolver un std::array<std::decay_t<T>, …>
.
Hay una desventaja sobre este enfoque: ya no podemos devolver una array
de tipos de valores calificados como cv. Pero la mayoría de las veces, en lugar de algo así como una array<const int, …>
, const array<int, …>
una const array<int, …>
todos modos. Hay un intercambio, pero creo que es razonable. El C ++ 17 std::make_optional
también toma este enfoque:
template< class T >
constexpr std::optional<std::decay_t<T>> make_optional( T&& value );
Teniendo en cuenta los puntos anteriores, una implementación completa de make_array
en C ++ 14 se ve así:
#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, "!");
Si std :: array no es una restricción y si tiene Boost, eche un vistazo a list_of()
. Esto no es exactamente como la inicialización de matriz tipo C que desea. Pero cerca.