Eu tenho tentado pensar em uma maneira de declarar typedefs strongmente tipados, para pegar uma certa classe de bugs no estágio de compilação. Geralmente, é o caso que eu digitei 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 depois de uma longa noite de codificação, pode-se cometer erros bobos, 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, sugestões que eu encontrei para typedefs strongmente tipados incluem usar boost, que pelo menos para mim não é uma possibilidade (eu tenho pelo menos c ++ 11). Então, depois de pensar um pouco, descobri essa ideia e quis que ela fosse executada por alguém.
Primeiro, você declara o tipo base como um modelo. O parâmetro do modelo 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 amigos precisam ser encaminhadas antes da definição da classe, o que exige uma declaração de encaminhamento da classe de modelo.
Em seguida, definimos todos os membros para o tipo base, lembrando que é uma classe de modelo.
Por fim, quando queremos usá-lo, digitamos como:
class EntityT;
typedef IDType<EntityT> EntityID;
class ModelT;
typedef IDType<ModelT> ModelID;
Os tipos agora são totalmente separados. As funções que usam um EntityID lançarão um erro do compilador se você tentar alimentá-las com um ModelID, por exemplo. Além de ter que declarar os tipos 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 veio à mente 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 de multiplicar um vetor por um escalar daria outro vetor, então eu poderia fazer:
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 meu typedef strongmente digitado, eu teria que dizer ao compilador que o multiregamento de Velocity by a Time 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 cada conversão explicitamente, o que pode ser um tipo de incômodo. Por outro lado, essa limitação pode ajudar a evitar outros tipos de erros (digamos, multiplicar uma Velocidade por uma Distância, talvez, o que não faria sentido nesse domínio). Então estou dividido e imaginando se as pessoas têm alguma opinião sobre meu problema original ou sobre minha abordagem para resolvê-lo.