Рассмотрим следующий код:
#include <iostream>
#include <type_traits>
template <class T>
constexpr std::size_t type_hash(T) noexcept
{
// Compute a hash for the type
// DO SOMETHING SMART HERE
}
int main(int argc, char* argv[])
{
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 std::size_t xhash = type_hash(x);
constexpr std::size_t yhash = type_hash(y);
constexpr std::size_t zhash = type_hash(z);
std::cout << (xhash == yhash) << std::endl; // should be 0
std::cout << (yhash == zhash) << std::endl; // should be 1
return 0;
}
Я хотел бы, чтобы функция type_hash возвращала хэш-ключ, уникальный для типа, во время компиляции. Есть ли способ сделать это в С++ 17 или в С++ 2a (в идеале, опираясь только на стандарт и не полагаясь на встроенные функции компилятора)?
@Ayub К сожалению, я не знаю типы заранее
В некоторых реализациях &typeid(T) всегда будет одинаковым для одного и того же типа, но это не гарантируется. И, к сожалению, std::type_info::hash_code() не constexpr.
@Vincent, пожалуйста, проверьте это, если это поможет wandbox.org/permlink/yp1VWQe1BmyNCcO5
@Ayub Как уже было сказано, std::type_info::hash_code() не является constexpr. И мне нужны эти хэши во время компиляции.
Это мелочь, но вы, наверное, хотели сравнить y с z, а не x с z во втором кауте, так как позже есть yhash == zhash.
@Vincent - Пожалуйста, не могли бы вы подтвердить, что хотите 1 от yhash == zhash? А может из xhash == zhash?
@aschepler: Моя первая мысль тоже. К сожалению, typeid() ведет себя полиморфно для типов указателей, так что это не может быть constexpr
Почему yhash должно быть таким же, как zhash? Должен ли xhash быть таким же, как zhash?





Я не знаю, как получить std::size_t для хеша.
Но если вы принимаете указатель на что-то, возможно, вы можете взять адрес статического члена в классе-шаблоне.
Я имею в виду... что-то вроде следующего
#include <iostream>
#include <type_traits>
template <typename>
struct type_hash
{
static constexpr int i { };
static constexpr int const * value { &i };
};
template <typename T>
static constexpr auto type_hash_v = type_hash<T>::value;
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 xhash = type_hash_v<decltype(x)>;
constexpr auto yhash = type_hash_v<decltype(y)>;
constexpr auto zhash = type_hash_v<decltype(z)>;
std::cout << (xhash == yhash) << std::endl; // should be 0
std::cout << (xhash == zhash) << std::endl; // should be 1
} // ...........^^^^^ xhash, not yhash
Если вы действительно хотите type_hash как функцию, я полагаю, вы могли бы просто создать функцию, которая возвращает type_hash_v<T> полученного типа.
Мне нравится идея, но она не работает: wandbox.org/permlink/HVAoJWlmj7onyapg
@MartinMorterol - Спасибо за отчет: раньше не замечал. Мне кажется, что поведение правильное, но последний cout (из ОП) неверен: второй cout (если мы хотим получить 1) должен сравнивать xhash и zhash, потому что x и z относятся к одному и тому же типу. Я исправил свой ответ.
xD тоже не видел. Кстати, хорошее решение!
Это хитрый подход. std::any реализации, как правило, используют аналогичные идеи, чтобы избежать RTTI. Жаль, что указатель нельзя reinterpret_cast преобразовать в целое число при вычислении константы.
Я не думаю, что это возможно. «хеш-ключ, уникальный для типа» звучит так, будто вы ищете идеальный хэш (без коллизий). Даже если мы проигнорируем тот факт, что size_t имеет конечное число возможных значений, в общем случае мы не можем знать все типы из-за таких вещей, как разделяемые библиотеки.
Вам нужно, чтобы он сохранялся между запусками? Если нет, вы можете настроить схему регистрации.
Я сомневаюсь, что это возможно только на стандартном C++.
Но есть решение, которое будет работать на большинстве основных компиляторов (по крайней мере, GCC, Clang и MSVC). Вы можете хэшировать строки, возвращаемые следующей функцией:
template <typename T> constexpr const char *foo()
{
#ifdef _MSC_VER
return __FUNCSIG__;
#else
return __PRETTY_FUNCTION__;
#endif
}
На основе ответа СвятойЧерныйКот переменная шаблона constexpr, которая является (наивной) реализацией хэша типа:
template <typename T>
constexpr std::size_t Hash()
{
std::size_t result{};
#ifdef _MSC_VER
#define F __FUNCSIG__
#else
#define F __PRETTY_FUNCTION__
#endif
for (const auto &c : F)
(result ^= c) <<= 1;
return result;
}
template <typename T>
constexpr std::size_t constexpr_hash = Hash<T>();
Может использоваться, как показано ниже:
constexpr auto f = constexpr_hash<float>;
constexpr auto i = constexpr_hash<int>;
Проверьте божественная стрела, что значения действительно вычисляются во время компиляции.
Я соглашусь с другими ответами, что это вообще невозможно, как указано в стандартном С++, но мы можем решить ограниченную версию проблемы.
Поскольку все это программирование во время компиляции, у нас не может быть изменяемого состояния, поэтому, если вы хотите использовать новую переменную для каждого изменения состояния, то возможно что-то вроде этого:
Где «hash_state» на самом деле просто уникальный список всех типов, которые мы хэшировали до сих пор. Он также может предоставить значение size_t в результате хеширования нового типа.
Если тип, который мы пытаемся хешировать, уже присутствует в списке типов, мы возвращаем индекс этого типа.
Для этого требуется совсем немного шаблонного кода:
if constexpr для проверки наличия типа в списке типов (C++17)Опять же, вся заслуга Ответ @Deduplicator здесь в этой части. Следующий код снижает производительность во время компиляции, выполняя поиск в списке типов за время 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{};
"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_token (потому что у вас может быть два разных списка типов, но одно и то же хэш-значение. Например, если вы хешируете int чтобы получить токен, а затем сравнить этот токен с тем, который вы хешировали (int, float, char, int)).
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
}
Не очень полезно в большом количестве кода, но это может помочь решить некоторые проблемы метапрограммирования с ограничениями.
(отказ от ответственности: непривычный способ) Если вы уже знаете типы, вы можете просто специализировать функцию type_hash для интересующих типов, убедитесь, что вы возвращаете уникальные значения для каждого типа.