Я соглашаюсь с другими ответами, что это, как правило, пока невозможно, как указано в стандарте C ++, но мы можем решить ограниченную версию проблемы.
Поскольку это все программирование во время компиляции, у нас не может быть изменяемого состояния, поэтому, если вы готовы использовать новую переменную для каждого изменения состояния, то возможно что-то подобное:
- hash_state1 = hash (type1)
- hash_state2 = hash (type2, hash_state1)
- hash_state3 = hash (type3, hash_state2)
Где "hash_state" - это на самом деле просто уникальный список типов всех типов, которые мы хэшировали до сих пор. Он также может предоставить значение size_t
в результате хеширования нового типа.
Если тип, который мы ищем, уже присутствует в списке типов, мы возвращаем индекс этого типа.
Это требует довольно много шаблонного:
- Обеспечение уникальности типов в списке типов: здесь я использовал ответ @ Deduplicator: https://stackoverflow.com/a/56259838/27678
- Поиск типа в уникальном списке типов
- Использование
if constexpr
для проверки наличия типа в списке типов (C ++ 17)
Часть 1: уникальный список типов:
Опять же, все заслуги @ ответа Дедупликатора здесь в этой части. Следующий код сохраняет производительность во время компиляции, выполняя поиск по списку типов за O (log N), благодаря использованию tuple-cat.
Код написан почти безумно, но приятно то, что он позволяет работать с любым универсальным списком типов (tuple
, variant
, что-то нестандартное).
namespace detail {
template <template <class...> class TT, template <class...> class UU, class... Us>
auto pack(UU<Us...>)
-> std::tuple<TT<Us>...>;
template <template <class...> class TT, class... Ts>
auto unpack(std::tuple<TT<Ts>...>)
-> TT<Ts...>;
template <std::size_t N, class T>
using TET = std::tuple_element_t<N, T>;
template <std::size_t N, class T, std::size_t... Is>
auto remove_duplicates_pack_first(T, std::index_sequence<Is...>)
-> std::conditional_t<(... || (N > Is && std::is_same_v<TET<N, T>, TET<Is, T>>)), std::tuple<>, std::tuple<TET<N, T>>>;
template <template <class...> class TT, class... Ts, std::size_t... Is>
auto remove_duplicates(std::tuple<TT<Ts>...> t, std::index_sequence<Is...> is)
-> decltype(std::tuple_cat(remove_duplicates_pack_first<Is>(t, is)...));
template <template <class...> class TT, class... Ts>
auto remove_duplicates(TT<Ts...> t)
-> decltype(unpack<TT>(remove_duplicates<TT>(pack<TT>(t), std::make_index_sequence<sizeof...(Ts)>())));
}
template <class T>
using remove_duplicates_t = decltype(detail::remove_duplicates(std::declval<T>()));
Далее я объявляю свой собственный список типов для использования приведенного выше кода. Довольно простая пустая структура, которую большинство из вас видели раньше:
template<class...> struct typelist{};
Часть 2: наше "hash_state"
"hash_state", который я называю hash_token
:
template<size_t N, class...Ts>
struct hash_token
{
template<size_t M, class... Us>
constexpr bool operator ==(const hash_token<M, Us...>&)const{return N == M;}
constexpr size_t value() const{return N;}
};
Просто инкапсулирует size_t
для значения хеша (к которому вы также можете получить доступ через функцию value()
) и компаратора, чтобы проверить, идентичны ли два значения hash_tokens (потому что вы можете иметь два разных списка типов, но одно и то же значение хеш-функции например, если вы хешировали int
, чтобы получить токен, а затем сравнили этот токен с тем, где вы хэшировали (int
, float
, char
, int
)).
Часть 3: type_hash
Функция
Наконец наша функция type_hash
:
template<class T, size_t N, class... Ts>
constexpr auto type_hash(T, hash_token<N, Ts...>) noexcept
{
if constexpr(std::is_same_v<remove_duplicates_t<typelist<Ts..., T>>, typelist<Ts...>>)
{
return hash_token<detail::index_of<T, Ts...>(), Ts...>{};
}
else
{
return hash_token<N+1, Ts..., T>{};
}
}
template<class T>
constexpr auto type_hash(T) noexcept
{
return hash_token<0, T>{};
}
Первая перегрузка для общего случая; Вы уже «хэшировали» несколько типов, и вы хотите хэшировать еще один. Он проверяет, хеширован ли уже тип, который вы хэшируете, и, если так, возвращает индекс типа в списке уникальных типов.
Чтобы добиться получения индекса типа в списке типов, я использовал простое расширение шаблона, чтобы сохранить некоторые экземпляры шаблона времени компиляции (избегая рекурсивного поиска):
// find the first index of T in Ts (assuming T is in Ts)
template<class T, class... Ts>
constexpr size_t index_of()
{
size_t index = 0;
size_t toReturn = 0;
using swallow = size_t[];
(void)swallow{0, (void(std::is_same_v<T, Ts> ? toReturn = index : index), ++index)...};
return toReturn;
}
Вторая перегрузка type_hash
предназначена для создания начального hash_token
, начиная с 0
.
Использование:
int main()
{
auto x = []{};
auto y = []{};
auto z = x;
std::cout << std::is_same_v<decltype(x), decltype(y)> << std::endl; // 0
std::cout << std::is_same_v<decltype(x), decltype(z)> << std::endl; // 1
constexpr auto xtoken = type_hash(x);
constexpr auto xytoken = type_hash(y, xtoken);
constexpr auto xyztoken = type_hash(z, xytoken);
std::cout << (xtoken == xytoken) << std::endl; // 0
std::cout << (xtoken == xyztoken) << std::endl; // 1
}
Вывод:
Не очень полезен во многих кодах, но это может помочь решить некоторые ограниченные проблемы метапрограммирования.