Se llama a C++ Copy constructor en lugar de initializer_list<>
c++ 11 constructor (2)
Examinemos lo que dice la especificación de C ++ 14 sobre la inicialización de listas aquí. [dcl.init.list] 3 tiene una secuencia de reglas que deben aplicarse en orden:
3.1 no se aplica, ya que Foo no es un agregado.
3.2 no se aplica, ya que la lista no está vacía.
3.3 no se aplica, ya que Foo no es una especialización de initializer_list .
3.4 se aplica, ya que Foo es un tipo de clase. Dice considerar los constructores con resolución de sobrecarga, de acuerdo con [over.match.list]. Y esa regla dice que primero hay que verificar los constructores initializer_list . Dado que su tipo tiene un constructor initilaizer_list , el compilador debe verificar si una lista de initializer_list coincida con uno de esos constructores se puede fabricar a partir de los valores dados. Puede, así que eso es lo que hay que llamar .
En resumen, GCC tiene razón y Clang está equivocado .
Cabe señalar que el borrador de trabajo de C ++ 17 no cambia nada al respecto. Tiene una nueva sección 3.1 que tiene una redacción especial para listas de valor único, pero que solo se aplica a los agregados . Foo no es un agregado, por lo que no se aplica.
Basado en este código
struct Foo
{
Foo()
{
cout << "default ctor" << endl;
}
Foo(std::initializer_list<Foo> ilist)
{
cout << "initializer list" << endl;
}
Foo(const Foo& copy)
{
cout << "copy ctor" << endl;
}
};
int main()
{
Foo a;
Foo b(a);
// This calls the copy constructor again!
//Shouldn''t this call the initializer_list constructor?
Foo c{b};
_getch();
return 0;
}
La salida es:
ctor por defecto
copia ctor
copia ctor
En el tercer caso, estoy poniendo b en la inicialización de llaves que debería llamar al constructor initializer_list <>.
En cambio, el constructor de copia toma la iniciativa.
¿Alguien de ustedes me dirá cómo funciona esto y por qué?
Como señaló Nicol Bolas, la versión original de esta respuesta era incorrecta: cppreference en el momento de escribir, documentaba incorrectamente el orden en que los constructores se consideraban en la inicialización de listas. A continuación hay una respuesta que utiliza las reglas tal como existen en el borrador n4140 de la norma, que es muy similar a la norma oficial C ++ 14.
El texto de la respuesta original todavía se incluye, para el registro.
Respuesta actualizada
Según el comentario de NathanOliver, gcc y clang producen diferentes resultados en esta situación:
g++ -std=c++14 -Wall -pedantic -pthread main.cpp && ./a.out
default ctor
copy ctor
copy ctor
initializer list
clang++ -std=c++14 -Wall -pedantic -pthread main.cpp && ./a.out
default ctor
copy ctor
copy ctor
gcc es correcto
n4140 [dcl.init.list] / 1
La inicialización de lista es la inicialización de un objeto o referencia desde una lista-iniciada con paréntesis.
Está utilizando la inicialización de listas allí, y como c es un objeto, las reglas para su inicialización de listas se definen en [dcl.init.list] / 3:
[dcl.init.list] / 3:
La inicialización de lista de un objeto o referencia de tipo T se define de la siguiente manera:
- Si
Tes un agregado ...- De lo contrario, si la lista de inicializadores no tiene elementos ...
- De lo contrario, si
Tes una especialización destd::initializer_list<E>...
pasando por la lista hasta ahora:
-
Foono es un agregado. - Tiene un elemento.
-
Foono es una especialización destd::initializer_list<E>.
Luego pulsamos [dcl.init.list] /3.4:
De lo contrario, si
Tes un tipo de clase, los constructores son considerados. Los constructores correspondientes se enumeran y el mejor se elige mediante resolución de sobrecarga (13.3, 13.3.1.7). Si se requiere una conversión de reducción (ver más abajo) para convertir cualquiera de los argumentos, el programa no está bien formado.
Ahora estamos llegando a alguna parte. 13.3.1.7 también se conoce como [over.match.list]:
Inicialización por inicialización de lista
Cuando los objetos de clase de clase no agregadaTestán inicializados en lista (8.5.4), la resolución de sobrecarga selecciona el constructor en dos fases:
- Inicialmente, las funciones candidatas son los constructores de la lista de inicializadores (8.5.4) de la clase
Ty la lista de argumentos consta de la lista de inicializadores como un único argumento.- Si no se encuentra un constructor de lista de inicializadores viable, la resolución de sobrecarga se realiza de nuevo, donde las funciones candidatas son todas las constructoras de la clase
Ty la lista de argumentos consta de los elementos de la lista de inicializadores.
Por lo tanto, el constructor de copia solo se considerará después de los constructores de la lista de inicializadores, en la segunda fase de resolución de sobrecarga. El constructor de la lista de inicializadores se debe utilizar aquí.
Vale la pena señalar que [over.match.list] luego continúa con:
Si la lista de inicializadores no tiene elementos y
Ttiene un constructor predeterminado, se omite la primera fase. En la inicialización de la lista de copia, si se elige un constructor explícito, la inicialización no está bien formada.
y que después de [dcl.init.list] / 3. 5 trata de la inicialización de la lista de un solo elemento:
De lo contrario, si la lista de inicializadores tiene un solo elemento de tipo
EyTno es un tipo de referencia o si su tipo referenciado está relacionado conE, el objeto o la referencia se inicializa desde ese elemento; Si se requiere una conversión de reducción (ver más abajo) para convertir el elemento aT, el programa no está bien formado.
lo que explica dónde cppreference obtuvo su caso especial para la inicialización de la lista de un solo elemento, aunque lo colocaron más alto en el orden de lo que debería ser.
Respuesta original
Se está encontrando con un aspecto interesante de la inicialización de lista, donde si la lista cumple con ciertos requisitos, puede tratarse como una inicialización de copia en lugar de una inicialización de lista.
de cppreference :
Los efectos de la inicialización de lista de un objeto de tipo
Tson:Si
Tes un tipo de clase y la lista de inicializadores tiene un solo elemento del mismo tipo o derivado (posiblemente calificado por cv), el objeto se inicializa desde ese elemento (mediante copia-inicialización para copiar-lista-inicialización, o directamente inicialización para inicialización directa de listas). (desde c ++ 14)
Foo c{b} cumple todos estos requisitos.