2010-10-08 24 views
6

La última pregunta que hice fue algo con lo que tropecé al tratar de comprender otra cosa ... que tampoco puedo entender (no es mi día).No ocurre la conversión implícita

Esta es una pregunta bastante larga, pero al menos espero que esta pregunta pueda ser útil para muchas personas y no solo para mí.

El código que tengo es la siguiente:

template <typename T> class V; 
template <typename T> class S; 

template <typename T> 
class V 
{ 
public: 
T x; 

explicit V(const T & _x) 
:x(_x){} 

V(const S<T> & s) 
:x(s.x){} 
}; 

template <typename T> 
class S 
{ 
public: 
T &x; 

explicit S(V<T> & v) 
:x(v.x) 
{} 
}; 

template <typename T> 
V<T> operator+(const V<T> & a, const V<T> & b) 
{ 
return V<T>(a.x + b.x); 
} 

int main() 
{ 
V<float> a(1); 
V<float> b(2); 
S<float> c(b); 

b = a + V<float>(c); // 1 -- compiles 
b = a + c;   // 2 -- fails 
b = c;    // 3 -- compiles 

return 0; 
} 

expresiones 1 y 3 funcionan a la perfección, mientras que la expresión 2 no compila.

Si he entendido bien, lo que sucede es:

Expresión 1

  1. c se se convierte implícitamente a const mediante el uso de una secuencia de conversión estándar (que consiste en un solo calificación conversión).
  2. V<float>(const S<T> & s) se llama y un objeto temporal const V<float> generado (llamémoslo t). Ya es const-calificado porque es un valor temporal.
  3. a se convierte en const de forma similar a c.
  4. operator+(const V<float> & a, const V<float> & b) se llama, resultando en un temporal de tipo const V<float> que podemos llamar q.
  5. se llama al valor predeterminado V<float>::operator=(const & V<float>).

¿Estoy bien hasta aquí? Si hice incluso el más sutil error por favor, hágamelo saber, porque yo estoy tratando de obtener una comprensión acerca de la conversión implícita lo más profundo posible ...

Expresión 3

  1. c es convertido a V<float>. Para eso, tenemos una secuencia de conversión definida por el usuario:
    1.1. primera conversión estándar: S<float> a const S<float> a través de la conversión de calificación.
    1.2. Conversión definida por el usuario: const S<float> a V<float> a través del constructor V<float>(const S<T> & s).
    1.3 segundos de conversión estándar: V<float> a const V<float> a través de la conversión de calificación.
  2. se llama al valor predeterminado V<float>::operator=(const & V<float>).

Expression 2?

Lo que no entiendo es por qué hay un problema con la segunda expresión. ¿Por qué la siguiente secuencia no es posible?

  1. c se convierte en V<float>. Para eso, tenemos una secuencia de conversión definida por el usuario:
    1.1. Conversión estándar inicial: S<float> a const S<float> a través de la conversión de calificación.
    1.2. Conversión definida por el usuario: const S<float> a V<float> a través del constructor V<float>(const S<T> & s).
    1.3. conversión estándar final: V<float> a const V<float> a través de la conversión de calificación.
  2. Pasos 2 a 6 son los mismos que en el caso de la expresión 1.

Después de leer el C++ estándar pensé: '¡Hey! tal vez el problema tiene que ver con 13.3.3.1.2.3! ' que establece:

If the user-defined conversion is specified by a template conversion function, the second standard conversion sequence must have exact match rank.

Pero eso no puede ser el caso ya que la conversión de calificación tiene rango de concordancia exacta ...

Realmente no tengo ni idea ...

Bueno, si usted tiene la respuesta o no, gracias por leer hasta aquí :)

Respuesta

9

Como señaló Edric, las conversiones no se consideran durante la deducción del argumento de la plantilla. Aquí, usted tiene dos contextos donde el parámetro de plantilla T puede deducirse a partir del tipo de los argumentos:

template<class T> 
v<T> operator+(V<T> const&, V<T> const&); 
       ~~~~~~~~~~~ ~~~~~~~~~~~~ 

Pero intenta invocar esta plantilla de función con un V<float> en el lado izquierdo y una S en el lado derecho. La deducción del argumento de plantilla da como resultado T = flotante para el lado izquierdo y obtendrá un error para el lado derecho porque no hay T para que V<T> sea igual a . Esto califica como una falla de deducción de argumento de plantilla y la plantilla simplemente se ignora.

Si desea permitir conversiones, su operador + no debe ser una plantilla. No es el siguiente truco: Se puede definir como un amigo en línea dentro de la plantilla de clase para V:

template<class T> 
class V 
{ 
public: 
    V(); 
    V(S<T> const&); // <-- note: no explicit keyword here 

    friend V<T> operator+(V<T> const& lhs, V<T> const& rhs) { 
     ... 
    } 
}; 

De esta manera, el operador no es una plantilla más.Por lo tanto, no es necesario deducir el argumento de la plantilla y su invocación debería funcionar. El operador se encuentra a través de ADL (búsqueda dependiente del argumento) porque el lado izquierdo es V<float>. El lado derecho se convierte correctamente a V<float> también.

También es posible deshabilitar la deducción de argumentos de la plantilla para un argumento específico. Por ejemplo:

template<class T> 
struct id {typedef T type;}; 

template<class T> 
T clip(
    typename id<T>::type min, 
    T value, 
    typename id<T>::type max) 
{ 
    if (value<min) value=min; 
    if (value>max) value=max; 
    return value; 
} 

int main() { 
    double x = 3.14; 
    double y = clip(1,x,3); // works, T=double 
} 

pesar de que el tipo del primer y último argumento es un entero, que no se consideran durante la plantilla de la deducción argumento porque id<T>::type no es una llamada context` * deducible. Entonces, T solo se deduce de acuerdo con el segundo argumento, lo que resulta en T = doble sin contradicciones.

+0

¡Muchas gracias! Estuve mirando el problema desde el principio. Muchas gracias también por el truco del "operador amigo", creo que eso es lo que haré :) – jmeseguerdepaz

+0

gracias, bien. ¡Otra prueba de que C++ puede ser realmente feo y malo! – Atmocreations

+0

Puede usar 'std :: identity' en' 'en lugar de' id' – David

0

Sólo una conjetura, pero tal vez el compilador no puede distinguir entre la conversión de V-> S o de S> V al tratar de encontrar la manera de poner a + c en la expresión 2. Estás asumiendo que el compilador será inteligente eno ugh para elegir el que permite que la compilación continúe debido al resto de las funciones disponibles, pero el compilador probablemente no está "leyendo por adelantado" (por así decirlo), y se está confundiendo con la ambigüedad de la conversión ascendente antes de intentarlo para encontrar el operador '+'.

Por supuesto, si se ha añadido el error de compilación, que podría ayudar a aclarar el problema también ...

+0

Bueno, según el estándar, el operador + es una función candidata porque tiene el número correcto de argumentos. Entonces el compilador debería poder rastrear la conversión que acabo de escribir ... o eso entendí. En cuanto al mensaje de error: ../main.cpp:42: error: no hay coincidencia para 'operator +' en 'a + c' (lo siento, no sé cómo poner nuevas líneas aquí) – jmeseguerdepaz

+0

Como dije, eso requeriría que el compilador "lea adelante" y convierta c usando el constructor V, antes de que sea capaz de coincidir con el operador personalizado + especificado. Como no hay un operador de coincidencia directa, y no hay una conversión ascendente definitiva (que podría ser intrínseca, no segura), el compilador se da por vencido. Esa es mi suposición sobre la lógica del compilador, de todos modos. – Nick

+0

Aclaración: no tiene un operador candidato, porque su operador no puede crear instancias de plantilla para que coincida con los tipos que se agregan sin invocar operadores de conversión personalizados. Esa es mi comprensión. – Nick

4

Al considerar correspondencias del molde, no se utilizan las conversiones implícitas. Por lo tanto, en el siguiente ejemplo:

template < typename T > 
void foo(T t1, T t2) { /* do stuff */ } 

int main(int argc, char ** argv) { 
    foo(1, 1.0); 
    return 0; 
} 

Eso no va a compilar a pesar de que alguno de los argumentos se podría convertir implícitamente al otro tipo (int < -> doble).

+0

Tiene razón en que el código que proporciona no funciona. ¿Pero por qué? No veo una razón en el estándar, y en realidad http://accu.org/index.php/articles/268 dice "n la mayoría de los casos, una plantilla de función se comporta como una función normal al considerar la resolución de sobrecarga". Y finalmente tenemos lo que sucede con la conversión implícita en la expresión "b = c" – jmeseguerdepaz

+0

Por cierto, la razón por la que su código no funciona es un problema de instanciación, no de conversión implícita. Si llama a "foo" con foo (1, 1.0), no hay problemas para compilar. – jmeseguerdepaz

+0

Oh, después de leer la respuesta de Sellibitze, me doy cuenta de que probablemente estabas tratando de decirme eso ... No lo entendí, lo siento. – jmeseguerdepaz

Cuestiones relacionadas