He estado tratando de pensar en una forma de declarar typedefs fuertemente tipados, para detectar una cierta clase de errores en la etapa de compilación. A menudo es el caso que escribo def de 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 identificadores, 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 tipados incluyen el uso de boost, que al menos para mí no es una posibilidad (tengo al menos c ++ 11). Entonces, después de pensar un poco, me encontré con esta idea y quise ponerla en práctica por alguien.
Primero, declaras 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 );
};
En realidad, las funciones de amigo deben declararse hacia adelante 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 del compilador si intenta alimentarlos con un ModelID, 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 se me ocurrió al escribir esto, en el caso de las posiciones y las velocidades, por ejemplo, sería que no puedo convertir entre tipos tan libremente como antes. Donde antes de multiplicar un vector por un escalar daría otro vector, por lo 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 multiplicar una Velocidad por un Tiempo da como resultado 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 cada conversión explícitamente, lo que puede ser una molestia. Por otro lado, esta limitación puede ayudar a prevenir otros tipos de errores (por ejemplo, multiplicando una Velocidad por una Distancia, tal vez, lo que no tendría sentido en este dominio). Así que estoy desgarrado y me pregunto si las personas tienen alguna opinión sobre mi problema original o mi enfoque para resolverlo.