J'ai essayé de trouver un moyen de déclarer des typedefs fortement typés, d'attraper une certaine classe de bogues au stade de la compilation. Il arrive souvent que je tape un int dans plusieurs types d'identifiants, ou un vecteur de position ou de vélocité:
typedef int EntityID;
typedef int ModelID;
typedef Vector3 Position;
typedef Vector3 Velocity;
Cela peut rendre l'intention du code plus claire, mais après une longue nuit de codage, on peut faire des erreurs stupides comme comparer différents types d'identifiants ou ajouter une position à une vélocité.
EntityID eID;
ModelID mID;
if ( eID == mID ) // <- Compiler sees nothing wrong
{ /*bug*/ }
Position p;
Velocity v;
Position newP = p + v; // bug, meant p + v*s but compiler sees nothing wrong
Malheureusement, les suggestions que j'ai trouvées pour les types de caractères fortement typés incluent l’utilisation de boost, ce qui pour moi au moins n’est pas une possibilité (j’ai au moins le c ++ 11). Donc, après un peu de réflexion, je suis tombé sur cette idée et je voulais la faire passer à quelqu'un.
Tout d'abord, vous déclarez le type de base en tant que modèle. Le paramètre template n'est utilisé pour rien dans la définition, cependant:
template < typename T >
class IDType
{
unsigned int m_id;
public:
IDType( unsigned int const& i_id ): m_id {i_id} {};
friend bool operator==<T>( IDType<T> const& i_lhs, IDType<T> const& i_rhs );
};
Les fonctions d'ami doivent en fait être déclarées en aval avant la définition de la classe, ce qui nécessite une déclaration en aval de la classe de modèle.
Nous définissons ensuite tous les membres pour le type de base, en nous rappelant qu'il s'agit d'une classe de modèle.
Enfin, lorsque nous voulons l’utiliser, nous le typons comme suit:
class EntityT;
typedef IDType<EntityT> EntityID;
class ModelT;
typedef IDType<ModelT> ModelID;
Les types sont maintenant entièrement séparés. Les fonctions qui prennent un EntityID génèreront une erreur de compilation si vous essayez de leur fournir un ModelID, par exemple. En plus de devoir déclarer les types de base en tant que modèles, avec les problèmes que cela entraîne, il est également assez compact.
J'espérais que quelqu'un avait des commentaires ou des critiques sur cette idée?
Un problème qui m'est venu à l'esprit en écrivant ceci, dans le cas des positions et des vitesses, par exemple, est que je ne peux pas convertir entre types aussi librement qu'avant. Où, avant de multiplier un vecteur par un scalaire, je devrais faire un autre vecteur:
typedef float Time;
typedef Vector3 Position;
typedef Vector3 Velocity;
Time t = 1.0f;
Position p = { 0.0f };
Velocity v = { 1.0f, 0.0f, 0.0f };
Position newP = p + v*t;
Avec mon typedef fortement typé, je devrais dire au compilateur que multiplier une Velocity par Time donne une Position.
class TimeT;
typedef Float<TimeT> Time;
class PositionT;
typedef Vector3<PositionT> Position;
class VelocityT;
typedef Vector3<VelocityT> Velocity;
Time t = 1.0f;
Position p = { 0.0f };
Velocity v = { 1.0f, 0.0f, 0.0f };
Position newP = p + v*t; // Compiler error
Pour résoudre ce problème, je pense que je devrais spécialiser chaque conversion de manière explicite, ce qui peut être un peu gênant. D'autre part, cette limitation peut aider à prévenir d'autres types d'erreurs (par exemple, en multipliant une vitesse par une distance, ce qui n'aurait peut-être pas de sens dans ce domaine). Je suis donc déchiré et je me demande si les gens ont des opinions sur mon problème initial ou sur mon approche pour le résoudre.