Хеширование типов во время компиляции в C ++ 17 / C ++ 2a - PullRequest
8 голосов
/ 24 мая 2019

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

#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 возвращала хеш-ключ, уникальный для данного типа, во время компиляции. Есть ли способ сделать это в C ++ 17 или в C ++ 2a (в идеале, полагаясь только на стандарт и не полагаясь на встроенные функции компилятора)?

Ответы [ 5 ]

7 голосов
/ 25 мая 2019

Я сомневаюсь, что это возможно с чисто стандартным C ++.


Но есть решение, которое будет работать на большинстве основных компиляторов (по крайней мере, GCC, Clang и MSVC). Вы можете хешировать строки, возвращаемые следующей функцией:

template <typename T> constexpr const char *foo()
{
    #ifdef _MSC_VER
    return __FUNCSIG__;
    #else
    return __PRETTY_FUNCTION__;
    #endif
}
4 голосов
/ 24 мая 2019

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

2 голосов
/ 27 мая 2019

На основе HolyBlackCat ответа, шаблонной переменной 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>;

Проверьте на godbolt , что значения действительно вычисляются во время компиляции.

1 голос
/ 29 мая 2019

Я соглашаюсь с другими ответами, что это, как правило, пока невозможно, как указано в стандарте C ++, но мы можем решить ограниченную версию проблемы.

Поскольку это все программирование во время компиляции, у нас не может быть изменяемого состояния, поэтому, если вы готовы использовать новую переменную для каждого изменения состояния, то возможно что-то подобное:

  • hash_state1 = hash (type1)
  • hash_state2 = hash (type2, hash_state1)
  • hash_state3 = hash (type3, hash_state2)

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

Это требует довольно много шаблонного:

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

Live Demo


Часть 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
}

Вывод:

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

1 голос
/ 25 мая 2019

Я не думаю, что это возможно. «ключ хеша, уникальный для типа» звучит так, как будто вы ищете идеальный хеш (без коллизий). Даже если мы игнорируем, что size_t имеет конечное число возможных значений, в общем, мы не можем знать все типы из-за таких вещей, как разделяемые библиотеки.

Вам нужно, чтобы он сохранялся между пробежками? Если нет, вы можете настроить схему регистрации.

...