Один класс-обертка, чтобы управлять ими всеми.
Лучше всего было бы создать класс-обертку, но используя шаблоны, мы можем написать один шаблон класса-обертки и использовать его для всех различных идентификаторов, просто назначив ихразличные экземпляры шаблона.
template<class ID>
struct ID_wrapper
{
constexpr static auto name() -> decltype(ID::name()) {
return ID::name();
}
int value;
// Implicitly convertible to `int`, for C operability
operator int() const {
return value;
}
};
Перегрузка std::hash
(только один раз)
Мы можем придерживаться любых черт, которые мы хотим в классе ID
, но я предоставил name()
какпример.Поскольку ID_Wrapper
написан как шаблон, специализировать его для std::hash
и других классов нужно только один раз:
template<class ID>
class std::hash<ID_wrapper<ID>> : public std::hash<int>
{
public:
// I prefer using Base to typing out the actual base
using Base = std::hash<int>;
// Provide argument_type and result_type
using argument_type = int;
using result_type = std::size_t;
// Use the base class's constructor and function call operator
using Base::Base;
using Base::operator();
};
Распечатать идентификатор с его именем
Если выхочу, мы могли бы также специализировать operator<<
, но ID_wrapper
неявно конвертируется в int
в любом случае:
template<class ID>
std::ostream& operator<<(std::ostream& stream, ID_Wrapper<ID> id) {
stream << '(' << ID_Wrapper<ID>::name() << ": " << id.value << ')';
return stream;
}
Получив это, мы просто напишем класс признаков для каждого типа идентификатора!
struct ColorIDTraits {
constexpr static const char* name() {
return "color_id";
}
};
struct SmellIDTraits {
constexpr static const char* name() {
return "smell_id";
}
};
struct FlavorIDTraits {
constexpr static const char* name() {
return "flavor_id";
}
};
Оборачивая все это вместе
Тогда мы можем typedef
упаковщик ID:
using color_id = ID_wrapper<ColorIDTraits>;
using smell_id = ID_wrapper<SmellIDTraits>;
using flavor_id = ID_wrapper<FlavorIDTraits>;