¿Puedo sacar elementos de un std::initializer_list<T>
?
#include <initializer_list>
#include <utility>
template<typename T>
void foo(std::initializer_list<T> list)
{
for (auto it = list.begin(); it != list.end(); ++it)
{
bar(std::move(*it)); // kosher?
}
}
Dado que std::intializer_list<T>
requiere atención especial del compilador y no tiene una semántica de valor como los contenedores normales de la biblioteca estándar de C ++, prefiero prevenir que lamentar y preguntar.
c++
templates
c++11
move-semantics
initializer-list
fredoverflow
fuente
fuente
initializer_list<T>
son no -const. Me gusta, seinitializer_list<int>
refiere aint
objetos. Pero creo que es un defecto: se pretende que los compiladores puedan asignar estáticamente una lista en la memoria de solo lectura.Respuestas:
No, eso no funcionará como se esperaba; todavía recibirá copias. Estoy bastante sorprendido por esto, ya que pensé que
initializer_list
existía para mantener una serie de provisionales hasta que fueranmove
eliminados.begin
yend
parainitializer_list
devoluciónconst T *
, por lo que el resultado demove
en su código esT const &&
: una referencia rvalue inmutable. Esa expresión no se puede mover de manera significativa. Se vinculará a un parámetro de función de tipoT const &
porque los rvalues se vinculan a las referencias const lvalue, y aún verá la semántica de copia.Probablemente la razón de esto es que el compilador puede optar por hacer
initializer_list
una constante inicializada estáticamente, pero parece que sería más limpio hacer su tipoinitializer_list
oconst initializer_list
a discreción del compilador, por lo que el usuario no sabe si esperar unaconst
o mutable. resultado debegin
yend
. Pero ese es solo mi presentimiento, probablemente haya una buena razón por la que estoy equivocado.Actualización: escribí una propuesta ISO para
initializer_list
admitir tipos de solo movimiento. Es solo un primer borrador y aún no está implementado en ninguna parte, pero puede verlo para analizar más el problema.fuente
std::move
es seguro, si no productivo. (Excepto losT const&&
constructores de movimiento.)const std::initializer_list<T>
o simplementestd::initializer_list<T>
de una manera que no cause sorpresas muy a menudo. Considere que cada argumento en elinitializer_list
puede ser unoconst
o no y que se conoce en el contexto del llamador, pero el compilador debe generar solo una versión del código en el contexto del destinatario (es decir, dentrofoo
no sabe nada sobre los argumentos que está pasando la persona que llama)std::initializer_list &&
sobrecarga haga algo, incluso si también se requiere una sobrecarga sin referencia. Supongo que sería aún más confuso que la situación actual, que ya es mala.No de la manera que pretendes. No puedes mover un
const
objeto. Ystd::initializer_list
solo brindaconst
acceso a sus elementos. Entonces el tipo deit
esconst T *
.Su intento de llamar
std::move(*it)
solo resultará en un valor l. IE: una copia.std::initializer_list
hace referencia a la memoria estática . Para eso es la clase. No puedes moverte de la memoria estática, porque el movimiento implica cambiarla. Solo puede copiar de él.fuente
initializer_list
referencia a la pila si es necesario. (Si el contenido no es constante, todavía es seguro para subprocesos).initializer_list
objeto en sí puede ser un valor x, pero su contenido (la matriz real de valores a la que apunta) lo esconst
, porque esos contenidos pueden ser valores estáticos. Simplemente no puede moverse del contenido de uninitializer_list
.const
x.move
puede no tener sentido, pero es legal e incluso posible declarar un parámetro que acepte precisamente eso. Si mover un tipo en particular resulta ser una operación no operativa, incluso podría funcionar correctamente.std::move
. Esto asegura que puede saber por inspección cuándo ocurre una operación de movimiento, ya que afecta tanto al origen como al destino (no desea que suceda implícitamente para los objetos con nombre). Por eso, si usastd::move
en un lugar donde no ocurre una operación de movimiento (y no ocurrirá ningún movimiento real si tiene un valorconst
x), entonces el código es engañoso. Creo que es un errorstd::move
ser invocable en unconst
objeto.const &&
una clase de recolección de basura con punteros administrados, donde todo lo relevante era mutable y el movimiento movió la administración de punteros pero no afectó el valor contenido. Siempre hay casos extremos complicados: v).Esto no funcionará como se indica, porque
list.begin()
tiene tipoconst T *
y no hay forma de que pueda moverse desde un objeto constante. Los diseñadores del lenguaje probablemente lo hicieron así para permitir que las listas de inicializadores contengan, por ejemplo, constantes de cadena, de las que no sería apropiado moverse.Sin embargo, si se encuentra en una situación en la que sabe que la lista de inicializadores contiene expresiones rvalue (o si desea obligar al usuario a escribirlas), existe un truco que lo hará funcionar (me inspiré en la respuesta de Sumant para esto, pero la solución es mucho más simple que esa). Necesita que los elementos almacenados en la lista de inicializadores no sean
T
valores, sino valores que encapsulanT&&
. Entonces, incluso si esos valores en sí mismos estánconst
calificados, aún pueden recuperar un rvalue modificable.Ahora, en lugar de declarar un
initializer_list<T>
argumento, declara uninitializer_list<rref_capture<T> >
argumento. Aquí hay un ejemplo concreto, que involucra un vector destd::unique_ptr<int>
punteros inteligentes, para el cual solo se define la semántica de movimiento (por lo que estos objetos en sí mismos nunca pueden almacenarse en una lista de inicializadores); sin embargo, la lista de inicializadores a continuación se compila sin problemas.Una pregunta necesita una respuesta: si los elementos de la lista de inicializadores deben ser valores verdaderos (en el ejemplo son valores x), ¿el lenguaje asegura que la vida útil de los temporales correspondientes se extienda hasta el punto en el que se utilizan? Francamente, no creo que la sección 8.5 relevante de la norma aborde este problema en absoluto. Sin embargo, al leer 1.9: 10, parecería que la expresión completa relevante en todos los casos abarca el uso de la lista de inicializadores, por lo que creo que no hay peligro de que cuelguen referencias de rvalue.
fuente
"Hello world"
? Si se aleja de ellos, simplemente copie un puntero (o enlace una referencia).{..}
están vinculados a referencias en el parámetro de función derref_capture
. Esto no extiende su vida, todavía están destruidos al final de la expresión completa en la que fueron creados.std::initializer_list<rref_capture<T>>
de algún rasgo de la transformación de su elección - por ejemplo,std::decay_t
- para bloquear la deducción no deseado.Pensé que sería instructivo ofrecer un punto de partida razonable para una solución.
Comentarios en línea.
fuente
initializer_list
se podía mover de una, no si alguien tenía soluciones. Además, el principal punto de venta deinitializer_list
es que solo se basa en el tipo de elemento, no en la cantidad de elementos, y por lo tanto no requiere que los destinatarios también tengan una plantilla, y esto lo pierde por completo.initializer_list
pero que no estén sujetos a todas las restricciones que lo hacen útil. :)initializer_list
(a través de la magia del compilador) evita tener que modelar funciones en el número de elementos, algo que es inherentemente requerido por las alternativas basadas en matrices y / o funciones variadas, limitando así el rango de casos donde estos últimos son utilizables. Según tengo entendido, esta es precisamente una de las principales razones para tenerinitializer_list
, por lo que me pareció digno de mención.Parece que no está permitido en el estándar actual como ya se respondió . Aquí hay otra solución para lograr algo similar, definiendo la función como variable en lugar de tomar una lista de inicializadores.
Las plantillas variables pueden manejar referencias de valor r de manera apropiada, a diferencia de initializer_list.
En este código de ejemplo, utilicé un conjunto de pequeñas funciones auxiliares para convertir los argumentos variadic en un vector, para hacerlo similar al código original. Pero, por supuesto, puede escribir una función recursiva con plantillas variadas directamente en su lugar.
fuente
initializer_list
se podía mover de una, no si alguien tenía soluciones. Además, el principal punto de venta deinitializer_list
es que solo se basa en el tipo de elemento, no en la cantidad de elementos, y por lo tanto no requiere que los destinatarios también tengan una plantilla, y esto lo pierde por completo.Tengo una implementación mucho más simple que hace uso de una clase contenedora que actúa como una etiqueta para marcar la intención de mover los elementos. Este es un costo en tiempo de compilación.
La clase contenedora está diseñada para usarse en la forma en que
std::move
se usa, simplemente reemplácelastd::move
conmove_wrapper
, pero esto requiere C ++ 17. Para especificaciones más antiguas, puede utilizar un método de construcción adicional.Deberá escribir métodos de constructor / constructores que acepten clases contenedoras dentro
initializer_list
y mover los elementos en consecuencia.Si necesita copiar algunos elementos en lugar de moverlos, cree una copia antes de pasarla a
initializer_list
.El código debe estar autodocumentado.
fuente
En lugar de usar a
std::initializer_list<T>
, puede declarar su argumento como una referencia de rvalue de matriz:Ver ejemplo usando
std::unique_ptr<int>
: https://gcc.godbolt.org/z/2uNxv6fuente
Considere el
in<T>
idioma descrito en cpptruths . La idea es determinar lvalue / rvalue en tiempo de ejecución y luego llamar a move o copy-construction.in<T>
detectará rvalue / lvalue incluso si la interfaz estándar proporcionada por initializer_list es una referencia constante.fuente