Introducción a la programación genérica (IV)

Artículos de la serie:

Sobrecarga de operadores


Llegados a este punto, desearíamos habilitar las operaciones aritméticas básicas de los números complejos (suma, diferencia, producto de números complejos y producto por escalares reales). En primer lugar, sobrecargaremos los operadores binarios de asignación compuesta +=, -= y *= en la interfaz pública de la clase. Estos operadores permitirán calcular la suma, diferencia o multiplicación de un objeto dado (considerado de manera implícita como operando izquierdo) con otro número real o complejo (operando derecho), almacenándose el resultado en el operando izquierdo. Así, por ejemplo, la expresión x+=y resultará ser equivalente a x=x+y. Emplearemos el operador operator* para denotar tanto el producto por números complejos como el producto por escalares reales:

   template<typename T>       requires std::floating_point<T>    class Complex {       // ...    public:       // ...       constexpr autooperator+=(Complex const& z) noexcept       {          re_ += z.re_;          im_ += z.im_;          return *this;       }       constexpr autooperator+=(T t) noexcept       {          re_ += t; // im_ permanece inalterado          return *this;       }       constexpr autooperator-=(Complex const& z) noexcept       {          re_ -= z.re_;          im_ -= z.im_;          return *this;       }       constexpr autooperator-=(T t) noexcept       {          re_ -= t; // im_ permanece inalterado          return *this;       }       constexpr autooperator*=(Complex const& z) noexcept       {          T const r = re_*z.re_ - im_*z.im_;          im_ = re_*z.im_ + im_*z.re_;          re_ = r;          return *this;       }       constexpr autooperator*=(T t) noexcept       {          re_ *= t;          im_ *= t;          return *this;       }    };

Una asignación del tipo a+=b es, en este caso, equivalente a a.operator+=(b). Como es usual, las funciones miembro anteriores retornan una referencia al propio objeto asignado con el fin de garantizar la asociatividad de izquierda a derecha de los operadores, permitiéndose así la concatenación de asignaciones del tipo a+=b+=c (equivalente a a+=(b+=c)). Se permiten también operaciones de auto-asignación del tipo a*=a. A modo de ejemplo del funcionamiento de las funciones anteriores, se proporciona el código siguiente:

   auto x = Complex<double>{2.03.0};    auto y = Complex<double>{1.04.0};    x *= y; // ahora x = (-10,11)    y += x; // ahora y = (-9,15)

Por su parte, los operadores aritméticos usuales de suma, diferencia y producto deben ser implementados como funciones externas a la plantilla, y no como funciones miembro como en el caso de los operadores de asignación compuesta, con el fin de que tanto objetos de tipo Complex<T> como escalares de tipo T puedan ser aceptados como primer operando. En efecto, una función miembro de signatura

   constexpr auto Complex::operator+(Complex const&) noexcept-> Complex&;

permitiría expresiones de la forma x+y (equivalente a x.operator+(y)), pero imposibilitaría aquellas otras de la forma 5.0+x, dado que el primer operando (un simple escalar) no es de tipo Complex<T> y, por tanto, la llamada al operador produciría un error de compilación. Se proporcionan, a continuación, las funciones relativas a la suma de números complejos, parametrizadas también con un tipo escalar genérico T al igual que la plantilla de clase Complex<T>. La implementación de las funciones correspondientes a la diferencia y el producto constituye una mera adaptación de código que dejaremos como ejercicio al lector:

   template<typename T>    constexpr auto operator+(Complex<T> const& x, Complex<T> const& y) noexcept    {       auto r = x;       r += y;       return r;    }    template<typename T>    constexpr auto operator+(Complex<T> const& x, T y) noexcept    {       auto r = x;       r += y;       return r;    }    template<typename T>    constexpr auto operator+(T x, Complex<T> const& y) noexcept    {       auto r = y;       r += x;       return r;    }    // etc

A modo de ejemplo:

   auto x = Complex<double>{2.03.0};    auto y = Complex<double>{1.04.0};    auto z = 1.0 + x*(2.0 + 3.0*y); // z = (-25,39) de tipo Complex<double>

Tan sólo resta sobrecargar los operadores de relación con el fin de poder determinar si dos números complejos (o un número complejo y un escalar real) son idénticos:

   template<typename T>    constexpr auto operator==(Complex<T> const& x, Complex<T> const& y) noexcept -> bool    {       return x.real() == y.real() and x.imag() == y.imag();    }    template<typename T>    constexpr auto operator==(Complex<T> const& x, T t) noexcept -> bool    {       return x.real() == t and x.imag() == T{};    }    template<typename T>    constexpr auto operator==(T t, Complex<T> const& y) noexcept -> bool    {       return t == y.real() and T{} == y.imag();    }    // código similar para operator!= ...

Así, por ejemplo: 

   auto x = Complex<double>{2.03.0};    auto y = Complex<double>{1.04.0};    std::cout << std::boolalpha << (x == y); // output: false

Comprobamos, pues, que la sobrecarga de operadores en el lenguaje C++ es una herramienta de enorme utilidad que permite operar con los números complejos a través de una sintaxis natural (también empleada con los tipos numéricos primitivos como int o double) análoga a la encontrada en los textos matemáticos.


Puedes acceder al siguiente artículo de la serie a través de este enlace.

No hay comentarios:

Publicar un comentario