Pregunta

Por alguna razón pensé que C ++ 0x permitía std::initializer_list como argumento de función para las funciones que esperan tipos que se pueden construir a partir de tales, por ejemplo std::vector. Pero aparentemente, no funciona. ¿Es solo mi compilador, o esto nunca funcionará? ¿Se debe a posibles problemas de resolución de sobrecarga?

#include <string>
#include <vector>

void function(std::vector<std::string> vec)
{
}

int main()
{
    // ok
    std::vector<std::string> vec {"hello", "world", "test"};

    // error: could not convert '{"hello", "world", "test"}' to 'std::vector...'
    function( {"hello", "world", "test"} );
}
¿Fue útil?

Solución

GCC tiene un error. El estándar lo hace válido. Ver:

Observe que hay dos lados de este

  • ¿Cómo y qué inicialización se hace en general?
  • ¿Cómo se usa la inicialización durante la resolución de sobrecarga y qué costo tiene?

La primera pregunta se responde en la sección 8.5. La segunda pregunta se responde en la sección 13.3. Por ejemplo, el enlace de referencia se maneja en 8.5.3 y 13.3.3.1.4, mientras que la inicialización de la lista se maneja en 8.5.4 y 13.3.3.1.5.

8.5/14,16:

La inicialización que ocurre en la forma

T x = a;

así como en la aprobación de argumentos, retorno de la función, lanzar una excepción (15.1), el manejo de una excepción (15.3) y la inicialización del miembro agregado (8.5.1) se llama copia inicialización.
.
.
La semántica de los inicializadores es la siguiente [...]: si el inicializador es una lista de ininit en arranque, el objeto está inicializado en listas (8.5.4).

Al considerar al candidato function, el compilador verá una lista de inicializador (que aún no tiene ningún tipo, ¡es solo una construcción gramatical!) Como el argumento, y un std::vector<std::string> como el parámetro de function. Para averiguar cuál es el costo de la conversión y si pueden convertirlos en contexto de sobrecarga, 13.3.3.1/5 dice

13.3.3.1.5/1:

Cuando un argumento es una lista de inicializador (8.5.4), no es una expresión y reglas especiales se aplican para convertirla en un tipo de parámetro.

13.3.3.1.5/3:

De lo contrario, si el parámetro es una resolución de clase X y sobrecarga no agregada por 13.3.1.7 elige un mejor constructor de X para realizar la inicialización de un objeto de tipo X de la lista de inicializador de argumentos, la secuencia de conversión implícita es un usuario- secuencia de conversión definida. Las conversiones definidas por el usuario están permitidas para la conversión de los elementos de la lista de inicializador a los tipos de parámetros del constructor, excepto como se indica en 13.3.3.1.

La clase no agregada X es std::vector<std::string>, y descubriré el mejor constructor mejor a continuación. La última regla nos otorga usar conversiones definidas por el usuario en casos como las siguientes:

struct A { A(std::string); A(A const&); };
void f(A);
int main() { f({"hello"}); }

Se nos permite convertir la cadena literal a std::string, incluso si esto necesita una conversión definida por el usuario. Sin embargo, apunta a las restricciones de otro párrafo. Que hace 13.3.3.1 ¿decir?

13.3.3.1/4, que es el párrafo responsable de prohibir las conversiones definidas por múltiples usuarios. Solo veremos las inicializaciones de la lista:

Sin embargo, al considerar el argumento de una función de conversión definida por el usuario [(o constructor)], eso es un candidato de [...] 13.3.1.7 al pasar la lista de inicializador como un solo argumento o cuando la lista de inicializador tiene exactamente un elemento y se considera una conversión a alguna clase X o referencia a (posiblemente CV-calificada) X para el primer parámetro de un constructor de X, o [...], solo se permiten secuencias de conversión estándar y secuencias de conversión de elipsis.

Tenga en cuenta que esta es una restricción importante: si no fuera por esto, lo anterior puede usar el copia constructor para establecer una secuencia de conversión igualmente bien, y la inicialización sería ambigua. (Observe la posible confusión de "A o B y C" en esa regla: está destinado a decir "(A o B) y C", por lo que estamos restringidos solamente Al intentar convertir por un constructor de x que tenga un parámetro de tipo X).

Somos delegados a 13.3.1.7 Para recolectar los constructores podemos usar para hacer esta conversión. Abordemos este párrafo desde el lado general a partir de 8.5 que nos delegó a 8.5.4:

8.5.4/1:

La inicialización de la lista puede ocurrir en contextos de inicialización directa o copia de inicio; Se llama a la inicialización de la lista en un contexto de inicialización directa Inicialización de la lista directa y se llama a la inicialización de la lista en un contexto de copia inicialización Inicialización de la lista de copias.

8.5.4/2:

Un constructor es un constructor inicializador Si su primer parámetro es de tipo std::initializer_list<E> o referencia a posiblemente CV-calificada std::initializer_list<E> Para algún tipo E, y no hay otros parámetros o de lo contrario todos los demás parámetros tienen argumentos predeterminados (8.3.6).

8.5.4/3:

La inicialización de la lista de un objeto o referencia del tipo T se define de la siguiente manera: [...] De lo contrario, si T es un tipo de clase, se consideran constructores. Si T tiene un constructor de listas inicializador, la lista de argumentos consiste en la lista de inicializador como un solo argumento; De lo contrario, la lista de argumentos consiste en los elementos de la lista de inicializador. Los constructores aplicables se enumeran (13.3.1.7) y el mejor se elige a través de la resolución de sobrecarga (13.3).

En este momento, T es el tipo de clase std::vector<std::string>. Tenemos un argumento (¡que aún no tiene un tipo! Estamos en el contexto de tener una lista de inicializador gramatical). Los constructores se enumeran a partir de 13.3.1.7:

...] Si t tiene un constructor de listas inicializador (8.5.4), la lista de argumentos consiste en la lista de inicializador como un solo argumento; De lo contrario, la lista de argumentos consiste en los elementos de la lista de inicializador. Para la inicialización de la lista de copias, las funciones candidatas son todos los constructores de T. Sin embargo, si se elige un constructor explícito, la inicialización no está formada.

Solo consideraremos la lista de inicializador de std::vector Como el único candidato, ya que ya sabemos que los demás no ganarán contra él o no se ajustan al argumento. Tiene la siguiente firma:

vector(initializer_list<std::string>, const Allocator& = Allocator());

Ahora, las reglas de convertir una lista de inicializador a un std::initializer_list<T> (para clasificar el costo del argumento/conversión de parámetros) se enumeran en 13.3.3.1.5:

Cuando un argumento es una lista de inicializador (8.5.4), no es una expresión y reglas especiales se aplican para convertirla en un tipo de parámetro. [...] Si el tipo de parámetro es std::initializer_list<X> Y todos los elementos de la lista de inicializador pueden convertirse implícitamente en x, la secuencia de conversión implícita es la peor conversión necesaria para convertir un elemento de la lista en X. Esta conversión puede ser una conversión definida por el usuario Incluso en el contexto de una llamada a un constructor inicializador de lista.

Ahora, la lista de inicializador se convertirá correctamente, y la secuencia de conversión es una conversión definida por el usuario (de char const[N] a std::string). Cómo se hace esto se detalla en 8.5.4 otra vez:

De lo contrario, si t es una especialización de std::initializer_list<E>, un objeto Initializer_List se construye como se describe a continuación y se utiliza para inicializar el objeto de acuerdo con las reglas para la inicialización de un objeto de una clase del mismo tipo (8.5). (...)

Ver 8.5.4/4 Cómo se hace este paso final :)

Otros consejos

Parece funcionar de esta manera:

function( {std::string("hello"), std::string("world"), std::string("test")} );

Quizás sea un error del compilador, pero tal vez esté pidiendo demasiadas conversiones implícitas.

De manera, no estoy seguro, pero sospecho que lo que está sucediendo aquí es que convertir a un inicializador_list es una conversión, y convertir eso a vector es otra conversión. Si ese es el caso, está excediendo el límite de una sola conversión implícita ...

Este es un error del compilador o su compilador no es compatible con STD :: Initializer_List. Probado en GCC 4.5.1 y se compila bien.

Tienes que especificar el tipo de su inicializador_list

function(std::initializer_list<std::string>{"hello", "world", "test"} );

Buena suerte

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top