(Nota: Esta pregunta es no tener que especificar el número de elementos y todavía permitir tipos anidados a ser inicializados directamente.)
Esta pregunta se analizan los usos izquierda para una matriz C como int arr[20];
. En su respuesta , @James Kanze muestra una de las últimas fortalezas de las matrices C, sus características de inicialización únicas:
int arr[] = { 1, 3, 3, 7, 0, 4, 2, 0, 3, 1, 4, 1, 5, 9 };
No tenemos que especificar el número de elementos, ¡hurra! Ahora repita sobre él con las funciones de C ++ 11 std::begin
y std::end
desde <iterator>
( o sus propias variantes ) y nunca tendrá que pensar siquiera en su tamaño.
Ahora, ¿hay alguna forma (posiblemente TMP) de lograr lo mismo std::array
? El uso de macros permitió que se vea mejor. :)
??? std_array = { "here", "be", "elements" };
Editar : La 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 interesantes de C ++ 11:
- Plantillas Variadas
sizeof...
- referencias de valor
- reenvío perfecto
std::array
, por supuesto- inicialización uniforme
- omitiendo el tipo de retorno con inicialización uniforme
- inferencia de tipos (
auto
)
Y un ejemplo se puede encontrar aquí .
Sin embargo , como @Johannes señala 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.
TMP
tu pregunta?Respuestas:
Lo mejor que puedo pensar es:
Sin embargo, esto requiere que el compilador haga NRVO, y luego también 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 optimizarlo de manera tal que sea tan rápido como la inicialización directa.
fuente
make_array
llamada.static_assert
y algo de TMP para detectar cuándoTail
no es convertible implícitamenteT
y luego usandoT(tail)...
, pero eso queda como ejercicio para el lector :)Esperaría un simple
make_array
.fuente
std::array<ret, sizeof...(T)>
en lareturn
declaración. Eso obliga sin sentido a que exista un constructor de movimiento en el tipo de matriz (en oposición a una construcción desdeT&&
) en C ++ 14 y C ++ 11.Combinando algunas ideas de publicaciones anteriores, aquí hay una solución que funciona incluso para construcciones anidadas (probadas en GCC4.6):
Curiosamente, can no puede hacer que el valor de retorno sea una referencia de valor de r, que no funcionaría para construcciones anidadas. De todos modos, aquí hay una prueba:
(Para la última salida estoy usando mi bonita impresora ).
En realidad, mejoremos la seguridad de tipo 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 habilitar solo
make_array
cuando los tipos son iguales, así:De cualquier manera, necesitará el
all_same<Args...>
rasgo de tipo variadic . Aquí está, generalizando a partir destd::is_same<S, T>
(nota que la descomposición es importante para permitir la mezcla deT
,T&
,T const &
etc.):Tenga en cuenta que las
make_array()
devoluciones por copia de temporal, que el compilador (¡con suficientes indicadores de optimización!) Puede tratar como un valor r u optimizar de otro modo, ystd::array
es un tipo agregado, por lo que el compilador es libre de 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. Por lo tanto,std::array<Foo,2> x{Foo(1), Foo(2)};
no tiene copia / movimiento, peroauto x = make_array(Foo(1), Foo(2));
tiene dos copias / movimientos a medida que se envían los argumentosmake_array
. No creo que pueda mejorar eso, porque no puede pasar una lista de inicializador variadic léxicamente al asistente y deducir el tipo y el tamaño, si el preprocesador tenía unasizeof...
función para argumentos variados, tal vez eso podría hacerse, pero no dentro del lenguaje central.fuente
El uso de la sintaxis de retorno final
make_array
puede simplificarse aún másDesafortunadamente para las clases agregadas, requiere una especificación de tipo explícita
De hecho, esta
make_array
implementación aparece en el operador sizeof ...versión c ++ 17
Gracias a la deducción de argumentos de plantilla para la propuesta de plantillas de clase , podemos usar guías de deducción para deshacernos del
make_array
ayudanteCompilado con
-std=c++1z
bandera bajo x86-64 gcc 7.0fuente
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 es necesario confiar en RVO
Algunas respuestas mencionan que necesitamos confiar en RVO para devolver lo construido
array
. Eso no es verdad; podemos hacer uso de copy-list-initialization para garantizar que nunca se crearán temporarios. Entonces en lugar de:deberiamos:
2. Hacer
make_array
unaconstexpr
funciónEsto 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 están, 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 lo nuestro
static_assert
(quizás para proporcionar un mejor mensaje de error), probablemente deberíamos comparar los tipos deteriorados de los argumentos en lugar de los tipos sin formato. Por ejemplo,Si simplemente estamos
static_assert
haciendo esoa
,b
yc
tenemos el mismo tipo, entonces esta verificación fallará, pero eso probablemente no sea lo que esperaríamos. En cambio, debemos comparar susstd::decay_t<T>
tipos (que son todosint
s).4. Deduce el tipo de valor de matriz decayendo los argumentos reenviados
Esto es similar al punto 3. Usando el mismo fragmento de código, pero esta vez no especifique explícitamente el tipo de valor:
Probablemente queremos hacer un
array<int, 3>
, pero las implementaciones en las respuestas existentes probablemente no lo logren. Lo que podemos hacer es, en lugar de devolver astd::array<T, …>
, devolver astd::array<std::decay_t<T>, …>
.Hay una desventaja en este enfoque: ya no podemos devolver un
array
tipo de valor calificado por cv. Pero la mayoría de las veces, en lugar de algo así comoarray<const int, …>
, usaríamos un deconst array<int, …>
todos modos. Hay una compensación, pero creo que es razonable. El C ++ 17std::make_optional
también toma este enfoque:Teniendo en cuenta los puntos anteriores, una implementación de trabajo completa de
make_array
C ++ 14 se ve así:Uso:
fuente
C ++ 11 admitirá esta forma de inicialización para (¿la mayoría?) Contenedores estándar.
fuente
std::vector<>
no necesita el entero explícito, y no estoy seguro de por qué lostd::array
haría.{...}
sintaxis implica una extensión constante en tiempo de compilación, por lo que el ctor debería poder deducir la extensión.std::initializer_list::size
no es unaconstexpr
función y, por lo tanto, no se puede usar así. Sin embargo, hay planes de libstdc ++ (la implementación que se envía con GCC) para tener su versiónconstexpr
.(Solución por @dyp)
Nota: requiere C ++ 14 (
std::index_sequence
). Aunque uno podría implementarstd::index_sequence
en C ++ 11.fuente
make_array
como en la respuesta de Puppy.Implementación compacta С ++ 17.
fuente
Si std :: array no es una restricción y si tienes Boost, échale un vistazo
list_of()
. Esto no es exactamente como la inicialización de matriz de tipo C que desea. Pero cerca.fuente
Crea un tipo de generador de matriz.
Se sobrecarga
operator,
para generar una plantilla de expresión que encadena cada elemento al anterior a través de referencias.Agregue una
finish
función gratuita que tome el generador de matriz y genere una matriz directamente de la cadena de referencias.La sintaxis debería verse así:
No permite la
{}
construcción basada, como solo looperator=
hace. Si está dispuesto a usarlo=
, podemos hacerlo funcionar:o
Ninguno de estos parece buenas soluciones.
El uso de variadics lo limita a su límite impuesto por el compilador en el número de varargs y bloquea el uso recursivo de
{}
subestructuras.Al final, realmente no hay una buena solución.
Lo que hago es escribir mi código para que consuma ambos
T[]
y losstd::array
datos agnósticamente , no me importa cuál lo alimente. A veces esto significa que mi código de reenvío tiene que convertir cuidadosamente las[]
matrices enstd::array
s de forma transparente.fuente