Dado que el compilador debe decidir cómo realizar silenciosamente una conversión de tipos, puede meterse en problemas si el programador no diseña las conversiones correctamente. Una situación simple y obvia se da con una clase X que puede convertirse a sí misma en un objeto de la clase Y mediante un operator Y(). Si la clase Y tiene un constructor que toma un único argumento de tipo X, esto representa la misma conversión de tipos. El compilador ahora tiene dos formas de ir de X a Y, así que generará un error de ambigüedad cuando se produzca esa conversión:
//: C12:TypeConversionAmbiguity.cpp class Orange; // Class declaration class Apple { public: operator Orange() const; // Convert Apple to Orange }; class Orange { public: Orange(Apple); // Convert Apple to Orange }; void f(Orange) {} int main() { Apple a; //! f(a); // Error: ambiguous conversion } ///:~
Listado 12.24. C12/TypeConversionAmbiguity.cpp
La solución obvia a este problema es no hacerlo. Simplemente proporcione una ruta única para la conversión automática de un tipo a otro.
Un problema más difícil de detectar sucede cuando proporciona conversiones automáticas a más de un tipo. Esto se llama a veces abanico de salida (fan-out):
//: C12:TypeConversionFanout.cpp class Orange {}; class Pear {}; class Apple { public: operator Orange() const; operator Pear() const; }; // Overloaded eat(): void eat(Orange); void eat(Pear); int main() { Apple c; //! eat(c); // Error: Apple -> Orange or Apple -> Pear ??? } ///:~
Listado 12.25. C12/TypeConversionFanout.cpp
La clase Apple tiene conversiones automáticas a Orange y a Pear. Lo insidioso de esto es que no hay ningún problema hasta que alguien llega inocentemente y crea dos versiones sobrecargadas de eat(). (Con una única versión el código en main() funciona correctamente).
De nuevo la solución -- y el lema general de la conversión automática de tipos -- es proporcionar solo una conversión automática de un tipo a otro. Puede tener conversiones a otros tipos, sólo que no deberían ser automáticas. Puede crear llamadas a funciones explícitas con nombres como makeA() y makeB().
La conversión automática de tipos puede producir más actividad subyacente de la que se podría esperar. Mire esta modificación de CopyingVsInitialization.cpp como un pequeño rompecabezas:
//: C12:CopyingVsInitialization2.cpp class Fi {}; class Fee { public: Fee(int) {} Fee(const Fi&) {} }; class Fo { int i; public: Fo(int x = 0) : i(x) {} operator Fee() const { return Fee(i); } }; int main() { Fo fo; Fee fee = fo; } ///:~
Listado 12.26. C12/CopyingVsInitialization2.cpp
No hay un constructor para crear Fee fee de un objeto Fo. Sin embargo, Fo tiene una conversión automática de tipos a Fee. No hay un constructor de copia para crear un Fee a partir de un Fee, pero ésa es una de las funciones especiales que el compilador puede crear. (El constructor por defecto, el constructor de copia, operator= y el destructor pueden ser sintetizados automáticamente por el compilador). Así que, para la relativamente inocua sentencia:
Fee fee = fo;
se invoca el operador de conversión automática de tipos, y se crea un constructor de copia.
Use la conversión automática de tipos con precaución. Como con toda la sobrecarga de operadores, es excelente cuando reduce la tarea de codificación significativamente, pero normalmente no vale la pena usarla de forma gratuita.