Eu tenho tentado pensar em uma maneira de declarar typedefs fortemente tipados, para capturar uma certa classe de bugs no estágio de compilação. Geralmente, digito um int em vários tipos de IDs ou um vetor para posição ou velocidade:
typedef int EntityID;
typedef int ModelID;
typedef Vector3 Position;
typedef Vector3 Velocity;
Isso pode tornar a intenção do código mais clara, mas após uma longa noite de codificação, pode-se cometer erros tolos como comparar diferentes tipos de IDs ou adicionar uma posição a uma velocidade, talvez.
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
Infelizmente, as sugestões que encontrei para typedefs com tipos fortes incluem o uso de boost, o que pelo menos para mim não é uma possibilidade (eu tenho c ++ 11 pelo menos). Então, depois de pensar um pouco, me deparei com essa idéia e queria executá-la por alguém.
Primeiro, você declara o tipo de base como um modelo. O parâmetro template não é usado para nada na definição, no entanto:
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 );
};
As funções de amigo realmente precisam ser declaradas para frente antes da definição da classe, o que requer uma declaração de encaminhamento da classe de modelo.
Em seguida, definimos todos os membros para o tipo base, lembrando apenas que é uma classe de modelo.
Finalmente, quando queremos usá-lo, digitamos como:
class EntityT;
typedef IDType<EntityT> EntityID;
class ModelT;
typedef IDType<ModelT> ModelID;
Os tipos agora estão totalmente separados. As funções que usam um EntityID geram um erro de compilador se você tentar alimentar um ModelID, por exemplo. Além de ter que declarar os tipos de base como modelos, com os problemas que isso implica, também é bastante compacto.
Eu esperava que alguém tivesse comentários ou críticas sobre essa ideia?
Uma questão que me ocorreu ao escrever isso, no caso de posições e velocidades, por exemplo, seria que não posso converter entre tipos tão livremente quanto antes. Onde antes multiplicar um vetor por um escalar daria outro vetor, para que eu pudesse:
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;
Com o meu typedef fortemente tipado, eu teria que dizer ao compilador que a desmembragem de uma velocidade por tempo resulta em uma posição.
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 isso, acho que teria que especializar explicitamente todas as conversões, o que pode ser um incômodo. Por outro lado, essa limitação pode ajudar a evitar outros tipos de erros (por exemplo, multiplicar uma velocidade por uma distância, talvez, o que não faria sentido nesse domínio). Por isso, estou arrasado e me pergunto se as pessoas têm alguma opinião sobre o meu problema original ou minha abordagem para resolvê-lo.