У меня была похожая проблема несколько месяцев назад. Я искал технику для определения идентификаторов, которые одинаковы для каждого выполнения.
Если это требование, здесь - это еще один вопрос, в котором рассматривается более или менее та же проблема (конечно, он сопровождается хорошим ответом).
Во всяком случае, я не использовал предложенное решение. Далее следует описание того, что я сделал в тот раз.
Вы можете определить constexpr
функцию, подобную следующей:
static constexpr uint32_t offset = 2166136261u;
static constexpr uint32_t prime = 16777619u;
constexpr uint32_t fnv(uint32_t partial, const char *str) {
return str[0] == 0 ? partial : fnv((partial^str[0])*prime, str+1);
}
inline uint32_t fnv(const char *str) {
return fnv(offset, str);
}
Затем класс, подобный этому, от которого наследуется:
template<typename T>
struct B {
static const uint32_t id() {
static uint32_t val = fnv(T::identifier);
return val;
}
};
Идиома CRTP делает все остальное.
Например, вы можете определить производный класс следующим образом:
struct C: B<C> {
static const char * identifier;
};
const char * C::identifier = "ID(C)";
Пока вы предоставляете разные идентификаторы для разных классов, у вас будут уникальные числовые значения, которые можно использовать для различения типов.
Идентификаторы не обязательно должны быть частью производных классов. Например, вы можете предоставить их с помощью признака:
template<typename> struct trait;
template<> struct trait { static const char * identifier; };
// so on with all the identifiers
template<typename T>
struct B {
static const uint32_t id() {
static uint32_t val = fnv(trait<T>::identifier);
return val;
}
};
Преимущества:
- Простота реализации.
- Нет зависимостей.
- Числовые значения одинаковы при каждом выполнении.
- Классы могут использовать один и тот же числовой идентификатор, если это необходимо.
Недостатки:
- Склонность к ошибкам: копирование и вставка могут быстро стать вашим злейшим врагом.
Это минимальный рабочий пример того, что было описано выше.
Я адаптировал код так, чтобы можно было использовать метод ID
member в выражении switch
:
#include<type_traits>
#include<cstdint>
#include<cstddef>
static constexpr uint32_t offset = 2166136261u;
static constexpr uint32_t prime = 16777619u;
template<std::size_t I, std::size_t N>
constexpr
std::enable_if_t<(I == N), uint32_t>
fnv(uint32_t partial, const char (&)[N]) {
return partial;
}
template<std::size_t I, std::size_t N>
constexpr
std::enable_if_t<(I < N), uint32_t>
fnv(uint32_t partial, const char (&str)[N]) {
return fnv<I+1>((partial^str[I])*prime, str);
}
template<std::size_t N>
constexpr inline uint32_t fnv(const char (&str)[N]) {
return fnv<0>(offset, str);
}
template<typename T>
struct A {
static constexpr uint32_t ID() {
return fnv(T::identifier);
}
};
struct C: A<C> {
static constexpr char identifier[] = "foo";
};
struct D: A<D> {
static constexpr char identifier[] = "bar";
};
int main() {
constexpr auto val = C::ID();
switch(val) {
case C::ID():
break;
case D::ID():
break;
default:
break;
}
}
Обратите внимание, что если вы хотите использовать ID
в неконстантном выражении, вы должны где-то определить identifier
s следующим образом:
constexpr char C::identifier[];
constexpr char D::identifier[];
Как только вы это сделали, вы можете сделать что-то вроде этого:
int main() {
constexpr auto val = C::ID();
// Now, it is well-formed
auto ident = C::ID();
// ...
}