std::lista de inicializadores como argumento de función


Por alguna razón pensé que C++0x permitía std::initializer_list como argumento de función para 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 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"} );
}
Author: Johannes Schaub - litb, 2010-03-01

5 answers

GCC tiene un error. La Norma lo hace válido. Véase:

Observe que hay dos lados de esto{[43]]}

  • ¿Cómo y qué inicialización se hace en general?
  • ¿Cómo se utiliza 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 se produce en la forma

T x = a;

Así como en el paso de argumentos, la función return, lanzar una excepción (15.1), manejar una excepción (15.3), y la inicialización de miembro agregado (8.5.1) se llama inicialización de copia.
.
.
La semántica de los inicializadores es la siguiente[...]: Si el inicializador es una lista de inicio entre corchetes, el objeto es inicializado por la lista (8.5.4).

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

13.3.3.1.5/1:

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

13.3.3.1.5/3:

De lo contrario, si el parámetro es una clase X no agregada y la resolución de sobrecarga según 13.3.1.7 elige un único mejor constructor de X para realizar la inicialización de un objeto de tipo X de la lista inicializador de argumentos, la secuencia de conversión implícita es una secuencia de conversión definida por el usuario. Se permiten conversiones definidas por el usuario para la conversión de los elementos de la lista inicializador a los tipos de parámetro constructor salvo lo indicado en 13.3.3.1.

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

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

Se nos permite convertir el literal de cadena a std::string, incluso si esto necesita una conversión definida por el usuario. Sin embargo, señala las restricciones de otro párrafo. ¿Qué dice 13.3.3.1?

13.3.3.1/4, que es el párrafo responsable de prohibir múltiples conversiones definidas por el usuario. 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)] que es un candidato por [...] 13.3.1.7 cuando se pasa la lista del inicializador como un solo argumento o cuando la lista del inicializador tiene exactamente un elemento y una conversión a alguna clase X o referencia a (posiblemente calificada cv) X se considera 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 puntos suspensivos.

Observe que esta es una restricción importante: Si no fuera por esto, los anteriores pueden usar el constructor de copia para establecer una secuencia de conversión igualmente bien, y la inicialización sería ambigua. (observe la confusión potencial de "A o B y C" en esa regla: Significa decir" (A o B) y C " - así que estamos restringidos solo cuando tratamos de convertir por un constructor de X que tiene una parámetro de tipo X).

Estamos delegados a 13.3.1.7 para recopilar los constructores que podemos usar para hacer esta conversión. Vamos a abordar 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 lista puede ocurrir en contextos de inicialización directa o de inicialización de copia; la inicialización de lista en un contexto de inicialización directa se llama direct-list-initialization y un contexto de inicialización de copia se llama copy-list-initialization.

8.5.4/2:

Un constructor es un inicializador-constructor de lista si su primer parámetro es de tipo std::initializer_list<E> o referencia a posiblemente cv-calificado std::initializer_list<E> para algún tipo E, y o bien no hay otros parámetros o bien todos los demás parámetros tienen argumentos por defecto (8.3.6).

8.5.4/3:

List-inicialización de un objeto o referencia de tipo T se define como sigue: [...] De lo contrario, si T es un tipo de clase, se consideran constructores. Si T tiene un constructor initializer-list, la lista de argumentos consiste en la lista inicializador como un solo argumento; de lo contrario, la lista de argumentos consiste en los elementos de la lista inicializador. Se enumeran los constructores aplicables (13.3.1.7) y se elige el mejor 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 no tiene un tipo todavía! Estamos en el contexto de tener una lista de inicializadores gramaticales). Los constructores se enumeran como de 13.3.1.7:

[...] Si T tiene un constructor initializer-list (8.5.4), la lista de argumentos consiste en la lista inicializer como un solo argumento; de lo contrario, la lista de argumentos consiste en los elementos de la lista inicializer. Para la inicialización de copy-list, las funciones candidatas son todos los constructores de T. Sin embargo, si un constructor explícito es elegido, la inicialización está mal formada.

Solo consideraremos la lista inicializadora de std::vector como el único candidato, ya que sabemos que los demás no ganarán contra ella o no encajarán en el argumento. Lleva la firma siguiente:

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

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

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

Ahora, la lista inicializadora será exitosa convertido, 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 de nuevo:

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 realiza este paso final:)

 54
Author: Johannes Schaub - litb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-03 16:17:26

Parece que funciona de esta manera:

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

Tal vez es un error del compilador, pero tal vez usted está pidiendo demasiadas conversiones implícitas.

 3
Author: UncleBens,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-01 18:15:57

De repente, no estoy seguro, pero sospecho que lo que está pasando aquí es que convertir a una initializer_list es una conversión, y convertir eso a vector es otra conversión. Si ese es el caso, estás excediendo el límite de solo una conversión implícita...

 2
Author: Jerry Coffin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-01 17:02:55

Esto es un error del compilador o su compilador no soporta std::initializer_list. Probado en GCC 4.5.1 y compila bien.

 1
Author: Ricky65,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-13 12:52:21

Debe especificar el tipo de su inicial_list

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

Buena suerte

 1
Author: fnc12,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-07 17:30:55