C ++ 17 / C ++ 2a中的编译时哈希类型

时间:2019-05-24 11:50:57

标签: c++ hash c++17 template-meta-programming c++20

考虑以下代码:

#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 个答案:

答案 0 :(得分:6)

我怀疑仅使用标准C ++是否可行。


但是有一种解决方案可以在大多数主要编译器(至少GCC,Clang和MSVC)上运行。您可以对以下函数返回的字符串进行哈希处理:

template <typename T> constexpr const char *foo()
{
    #ifdef _MSC_VER
    return __FUNCSIG__;
    #else
    return __PRETTY_FUNCTION__;
    #endif
}

答案 1 :(得分:3)

我不知道一种为哈希获取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 :(得分:1)

基于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的值确实是在编译时计算的。

答案 3 :(得分:0)

我认为不可能。 “该类型唯一的哈希键”听起来像您在寻找完美的哈希(无冲突)。即使我们忽略size_t具有有限数量的可能值,通常由于共享库之类的原因,我们也不知道所有类型。

您是否需要它在两次运行之间持续存在?如果没有,则可以设置注册方案。

答案 4 :(得分:0)

我将同意其他答案,即标准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部分:唯一的类型列表:

同样,此部分的全部功劳归于@Deduplicator's answer here。由于依靠tuple-cat的实现,以下代码通过在O(log N)时间内在类型列表上进行查找来节省编译时的性能。

代码几乎是令人沮丧的通用编写方式,但是令人高兴的是,它允许您使用任何通用类型列表(tuplevariant,一些自定义名称)。

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进行哈希处理以获得令牌,然后将该令牌与您已对其进行哈希处理的令牌(intfloatcharint)。

第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

结论:

在很多代码中并没有真正的用处,但这可能有助于解决一些受限的元编程问题。