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.
fuente
Respuestas:
Estos son parámetros de tipo fantasma , es decir, parámetros de un tipo parametrizado que no se utilizan para su representación, sino para separar diferentes "espacios" de tipos con la misma representación.
Y hablando de espacios, esa es una aplicación útil de los tipos fantasmas:
Sin embargo, como has visto, hay algunas dificultades con los tipos de unidades. Una cosa que puede hacer es descomponer unidades en un vector de exponentes enteros en los componentes fundamentales:
Aquí estamos usando valores fantasmas para etiquetar valores de tiempo de ejecución con información en tiempo de compilación sobre los exponentes en las unidades involucradas. Esto escala mejor que hacer estructuras separadas para velocidades, distancias, etc., y podría ser suficiente para cubrir su caso de uso.
fuente
Tuve un caso similar en el que quería distinguir diferentes significados de algunos valores enteros y prohibir las conversiones implícitas entre ellos. Escribí una clase genérica como esta:
Por supuesto, si quieres estar aún más seguro, también puedes hacer el
T
constructorexplicit
. LaMeaning
continuación, se utiliza la siguiente manera:fuente
No estoy seguro de cómo funciona lo siguiente en el código de producción (soy un principiante en C ++ / programación, como el principiante CS101), pero preparé esto usando el sistema macro de C ++.
fuente