12.6.2. Conversión por operador

La segunda forma de producir conversiones automáticas de tipo es a través de la sobrecarga de operadores. Puede crear un método que tome el tipo actual y lo convierta al tipo deseado usando la palabra reservada operator seguida del tipo al que quiere convertir. Esta forma de sobrecarga de operadores es única porque parece que no se especifica un tipo de retorno -- el tipo de retorno es el nombre del operador que está sobrecargando. He aquí un ejemplo:

//: C12:OperatorOverloadingConversion.cpp
class Three {
  int i;
public:
  Three(int ii = 0, int = 0) : i(ii) {}
};

class Four {
  int x;
public:
  Four(int xx) : x(xx) {}
  operator Three() const { return Three(x); }
};

void g(Three) {}

int main() {
  Four four(1);
  g(four);
  g(1);  // Calls Three(1,0)
} ///:~

Listado 12.20. C12/OperatorOverloadingConversion.cpp


Con la técnica del constructor, la clase destino realiza la conversión, pero con los operadores, la realiza la clase origen. El valor de la técnica del constructor es que puede añadir una nueva ruta de conversión a un sistema existente al crear una nueva clase. Sin embargo, creando un constructor con un único argumento siempre define una conversión automática de tipos (incluso si requiere más de un argumento si el resto de los argumentos tiene un valor por defecto), que puede no ser lo que desea (en cuyo caso puede desactivarlo usando explicit). Además, no hay ninguna forma de usar una conversión por constructor desde un tipo definido por el usuario a un tipo incorporado; eso sólo es posible con la sobrecarga de operadores.

Reflexividad

Una de las razones más convenientes para usar operadores sobrecargados globales en lugar de operadores miembros es que en la versión global, la conversión automática de tipos puede aplicarse a cualquiera de los operandos, mientras que con objetos miembro, el operando de la parte izquierda debe ser del tipo apropiado. Si quiere que ambos operandos sean convertidos, la versión global puede ahorrar un montón de código. He aquí un pequeño ejemplo:

//: C12:ReflexivityInOverloading.cpp
class Number {
  int i;
public:
  Number(int ii = 0) : i(ii) {}
  const Number
  operator+(const Number& n) const {
    return Number(i + n.i);
  }
  friend const Number
    operator-(const Number&, const Number&);
};

const Number
  operator-(const Number& n1,
            const Number& n2) {
    return Number(n1.i - n2.i);
}

int main() {
  Number a(47), b(11);
  a + b; // OK
  a + 1; // 2nd arg converted to Number
//! 1 + a; // Wrong! 1st arg not of type Number
  a - b; // OK
  a - 1; // 2nd arg converted to Number
  1 - a; // 1st arg converted to Number
} ///:~

Listado 12.21. C12/ReflexivityInOverloading.cpp


La clase Number tiene tanto un miembro operator+ como un friend operator-. Dado que hay un constructor que acepta un argumento int simple, se puede convertir un int automáticamente a Number, pero sólo bajo las condiciones adecuadas. En main(), puede ver que sumar un Number a otro Number funciona bien dado que tiene una correspondencia exacta con el operador sobrecargado. Además, cuando el compilador ve un Number seguido de un + y de un int, puede hacer la correspondencia al método Number::operator+ y convertir el argumento int an Number usando el constructor. Pero cuando ve un int, un + y un Number, no sabe qué hacer porque todo lo que tiene es Number::operator+ que requiere que el operando de la izquierda sea ya un objeto Number. Así que, el compilador genera un error.

Con friend operator- las cosas son diferentes. El compilador necesita rellenar ambos argumentos como quiera; no está restringido a tener un Number como argumento de la parte izquierda. así que si ve:

1 - a

puede convertir el primer argumento a Number usando el constructor.

A veces querrá ser capaz de restringir el uso de sus operadores haciéndolos métodos. Por ejemplo, cuando multiplique una matriz por un vector, el vector debe ir a la derecha. Pero si quiere que sus operadores sean capaces de convertir cualquier argumento, haga el operador una función friend.

Afortunadamente, el compilador cogerá la expresión 1-1 y convertirá ambos argumentos a objetos Number y después llamará a operator-. Eso significaría que el código C existente podría empezar a funcionar de forma diferente. El compilador intenta primero la correspondencia «más simple», es decir, en este caso el operador incorporado para la expresión 1-1.