Pregunta

Tengo una clase que encapsula algo de aritmética, digamos cálculos de punto fijo. Me gusta la idea de sobrecargar operadores aritméticos, así que escribo lo siguiente:

class CFixed
{
   CFixed( int   );
   CFixed( float );
};

CFixed operator* ( const CFixed& a, const CFixed& b )
{ ... }

Todo funciona. Puedo escribir 3 * CFixed (0) y CFixed (3) * 10.0f. Pero ahora me doy cuenta, puedo implementar el operador * con un operando entero mucho más efectivo. Entonces lo sobrecargo:

CFixed operator* ( const CFixed& a, int b )
{ ... }
CFixed operator* ( int a, const CFixed& b )
{ ... }

Todavía funciona, pero ahora CFixed (0) * 10.0f llama a la versión sobrecargada, convirtiendo float en int (y esperaba que convirtiera float en CFixed). Por supuesto, también puedo sobrecargar una versión flotante, pero me parece una explosión combinatoria de código. ¿Hay alguna solución (o estoy diseñando mal mi clase)? ¿Cómo puedo decirle al compilador que llame a la versión sobrecargada del operador * SOLO con ints?

¿Fue útil?

Solución

Suponiendo que desea que se elija la versión especializada para cualquier tipo integral (y no solo int en particular, una cosa que podría hacer es proporcionar eso como una función de plantilla y usar Boost.EnableIf para eliminar esas sobrecargas del conjunto de sobrecarga disponible, si el operando no es un tipo integral.

#include <cstdio>
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits/is_integral.hpp>

class CFixed
{
public:
   CFixed( int   ) {}
   CFixed( float ) {}
};

CFixed operator* ( const CFixed& a, const CFixed&  )
{ puts("General CFixed * CFixed"); return a; }

template <class T>
typename boost::enable_if<boost::is_integral<T>, CFixed>::type operator* ( const CFixed& a, T  )
{ puts("CFixed * [integer type]"); return a; }

template <class T>
typename boost::enable_if<boost::is_integral<T>, CFixed>::type operator* ( T , const CFixed& b )
{ puts("[integer type] * CFixed"); return b; }


int main()
{
    CFixed(0) * 10.0f;
    5 * CFixed(20.4f);
    3.2f * CFixed(10);
    CFixed(1) * 100u;
}

Naturalmente, también podría usar una condición diferente para hacer que esas sobrecargas estén disponibles solo si T = int: typename boost::enable_if<boost::is_same<T, int>, CFixed>::type ...

En cuanto al diseño de la clase, quizás podría confiar más en las plantillas. Por ejemplo, el constructor podría ser una plantilla, y nuevamente, si necesita distinguir entre tipos integrales y reales, debería ser posible emplear esta técnica.

Otros consejos

También debe sobrecargar con el tipo float. La conversión de int al tipo especificado por el usuario (CFixed) es de menor prioridad que la conversión integral flotante incorporada a double. Por lo tanto, el compilador siempre elegirá la función con <=>, a menos que agregue también la función con <=>.

Para más detalles, lea la sección 13.3 del estándar C ++ 03. Siente el dolor.

Parece que también lo he perdido de vista. :-( UncleBens informa que agregar flotante solo no resuelve el problema, ya que la versión con <=> debe agregarse como bueno, pero en cualquier caso, agregar varios operadores relacionados con los tipos incorporados es tedioso, pero no resulta en un impulso combinatorio.

Si tiene constructores que se pueden invocar con un solo argumento, creó efectivamente un operador de conversión implícito. En su ejemplo, donde sea necesario un CFixed, se pueden pasar tanto un int como un float. Esto es, por supuesto, peligroso, porque el compilador podría generar silenciosamente código que llama a la función incorrecta en lugar de ladrarle cuando se olvidó de incluir alguna declaración de función.

Por lo tanto, una buena regla general dice que, siempre que esté escribiendo constructores que se puedan invocar con un solo argumento (tenga en cuenta que este foo(int i, bool b = false) también se puede invocar con un argumento, aunque se necesitan dos argumentos) , debe hacer que el constructor explicit, a menos que realmente desee que se inicie la conversión implícita. El compilador no utiliza constructores std::string::string(const char*) para las conversiones implícitas.

Tendrías que cambiar tu clase a esto:

class CFixed
{
   explicit CFixed( int   );
   explicit CFixed( float );
};

He descubierto que hay muy pocas excepciones a esta regla. (<=> es bastante famoso.)

Editar: Lo siento, perdí el punto de no permitir conversiones implícitas de <=> a <=>.

La única forma en que veo para evitar esto es proporcionar también los operadores para <=>.

¿Qué tal hacer la conversión explícita ?

De acuerdo con sbi, definitivamente debes hacer explícitos tus constructores de un solo parámetro.

Puede evitar una explosión en el operador < > funciones que escribes con plantillas, sin embargo:

template <class T>
CFixed operator* ( const CFixed& a, T b ) 
{ ... } 

template <class T>
CFixed operator* ( T a, const CFixed& b ) 
{ ... } 

Dependiendo de qué código esté en las funciones, esto solo se compilará con los tipos desde los que admite la conversión.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top