c++ - seguir - ¿Por qué el estándar diferencia entre inicialización de lista directa e inicialización de lista de copia?
no se guardaron los cambios vuelve a intentarlo instagram (1)
Sabemos que T v(x);
se llama inicialización directa , mientras que T v = x;
se llama inicialización de la copia , lo que significa que construirá una T
temporal de x
que se copiará / moverá a v
(que es más probable que se elimine).
Para la inicialización de listas, el estándar diferencia entre dos formas, según el contexto. T v{x};
se llama inicialización de lista directa mientras que T v = {x};
se llama copia-inicialización-lista :
§8.5.4 [dcl.init.list] p1
[...] La inicialización de lista puede ocurrir en contextos de inicialización directa o inicialización de copia; La inicialización de lista en un contexto de inicialización directa se denomina inicialización de lista directa y la inicialización de lista en un contexto de inicialización de copia se denomina inicialización de lista de copia . [...]
Sin embargo, solo hay dos referencias más en cada estándar. Para la inicialización directa de listas, se menciona al crear temporarios como T{x}
( §5.2.3/3
). Para copiar-lista-inicialización, es para la expresión en declaraciones de return {x};
como return {x};
( §6.6.3/2
).
Ahora, ¿qué pasa con el siguiente fragmento?
#include <initializer_list>
struct X{
X(X const&) = delete; // no copy
X(X&&) = delete; // no move
X(std::initializer_list<int>){} // only list-init from ''int''s
};
int main(){
X x = {42};
}
Normalmente, a partir de la X x = expr;
patrón, esperamos que el código no se compile, porque el constructor de movimiento de X
se define como delete
d. Sin embargo, las últimas versiones de Clang y GCC compilan el código anterior muy bien, y después de excavar un poco (y encontrar la cita anterior), ese es el comportamiento correcto. El estándar solo define el comportamiento para toda la inicialización de lista, y no diferencia entre las dos formas en absoluto, excepto por los puntos mencionados anteriormente. Bueno, al menos por lo que puedo ver, de todos modos.
Entonces, para resumir mi pregunta otra vez:
¿Cuál es el uso de la división de inicialización de listas en sus dos formas si (aparentemente) hacen exactamente lo mismo?
Porque no hacen exactamente lo mismo. Como se indica en 13.3.1.7 [over.match.list]:
En copy-list-initialization, si se elige un constructor explícito, la inicialización está mal formada.
En resumen, solo puede utilizar la conversión implícita en contextos de inicialización de lista de copia.
Esto se agregó explícitamente para hacer la inicialización uniforme no, um, uniforme. Sí, sé lo estúpido que suena, pero ten paciencia conmigo.
En 2008, se publicó N2640 (PDF) , que analiza el estado actual de inicialización uniforme. Observó específicamente la diferencia entre la inicialización directa ( T{...}
) y la inicialización de la copia ( T = {...}
).
Para resumir, la preocupación era que los constructores explicit
se convertirían efectivamente en inútiles. Si tengo algún tipo T
que quiero poder construir a partir de un entero, pero no quiero una conversión implícita, rotulo al constructor como explícito.
Entonces alguien hace esto:
T func()
{
return {1};
}
Sin la redacción actual, esto llamará a mi constructor explicit
. Entonces, ¿de qué sirve hacer explicit
al constructor si no cambia mucho?
Con la redacción actual, necesita al menos usar el nombre directamente:
T func()
{
return T{1};
}