Question

J'ai une classe qui encapsule des calculs, par exemple des calculs en virgule fixe. J'aime l'idée de surcharger les opérateurs arithmétiques, alors j'écris ce qui suit:

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

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

Tout fonctionne. Je peux écrire 3 * CFixed (0) et CFixed (3) * 10.0f. Mais maintenant je me rends compte que je peux implémenter operator * avec un opérande entier beaucoup plus efficace. Alors je le surcharge:

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

Cela fonctionne toujours, mais maintenant, CFixed (0) * 10.0f appelle la version surchargée, convertissant float en int (et je m'attendais à ce qu'il convertisse float en CFixed). Bien sûr, je peux surcharger une version float également, mais cela semble être une explosion combinatoire de code pour moi. Existe-t-il une solution de contournement (ou ma classe est-elle mal conçue)? Comment puis-je dire au compilateur d'appeler la version surchargée de l'opérateur * UNIQUEMENT avec ints?

Était-ce utile?

La solution

En supposant que vous souhaitiez que la version spécialisée soit sélectionnée pour tout type d'intégrale (et pas seulement pour int , vous pouvez le faire en tant que fonction de modèle et utiliser Boost.EnableIf. pour supprimer ces surcharges de l'ensemble de surcharge disponible, si l'opérande n'est pas de type intégral.

#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;
}

Naturellement, vous pouvez également utiliser une condition différente pour rendre ces surcharges disponibles uniquement si T = int: typename boost::enable_if<boost::is_same<T, int>, CFixed>::type ...

En ce qui concerne la conception de la classe, vous pourriez peut-être vous fier davantage aux modèles. E.g, le constructeur pourrait être un modèle, et encore une fois, si vous avez besoin de faire la distinction entre les types intégral et réel, vous devriez pouvoir utiliser cette technique.

Autres conseils

Vous devriez également surcharger avec float le type. La conversion de int vers le type spécifié par l'utilisateur (CFixed) a une priorité inférieure à celle de la conversion intégrée intégrale flottante en double. Le compilateur choisira donc toujours fonction avec <=>, sauf si vous ajoutez également fonction avec <=>.

Pour plus de détails, lisez la section 13.3 de la norme C ++ 03. Sentez la douleur.

Il semble que je l’aie aussi oublié. :-( UncleBens indique que l'ajout de float ne résout pas le problème, car la version avec <=> doit être ajoutée en tant que Dans tous les cas, il est fastidieux d’ajouter plusieurs opérateurs associés à des types intégrés, mais cela ne se traduit pas par un renforcement combinatoire.

Si vous avez des constructeurs pouvant être appelés avec un seul argument, vous avez effectivement créé un opérateur de conversion implicite. Dans votre exemple, chaque fois qu'un CFixed est requis, un int et un float peuvent être passés. Ceci est bien sûr dangereux, car le compilateur peut générer silencieusement du code appelant la mauvaise fonction au lieu de vous aboyer lorsque vous avez oublié d'inclure une déclaration de fonction.

Par conséquent, une bonne règle empirique indique que, chaque fois que vous écrivez des constructeurs pouvant être appelés avec un seul argument (notez que celui-ci foo(int i, bool b = false) peut également être appelé avec un argument, même s'il faut deux arguments). , vous devez créer ce constructeur explicit, à moins que vous ne souhaitiez réellement que la conversion implicite se déclenche. Les constructeurs std::string::string(const char*) ne sont pas utilisés par le compilateur pour les conversions implicites.

Il vous faudrait changer votre classe pour ceci:

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

J'ai constaté qu'il y a très peu d'exceptions à cette règle. (<=> est assez célèbre.)

Éditer: Je suis désolé, je n’ai pas compris le fait de ne pas autoriser les conversions implicites de <=> à <=>.

Le seul moyen d'éviter ce problème est de fournir également les opérateurs pour <=>.

Pourquoi ne pas rendre la conversion explicite ?

D'accord avec sbi, vous devez absolument expliciter vos constructeurs à paramètre unique.

Vous pouvez éviter une explosion de l'opérateur < > fonctions que vous écrivez avec des modèles, cependant:

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

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

En fonction du code contenu dans les fonctions, cela ne sera compilé qu'avec les types à partir desquels vous prenez en charge la conversion.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top