Ho cercato di pensare a un modo per dichiarare dattiloscritti fortemente tipizzati, per catturare una certa classe di bug nella fase di compilazione. È spesso il caso in cui inserirò un int in diversi tipi di ID, o un vettore da posizionare o velocità:
typedef int EntityID;
typedef int ModelID;
typedef Vector3 Position;
typedef Vector3 Velocity;
Questo può rendere più chiaro l'intento del codice, ma dopo una lunga notte di programmazione si potrebbero fare errori sciocchi come confrontare diversi tipi di ID, o forse aggiungere una posizione a una velocità.
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
Sfortunatamente, i suggerimenti che ho trovato per i typedef fortemente tipizzati includono l'uso di boost, che almeno per me non è una possibilità (ho almeno c ++ 11). Quindi, dopo aver riflettuto un po ', ho avuto l'idea e volevo farla funzionare da qualcuno.
Innanzitutto, dichiari il tipo di base come modello. Il parametro template non viene utilizzato per nulla nella definizione, tuttavia:
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 );
};
Le funzioni degli amici devono in realtà essere dichiarate in avanti prima della definizione della classe, che richiede una dichiarazione diretta della classe modello.
Definiamo quindi tutti i membri per il tipo di base, ricordando solo che si tratta di una classe modello.
Alla fine, quando vogliamo usarlo, lo scriviamo come:
class EntityT;
typedef IDType<EntityT> EntityID;
class ModelT;
typedef IDType<ModelT> ModelID;
I tipi sono ora completamente separati. Le funzioni che accettano un EntityID generano un errore del compilatore se si tenta di alimentare loro un ModelID, ad esempio. Oltre a dover dichiarare i tipi di base come modelli, con i problemi che ciò comporta, è anche abbastanza compatto.
Speravo che qualcuno avesse commenti o critiche su questa idea?
Un problema che mi è venuto in mente mentre scrivevo questo, nel caso di posizioni e velocità per esempio, sarebbe che non posso convertire tra i tipi così liberamente come prima. Dove prima di moltiplicare un vettore per uno scalare darebbe un altro vettore, quindi potrei fare:
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;
Con il mio typedef fortemente tipizzato dovrei dire al compilatore che il multiplo di una Velocity per Time risulta in una Posizione.
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
Per risolvere questo, penso che dovrei specializzare esplicitamente ogni conversione, il che può essere una seccatura. D'altra parte, questa limitazione può aiutare a prevenire altri tipi di errori (diciamo, forse, moltiplicare una velocità per una distanza, che non avrebbe senso in questo dominio). Quindi sono lacerato e mi chiedo se le persone abbiano opinioni sul mio problema originale o sul mio approccio per risolverlo.