Типы хеширования во время компиляции в C++17/C++2a

Рассмотрим следующий код:

#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 (в идеале, опираясь только на стандарт и не полагаясь на встроенные функции компилятора)?

(отказ от ответственности: непривычный способ) Если вы уже знаете типы, вы можете просто специализировать функцию type_hash для интересующих типов, убедитесь, что вы возвращаете уникальные значения для каждого типа.

Ayub 24.05.2019 13:55

@Ayub К сожалению, я не знаю типы заранее

Vincent 24.05.2019 13:57

В некоторых реализациях &typeid(T) всегда будет одинаковым для одного и того же типа, но это не гарантируется. И, к сожалению, std::type_info::hash_code() не constexpr.

aschepler 24.05.2019 14:10

@Vincent, пожалуйста, проверьте это, если это поможет wandbox.org/permlink/yp1VWQe1BmyNCcO5

Ayub 24.05.2019 14:18

@Ayub Как уже было сказано, std::type_info::hash_code() не является constexpr. И мне нужны эти хэши во время компиляции.

Vincent 24.05.2019 14:19

Это мелочь, но вы, наверное, хотели сравнить y с z, а не x с z во втором кауте, так как позже есть yhash == zhash.

Tacet 25.05.2019 02:02

@Vincent - Пожалуйста, не могли бы вы подтвердить, что хотите 1 от yhash == zhash? А может из xhash == zhash?

max66 25.05.2019 12:28

@aschepler: Моя первая мысль тоже. К сожалению, typeid() ведет себя полиморфно для типов указателей, так что это не может быть constexpr

AndyG 29.05.2019 15:22

Почему yhash должно быть таким же, как zhash? Должен ли xhash быть таким же, как zhash?

AndyG 29.05.2019 17:32
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
8
9
1 180
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Ответ принят как подходящий

Я не знаю, как получить 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

Martin Morterol 25.05.2019 07:46

@MartinMorterol - Спасибо за отчет: раньше не замечал. Мне кажется, что поведение правильное, но последний cout (из ОП) неверен: второй cout (если мы хотим получить 1) должен сравнивать xhash и zhash, потому что x и z относятся к одному и тому же типу. Я исправил свой ответ.

max66 25.05.2019 11:42

xD тоже не видел. Кстати, хорошее решение!

Martin Morterol 25.05.2019 15:53

Это хитрый подход. std::any реализации, как правило, используют аналогичные идеи, чтобы избежать RTTI. Жаль, что указатель нельзя reinterpret_cast преобразовать в целое число при вычислении константы.

metalfox 30.05.2019 12:25

Я не думаю, что это возможно. «хеш-ключ, уникальный для типа» звучит так, будто вы ищете идеальный хэш (без коллизий). Даже если мы проигнорируем тот факт, что 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_state1 = хэш (тип1)
  • hash_state2 = хэш (тип2, hash_state1)
  • hash_state3 = хэш (тип3, hash_state2)

Где «hash_state» на самом деле просто уникальный список всех типов, которые мы хэшировали до сих пор. Он также может предоставить значение size_t в результате хеширования нового типа. Если тип, который мы пытаемся хешировать, уже присутствует в списке типов, мы возвращаем индекс этого типа.

Для этого требуется совсем немного шаблонного кода:

  1. Обеспечение уникальности типов в списке типов: здесь я использовал ответ @Deduplicator: https://stackoverflow.com/a/56259838/27678
  2. Поиск типа в уникальном списке типов
  3. Использование if constexpr для проверки наличия типа в списке типов (C++17)

Живая демонстрация


Часть 1: уникальный список типов:

Опять же, вся заслуга Ответ @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{};

Часть 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_token (потому что у вас может быть два разных списка типов, но одно и то же хэш-значение. Например, если вы хешируете 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
}

Заключение:

Не очень полезно в большом количестве кода, но это может помочь решить некоторые проблемы метапрограммирования с ограничениями.

Другие вопросы по теме