2012-10-01 12 views
5

¿Es legal este programa?¿Es posible invocar una función de conversión definida por el usuario a través de la inicialización de la lista?

struct X { X(const X &); }; 
struct Y { operator X() const; }; 

int main() { 
    X{Y{}}; // ?? error 
} 

Después n2672, y modificado por defect 978, 13.3.3.1 [over.best.ics] tiene:

4 - Sin embargo, al considerar el argumento de un constructor o el usuario función de conversión definida que es candidata [...] por 13.3.1.7 [...] cuando la lista de inicializadores tiene exactamente un elemento y una conversión a alguna clase X o referencia a (posiblemente cv-calificado) X se considera para el primer parámetro de un constructor de X [...], solo se consideran secuencias de conversión estándar y secuencias de conversión de puntos suspensivos.

Esto parece bastante perverso; que tiene como resultado que la especificación de una conversión usando un yeso lista de inicialización es ilegal:

void f(X); 
f(Y{});  // OK 
f(X{Y{}}); // ?? error 

Según entiendo n2640, lista de inicialización se supone que es capaz de sustituir todos los usos de directo de inicialización y el copia-inicialización, pero parece que no hay manera de construir un objeto de tipo X de un objeto de tipo Y utilizando sólo la lista de inicialización:

X x1(Y{}); // OK 
X x2 = Y{}; // OK 
X x3{Y{}}; // ?? error 

¿Es esta la intención real de la norma; si no, ¿cómo debería leerse o leerse?

+2

inicialización de lista _no_ se supone que es capaz de reemplazar todos los usos de la inicialización directa y la inicialización de la copia. Necesitamos encontrar a quien propague esta desinformación.:/ –

+0

Lo más confuso es que por el mismo pasaje, 'X x2 = Y {};' también debería ser ilegal, ya que cae bajo la parte 13.3.1.3 ("cuando se invoca para la copia/movimiento del temporal en el segundo paso de una copia de clase-inicialización "). Es probable que esté pasando algo más aquí. –

+0

@NicolBolas Creo que el primer paso en la inicialización de copia de clase sería usar el operador de conversión para obtener una X (temporal) y el segundo paso para copiar desde esa X. O al menos así es como lo leí. .. – fgp

Respuesta

2

La versión de clang 3.1 que se incluye con XCode 4.4 está de acuerdo con su interpretación y rechaza X{Y{}};. Al igual que yo, después de volver a leer las partes pertinentes de la norma unas cuantas veces, FWIW.

Si modifico el constructor X para tomar dos argumentos, ambos de tipo const X&, clang acepta la instrucción Y y; X{y,y}. (Se bloquea si intento X{Y{},Y{}} ...). Esto parece ser coherente con 13.3.3.1p4, que exige que las conversiones definidas por el usuario se omitan solo para el caso de un solo elemento.

Parece que la restricción de las secuencias de conversión estándar y de puntos suspensivos se ha añadido inicialmente solo en los casos en que ya ha tenido lugar otra conversión definida por el usuario. O al menos así es como leo http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#84.

Es interesante ver cómo el estándar es de cuidado de aplicar la restricción única al segundo paso de la copia de inicialización, que copia de un temporal que ya tiene el tipo correcto (y era obtener potencialmente a través de una conversión definida por el usuario!) . Sin embargo, para la lista de inicialización, no existe un mecanismo similar parece existe ...

+2

Suena como un defecto en la especificación. Mejor informarlo como tal. No hay ninguna razón por la cual 'X {Y {}, Y {}}' debería funcionar cuando 'X {Y {}}' no. –

+0

@NicolBolas He redactado un informe de defectos a continuación: http://stackoverflow.com/a/12732730/567292 – ecatmur

4

La intención original de 13.3.3.1p4 es describir cómo aplicar el requisito de que 12.3p4:

4 - A lo sumo una conversión definida por el usuario (constructor o función de conversión) se aplica implícitamente a un solo valor.

Antes defect 84, 13.3.3.1P4 era casi puramente informativo:

4 - En el contexto de una inicialización por conversión definida por el usuario (es decir, cuando se considera el argumento de una función de conversión definida por el usuario; ver 13.3.1.4 [over.match .copy], 13.3.1.5 [over.match.conv]), solo se permiten secuencias de conversión estándar y secuencias de conversión de puntos suspensivos.

Esto se debe a 13.3.1.4 párrafo 1 bala 2 y 13.3.1.5p1b1 restringir las funciones candidatas a los de la clase S rendimiento tipo T, donde S es el tipo de clase de la expresión de inicialización y T es el tipo de el objeto que se está inicializando, por lo que no hay latitud para que se inserte otra secuencia de conversión de conversión definida por el usuario. (13.3.1.4p1b1 es otro asunto, ver más abajo).

Defect 84 reparó el auto_ptr vacío (es decir auto_ptr<Derived> -> auto_ptr<Base> -> auto_ptr_ref<Base> -> auto_ptr<Base>, a través de dos funciones de conversión y un constructor de conversión) mediante la restricción de las secuencias de conversión permitida para el único parámetro del constructor en el segundo paso de la clase de copia de inicialización (aquí el constructor de auto_ptr<Base> teniendo auto_ptr_ref<Base>, no permitir el uso de una función de conversión para convertir su argumento de auto_ptr<Base>):

4 - Sin embargo, al considerar el argumento de una función de conversión definida por el usuario que es un candidato por 13.3.1.3 [sobre .match.ctor] cuando se invoca para copiar el temporal en el segundo paso o Inicialización de copias de clase fa, o por 13.3.1.4 [over.match.copy], 13.3.1.5 [over.match.conv], o 13.3.1.6 [over.match.ref] en todos los casos, solo secuencias de conversión estándar y se permiten secuencias de conversión de elipsis.

n2672 a continuación añade:

[...] por 13.3.1.7 [over.match.list] al pasar la lista de inicialización como un solo argumento o cuando la lista de inicialización tiene exactamente un elemento y una conversión a alguna clase X o referencia a (posiblemente cv-calificado) X se considera para el primer parámetro de un constructor de X, [...]

Esto es claramente confuso, ya que las únicas conversiones que son un candidato por 13.3.1.3 y 13.3.1.7 son constructores, no como diversión de conversión ctions. Defect 978 corrige esto:

4 - Sin embargo, al considerar el argumento de un constructor o función de conversión definida por el usuario [...]

Esto también hace 13.3.1.4p1b1 consistente con 12.3p4, ya que de lo contrario sería permitir la aplicación ilimitada de la conversión de los constructores de copia de inicialización:

struct S { S(int); }; 
struct T { T(S); }; 
void f(T); 
f(0); // copy-construct T by (convert int to S); error by 12.3p4 

la cuestión es entonces lo que el texto que se refiere a medios 13.3.1.7. X se está copiando o moviendo construido para que el lenguaje excluya la aplicación de una conversión definida por el usuario para llegar a su argumento X.std::initializer_list no tiene funciones de conversión, por lo que el lenguaje debe estar destinado a aplicarse a otra cosa; si no se pretende excluir funciones de conversión, se debe excluir la conversión de constructores:

struct R {}; 
struct S { S(R); }; 
struct T { T(const T &); T(S); }; 
void f(T); 
void g(R r) { 
    f({r}); 
} 

Hay dos constructores disponibles para la lista de inicialización; T::T(const T &) y T::T(S). Al excluir al constructor de copia de la consideración (ya que su argumento debería convertirse a través de una secuencia de conversión definida por el usuario) nos aseguramos de que solo se considere el constructor T::T(S) correcto. En ausencia de este lenguaje, la inicialización de la lista sería ambigua. Pasando la lista de inicialización como un solo argumento funciona de manera similar:

struct U { U(std::initializer_list<int>); }; 
struct V { V(const V &); V(U); }; 
void h(V); 
h({{1, 2, 3}}); 

Editar: y después de haber pasado por todo eso, me he encontrado por a discussionJohannes Schaub que confirma este análisis:

Con ello se pretende que factor fuera del constructor de copia para la inicialización de lista porque como estamos autorizados a usar conversiones anidadas definidas por el usuario, siempre podríamos generar una segunda ruta de conversión ambigua invocando primero el constructor de copia y luego haciendo lo mismo que hicimos con el otro conversiones.


bien, fuera a presentar un informe por defecto. Voy a proponer de separarse 13.3.3.1p4:

4 - Sin embargo, al considerar el argumento de un constructor o función de conversión definida por el usuario que es un candidato:

  • 13,3. 1.3 [over.match.ctor] cuando se invoca para la copia del temporal en el segundo paso de una copia de clase-inicialización, o
  • por 13.3.1.4 [over.match.copy], 13.3.1.5 [over.match .conv], o 13.3.1.6 [over.match.ref] en todos los casos,

solo se consideran secuencias de conversión estándar y secuencias de conversión de elipsis; cuando se considera el primer argumento de un constructor de una clase X que es candidato por 13.3.1.7 [over.match.list] al pasar la lista de inicializadores como un único argumento o cuando la lista de inicializadores tiene exactamente un elemento, un usuario definido conversión a X o referencia a (posiblemente cv -calificada) X solo se considera si su conversión definida por el usuario se especifica mediante una función de conversión. [Nota: porque se permite más de una conversión definida por el usuario en una secuencia de conversión implícita en el contexto de inicialización de lista, esta restricción es necesaria para garantizar que un constructor de conversión de X, llamado con un solo argumento a que no sea de tipo X o un tipo derivado de X, no es ambiguo contra un constructor de X llamado con un objeto temporal X construido a partir del a. - fin nota]

+0

Me gusta cómo todavía permite las funciones de conversión. Hace 'struct A {A (int); }; struct B {operador A(); operador int(); }; B b; A a {b}; 'ambiguo en lugar de elegir el ctor' A (int) '(como lo hace actualmente). El comportamiento actual parece sorprendente. –

Cuestiones relacionadas