¿Por qué se prefiere el constructor de lista std::initializer cuando se usa una lista inicializer entre corchetes?


Considere el código

#include <iostream>

class Foo
{
    int val_;
public:
    Foo(std::initializer_list<Foo> il)
    {
        std::cout << "initializer_list ctor" << std::endl;
    }
    /* explicit */ Foo(int val): val_(val)
    {
        std::cout << "ctor" << std::endl;
    };
};

int main(int argc, char const *argv[])
{
    // why is the initializer_list ctor invoked?
    Foo foo {10}; 
}

La salida es

ctor
initializer_list ctor

Por lo que entiendo, el valor 10 se convierte implícitamente en una Foo (primera salida ctor), luego el constructor inicializador se activa (segunda salida initializer_list ctor). Mi pregunta es ¿por qué sucede esto? ¿No es el constructor estándar Foo(int) una mejor coincidencia? Es decir, hubiera esperado que la salida de este fragmento fuera solo ctor.

PD: Si marca el constructor Foo(int) como explicit, entonces Foo(int) es el único constructor invocado, ya que el entero 10 ahora no se puede convertir implícitamente a un Foo.

Author: Rakete1111, 2014-11-26

3 answers

§13.3.1.7 [cambio.coincidir.list] / p1:

Cuando los objetos de tipo de clase no agregado T se inicializan en la lista (8.5.4), la resolución de sobrecarga selecciona el constructor en dos fases:

  • Inicialmente, las funciones candidatas son los constructores inicializador-lista (8.5.4) de la clase T y la lista de argumentos consiste en la lista inicializador como un solo argumento.
  • Si no se encuentra un constructor de lista inicializador viable, se realiza la resolución de sobrecarga una vez más, donde las funciones candidatas son todas los constructores de la clase T y la lista de argumentos consiste en los elementos de la lista inicializador.

Si la lista inicializadora no tiene elementos y T tiene un valor predeterminado constructor, se omite la primera fase. En copy-list-initialization, si se elige un constructor explicit, la inicialización es mal formado.

Mientras haya un constructor de lista inicializador viable, triunfará sobre todos non-initializer-list constructors cuando se usa la inicialización de lista y la lista del inicializador tiene al menos un elemento.

 23
Author: T.C.,
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-11-26 08:25:11

La propuesta n2100 para listas inicializadoras entra en gran detalle sobre la decisión de hacer que los constructores de secuencia (lo que ellos llaman constructores que toman std::initializer_lists) tengan prioridad sobre los constructores regulares. Véase el apéndice B para un análisis detallado. Se resume sucintamente en la conclusión:

11.4 Conclusión

Entonces, ¿cómo decidimos entre las dos alternativas restantes ("ambiguity" y " sequence constructors take priority sobre ordinario constructores)? Nuestra propuesta da constructores de secuencia prioridad porque

  • Buscar ambigüedades entre todos los constructores conduce a demasiados "falsos positivos"; es decir, choques entre aparentemente no relacionados constructor. Vea los ejemplos a continuación.
  • La desambiguación es propensa a errores (así como detallada). Ver ejemplos en §11.3.
  • Usar exactamente la misma sintaxis para cada número de elementos de una lista homogénea es importante-la desambiguación debe ser hecho para constructores ordinarios (que no tienen un patrón regular de argumento). Ver ejemplos en §11.3. El ejemplo más simple de un falso positive es el constructor por defecto:

El ejemplo más simple de un falso positivo es el constructor por defecto:

vector<int> v; 
vector<int> v { }; // potentially ambiguous
void f(vector<int>&); 
// ...
f({ }); // potentially ambiguous

Es posible pensar en clases donde la inicialización con no miembros es semánticamente distinto de la inicialización por defecto, pero no complicaría el lenguaje para proporcionar un mejor soporte para aquellos casos que para el caso más común donde son semánticamente el igual.

Dar prioridad a los constructores de secuencia rompe la comprobación de argumentos en trozos más comprensibles y da mejor localidad.

void f(const vector<double>&);
// ...
struct X { X(int); /* ... */ };
void f(X);
// ...
f(1);     // call f(X); vector’s constructor is explicit
f({1});   // potentially ambiguous: X or vector?
f({1,2}); // potentially ambiguous: 1 or 2 elements of vector

Aquí, dar prioridad a los constructores de secuencias elimina la interferencia de X. Picking X para f(1) es una variante del problema con explícito se muestra en §3.3.

 13
Author: ,
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-11-26 08:34:17

Todo lo de la lista inicializadora estaba destinado a habilitar la inicialización de la lista de la siguiente manera:

std::vector<int> v { 0, 1, 2 };

Considere el caso

std::vector<int> v { 123 };

Se pretende que esto inicialice el vector con un elemento de valor 123 en lugar de 123 elementos de valor cero.

Para acceder al otro constructor, use la sintaxis antigua

Foo foo(10);
 5
Author: seldon,
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-11-26 08:12:56