Я намагався придумати спосіб декларування сильно набраних типів, щоб знайти певний клас помилок на етапі компіляції. Часто буває так, що я набираю int на кілька типів ідентифікаторів, або вектор для позиціонування або швидкості:
typedef int EntityID;
typedef int ModelID;
typedef Vector3 Position;
typedef Vector3 Velocity;
Це може зробити намір коду більш зрозумілим, але після довгої ночі кодування можна зробити дурні помилки, як порівняння різних типів ідентифікаторів або, можливо, додавання позиції до швидкості.
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
На жаль, пропозиції, які я знайшов для сильно типізованих typedefs, включають використання boost, що принаймні для мене не є можливим (у мене є c ++ 11 принаймні). Отже, трохи подумавши, я придумав цю ідею і хотів її здійснити хтось.
Спочатку ви оголошуєте базовий тип як шаблон. Параметр шаблону не використовується ні для чого у визначенні:
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 );
};
Дружні функції насправді потрібно оголосити вперед перед визначенням класу, що вимагає попереднього оголошення класу шаблонів.
Потім ми визначаємо всіх членів для базового типу, просто пам’ятаючи, що це клас шаблонів.
Нарешті, коли ми хочемо використовувати його, ми вводимо його як:
class EntityT;
typedef IDType<EntityT> EntityID;
class ModelT;
typedef IDType<ModelT> ModelID;
Зараз типи цілком окремі. Функції, які приймають EntityID, призведуть до помилки компілятора, якщо ви спробуєте подати їм, наприклад, ModelID. Окрім необхідності декларувати базові типи як шаблони, з проблемами, які випливає, це також досить компактно.
Я сподівався, що хтось матиме зауваження чи критику щодо цієї ідеї?
Одне питання, яке прийшло в голову під час написання цього запису, наприклад, у випадку позицій та швидкостей, - це те, що я не можу перетворювати між типами так само вільно, як раніше. Де раніше, ніж множення вектора на скаляр, дав би інший вектор, то я міг би зробити:
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;
З моїм сильно набраним typedef я повинен був би сказати компілятору, що мультиплікація швидкості за часом призводить до позиції.
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
Щоб вирішити це, я думаю, що мені доведеться чітко спеціалізувати кожну конверсію, яка може стати певною проблемою. З іншого боку, це обмеження може допомогти запобігти іншим помилкам (скажімо, множення швидкості на відстань, можливо, що не має сенсу в цій області). Тож я розірваний, і цікаво, чи люди мають якісь думки щодо мого оригінального питання чи мого підходу до його вирішення.