Próbowałem wymyślić sposób na zadeklarowanie silnie typowanych typów maszynopisów, aby złapać pewną klasę błędów na etapie kompilacji. Często zdarza się, że wpisuję int w kilku typach id lub wektorze do pozycji lub prędkości:
typedef int EntityID;
typedef int ModelID;
typedef Vector3 Position;
typedef Vector3 Velocity;
Może to uczynić cel kodu bardziej wyraźnym, ale po długiej nocy kodowania można popełnić głupie błędy, takie jak porównywanie różnych rodzajów identyfikatorów lub może dodanie pozycji do prędkości.
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
Niestety, sugestie, które znalazłem dla silnie typowanych typów typefów, obejmują użycie boosta, co przynajmniej dla mnie nie jest możliwe (mam przynajmniej c ++ 11). Po krótkim namyśle wpadłem na ten pomysł i chciałem go uruchomić.
Najpierw zadeklarujesz typ podstawowy jako szablon. Jednak parametr szablonu nie jest używany do niczego w definicji, ale:
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 );
};
Funkcje zaprzyjaźnione faktycznie muszą być zadeklarowane w przód przed definicją klasy, co wymaga deklaracji w przód klasy szablonu.
Następnie definiujemy wszystkich członków dla typu podstawowego, pamiętając tylko, że jest to klasa szablonów.
Wreszcie, gdy chcemy go użyć, wpisaliśmy go jako:
class EntityT;
typedef IDType<EntityT> EntityID;
class ModelT;
typedef IDType<ModelT> ModelID;
Typy są teraz całkowicie oddzielne. Funkcje pobierające identyfikator EntityID wyrzucą błąd kompilatora, jeśli spróbujesz na przykład podać im identyfikator modelu. Oprócz konieczności zadeklarowania typów podstawowych jako szablonów, z towarzyszącymi im problemami, jest również dość kompaktowy.
Miałem nadzieję, że ktoś miał komentarze lub krytykę na temat tego pomysłu?
Jednym z problemów, które przyszło mi do głowy podczas pisania tego, na przykład w przypadku pozycji i prędkości, jest to, że nie mogę swobodnie konwertować między typami tak swobodnie, jak wcześniej. Gdzie przed pomnożeniem wektora przez skalar dałby inny wektor, więc mógłbym zrobić:
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;
Z moim silnie wpisanym typemef musiałbym powiedzieć kompilatorowi, że błędne odczytanie Prędkości przez Czas skutkuje Pozycją.
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
Aby rozwiązać ten problem, myślę, że musiałbym specjalnie specjalizować każdą konwersję, co może być trochę kłopotliwe. Z drugiej strony to ograniczenie może pomóc w zapobieganiu innym rodzajom błędów (powiedzmy, pomnożenie Prędkości przez Odległość, być może nie ma sensu w tej dziedzinie). Jestem rozdarty i zastanawiam się, czy ludzie mają jakieś opinie na temat mojego pierwotnego problemu lub mojego podejścia do jego rozwiązania.