c++ copy-constructor initializer-list list-initialization

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:

  1. Si T es un agregado ...
  2. De lo contrario, si la lista de inicializadores no tiene elementos ...
  3. De lo contrario, si T es una especialización de std::initializer_list<E> ...

pasando por la lista hasta ahora:

  1. Foo no es un agregado.
  2. Tiene un elemento.
  3. Foo no es una especialización de std::initializer_list<E> .

Luego pulsamos [dcl.init.list] /3.4:

De lo contrario, si T es 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 agregada T están inicializados en lista (8.5.4), la resolución de sobrecarga selecciona el constructor en dos fases:

  1. Inicialmente, las funciones candidatas son los constructores de la lista de inicializadores (8.5.4) de la clase T y la lista de argumentos consta de la lista de inicializadores como un único argumento.
  2. 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 T y 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 T tiene 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 E y T no es un tipo de referencia o si su tipo referenciado está relacionado con E , 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 a T , 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 T son:

Si T es 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.