2011-05-25 13 views
6
struct A {}; 
struct B 
{ 
    B (A* pA) {} 
    B& operator = (A* pA) { return *this; } 
}; 

template<typename T> 
struct Wrap 
{ 
    T *x; 
    operator T*() { return x; } 
}; 

int main() 
{ 
    Wrap<A> a; 
    B oB = a; // error: conversion from ‘Wrap<A>’ to non-scalar type ‘B’ requested 
    oB = a; // ok 
} 

Cuando oB se construye entonces por qué B::B(A*) no se invoca para Wrap<T>::operator T()? [Nota: B::operator = (A*) se invoca para Wrap<T>::operator T() en la siguiente instrucción]¿Por qué el constructor no se llama para un operador de conversión dado?

+0

¿Qué compilador? Compila bien en VS2010. –

+0

@Agnel Kurian, eso es porque VS2010 tiene un soporte parcial para C++ 0x. –

+0

@Agnel, es con g ++ 4.4.1. – iammilind

Respuesta

10

El problema es que el número de conversiones definidas por el usuario que se invoca de forma implícita se limita (a 1) por el estándar.

B ob = a; 

implica dos conversiones de usuario:

  • en a: Wrap<A>::operator A*() deberían ser llamados
  • en el resultado: B::B(A*) deberían ser llamados

la explicación de @ James Kanze: este la sintaxis se llama "inicialización de copia", efectivamente equivalente a B ob = B(a) (sin la copia). Esto es diferente de B ob(a) que es una "inicialización directa" y habría funcionado.

si califica explícitamente nada de esto, va a trabajar, por ejemplo:

B ob = B(a); 

Por otra parte, para el segundo caso no hay ningún problema:

ob = a; 

es corto mano para:

ob.operator=(a); 

Y, por lo tanto, solo se requiere una conversión definida por el usuario, lo que está permitido.

EDITAR:

ya que ha sido necesario en un comentario (a la respuesta de Kirill) podemos tener una pista sobre el motivo.

conversiones encadenados podrían ser largo, muy largo, y por lo tanto:

  • podría sorprender a los usuarios - conversiones implícitas ya que puede ser sorprendente, ya que es ...
  • podría conducir a una búsqueda exponencial de las posibilidades (para el compilador) - necesitaría ir desde ambos extremos, tratando de verificar todas las conversiones posibles, y de alguna manera "unir" las dos (con la ruta más corta posible).

Además, siempre que haya más de 1 conversión, correrá el riesgo de tener ciclos, que tendrían que detectarse (aunque el diagnóstico probablemente no sea necesario y estará sujeto a la Calidad de implementación))

Entonces, dado que es necesario un límite para evitar búsquedas infinitamente largas (podría haberse dejado sin especificar, con un mínimo requerido), y dado que más allá de 1 podemos tener nuevos problemas (ciclos), entonces 1 parece un límite como cualquiera después de todo.

+1

+1 Respuesta correcta. El estándar no permite la conversión encadenada. Permite la conversión de un usuario en una expresión. – Nawaz

+0

Esta respuesta es parcialmente correcta. El punto clave aquí es la semántica de "inicialización de copia", en oposición a "inicialización directa". –

2

Esto se debe a que C++ Standard solo permite una conversión definida por el usuario. Según § 12.3/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 .

B oB = a; // not tried: ob(a.operator T*()), 1 conversion func+1 constructor 
oB = a; // OK: oB.operator=(a.operator T*()), 1 conversion func+1 operator= 

Como solución alternativa se puede utilizar la forma explícita de la llamada al constructor:

B oB(a); // this requires only one implicit user-defined conversion 
+1

puede detallar un poco más. Especialmente cuál es el motivo detrás de no permitirlo. – iammilind

5

El estándar no permite la conversión implícita de . Si se le permitía, entonces se podría haber escrito como código:

struct A 
{ 
    A(int i) //enable implicit conversion from int to A 
}; 
struct B 
{ 
    B(const A & a); //enable implicit conversion from A to B 
}; 
struct C 
{ 
    C(const B & b); //enable implicit conversion from B to C 
}; 
C c = 10; //error 

No se puede esperar que 10 se convertirá en A que luego se convertirá en B que luego se convierte en C.


B b = 10; //error for same reason! 

A a = 10;  //okay, no chained implicit conversion! 
B ba = A(10); //okay, no chained implicit conversion! 
C cb = B(A(10)); //okay, no chained implicit conversion! 
C ca = A(10); //error, requires chained implicit conversion 

La misma regla se aplica para implícita conversión que invoca operator T() implícita.

Considere esto,

struct B {}; 

struct A 
{ 
    A(int i); //enable implicit conversion from int to A 
    operator B(); //enable implicit conversion from B to A 
}; 

struct C 
{ 
    C(const B & b); //enable implicit conversion from B to C 
}; 

C c = 10; //error 

No se puede esperar que 10 se convertirá en A que luego se convertirá en B (usando operator B()) que luego se convierte en C. S

Tales encadenadas implícitas conversiones no están permitidas. Tienes que hacer esto:

C cb = B(A(10); //okay. no chained implicit conversion! 
C ca = A(10); //error, requires chained implicit conversion 
7

Es porque estás utilizando "copia de inicialización". Si se escribe la declaración de oB :

B oB(a); 

, que debería funcionar. La semántica de las dos inicializaciones es diferente. Para B oB(a), el compilador intenta encontrar un constructor al que se puede llamar con los argumentos dados. En este caso, se puede llamar a B::B(A*) , porque hay una conversión implícita de Wrap<A> a A*. Para B oB = a, la semántica debe convertir implícitamente a a tipo B, luego utilice el constructor de copia de B para inicializar oB. (El copia real puede ser optimizado, pero la legalidad del programa es determinado como si no fuera.) Y no hay conversión implícita de Wrap<A>-B, sólo Wrap<A>-A*.

La asignación funciona, por supuesto, porque el operador de asignación también toma A*, por lo que la conversión implícita entra en juego.

+0

+1 buen punto planteado. Tenía la percepción de que 'B oB = a;' y 'B oB (a)' son lo mismo. – iammilind

+0

+1000. Bonito. No sabía la diferencia entre la semántica de las dos inicializaciones. – Nawaz

Cuestiones relacionadas