He estado tratando de pensar en una forma de declarar typedefs tipográficamente, para detectar una cierta clase de errores en la etapa de compilación. A menudo es el caso que tipearé un int en varios tipos de identificadores, o un vector para posicionar o velocidad:
typedef int EntityID;
typedef int ModelID;
typedef Vector3 Position;
typedef Vector3 Velocity;
Esto puede hacer que la intención del código sea más clara, pero después de una larga noche de codificación, uno podría cometer errores tontos, como comparar diferentes tipos de ID o agregar una posición a una velocidad tal vez.
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
Desafortunadamente, las sugerencias que he encontrado para typedefs fuertemente tipadas incluyen el uso de boost, que al menos para mí no es una posibilidad (al menos tengo c ++ 11). Así que, después de pensar un poco, se me ocurrió esta idea y quería que alguien la pusiera en marcha.
Primero, declara el tipo base como una plantilla. Sin embargo, el parámetro de plantilla no se usa para nada en la definición:
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 );
};
Las funciones de amigo en realidad deben ser declaradas hacia delante antes de la definición de clase, lo que requiere una declaración hacia adelante de la clase de plantilla.
Luego, definimos todos los miembros para el tipo base, solo recordando que es una clase de plantilla.
Finalmente, cuando queremos usarlo, lo escribimos como:
class EntityT;
typedef IDType<EntityT> EntityID;
class ModelT;
typedef IDType<ModelT> ModelID;
Los tipos ahora están completamente separados. Las funciones que toman un EntityID arrojarán un error de compilador si intenta darles un ModelID en su lugar, por ejemplo. Además de tener que declarar los tipos base como plantillas, con los problemas que esto conlleva, también es bastante compacto.
¿Esperaba que alguien tuviera comentarios o críticas sobre esta idea?
Un problema que me vino a la mente al escribir esto, en el caso de las posiciones y las velocidades, por ejemplo, es que no puedo convertir los tipos con tanta libertad como antes. Donde antes, multiplicar un vector por un escalar daría otro vector, así que podría hacer:
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 mi typedef fuertemente tipado tendría que decirle al compilador que el hecho de que unir un Velocity by a Time resulte en una Posición.
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
Para resolver esto, creo que tendría que especializar explícitamente cada conversión, lo que puede ser una molestia. Por otro lado, esta limitación puede ayudar a prevenir otros tipos de errores (por ejemplo, multiplicar una velocidad por una distancia, tal vez, lo que no tendría sentido en este dominio). Así que estoy desgarrado y me pregunto si la gente tiene alguna opinión sobre mi problema original o mi enfoque para resolverlo.