专门针对类满足条件的“std::hash”

Specializing `std::hash` for classes meeting condition

本文关键字:std hash 条件 满足      更新时间:2023-10-16

假设我有一个简单的布尔特征类,MyTrait .也就是说,对于任何类型的T,我都可以做MyTrait<T>::value并得到真或假。我想专注于所有类型的std::hash T MyTrait<T>::value是真的。有什么办法可以做到这一点吗?一些失败的尝试:

template <class T, typename std::enable_if<
                                MyTrait<T>::value, int
                            >::type = 0>
struct hash<T> {
...
}

失败,因为:

error: default template argument in a class template partial specialization

我还尝试将所有部分专业化的东西放在哈希之后,但随后出现一条错误消息,表明T处于非推断上下文中。

有什么办法可以做到这一点吗?至少之前关于SO的一个问题表明没有:将std::hash专门用于派生类。

无论是解决方案,还是明确的"否",然后是简短的解释,都将是一个很好的答案。

不确定它是否合法,但对于 C++20 的概念,您可以执行以下操作:

template <typename T>
concept MyConcept = MyTrait<T>::value;
namespace std
{
    template <MyConcept T>
    struct hash<T>
    {
        std::size_t operator()(const T& t) const { /*..*/ }
        // ...
    };
}

演示

std 命名空间中的给定模板可以专门用于任何用户定义的类型 (1)

为了将 std::hash 专门用于某种类型的 T,如果一个特征是真的,我们可以天真地写这样的东西(注意:不起作用):

namespace std
{
  template<class T>
    struct hash<std::enable_if_t<IsCustomHashable<T>, T>>
    {
       ...
    };
}

它当然不起作用,因为

23 : error: template parameters not deducible in partial specialization:

但即使这样做了,也会使我们面临违反上述(1)的风险。

因为如果有人只是简单地专门化了我们的 IsCustomHashable 元函数,以便它为int返回 true 怎么办?

现在我们将专门针对非用户定义的类型使用 std::hash,这是被禁止的。

一种快速且轻松的方法来执行您想要的操作是从遵循自由函数的帮助程序基类派生 std::hash 专用

#include <functional>
#include <utility>
#include <unordered_set>

// a base class that defers to a free function.
// the free function can be found via ADL and it can be 
// enabled/disabled with enable_if. it's up to you.
template<class T>
  struct impl_hash
  {
    using argument_type = T;
    using result_type = std::size_t;
    result_type operator()(const argument_type& arg) const {
      return hash_code(arg);
    }
  };

// a test class
struct my_hashable
{
  bool operator==(const my_hashable&) const {
    return true;
  }
};
// implement the free function in the same namespace as the argument
// type's definition
std::size_t hash_code(const my_hashable& mh)
{
  // calculate hash here
  return 0;
}
// now defining a hash specialisation becomes easy
// you could even macroify it
namespace std
{
  template<>
    struct hash<my_hashable>
      : impl_hash<my_hashable>
    {
    };
}
// check it compiles
int main()
{
  std::unordered_set<my_hashable> my_set;
  my_set.emplace();
  return 0;
}

他们说计算机科学中的所有问题都可以通过另一个层次的间接来解决。

如果您愿意,我们可以实现 Sean Parent 的运行时多态性技术,该技术使用类型擦除和少量多态性来委托给自由函数。我们可以专门std::hash擦除类型。

用法如下所示:

template<> struct MyTrait<Foo> : std::true_type{};
template<> struct MyTrait<Bar> : std::true_type{};
// ...
Foo a;
Bar b;
Bad c; // MyTrait<Bad>::value is false
std::cout << std::hash<my_hashable>{}(my_hashable{a}) << std::endl;
std::cout << std::hash<my_hashable>{}(my_hashable{b}) << std::endl;
// compiler error
//std::cout << std::hash<my_hashable>{}(my_hashable{c}) << std::endl;

演示

请参考 Sean 的演讲以深入了解该方法,但这里是代码(后面是我的简短解释)。

首先,我们的类型擦除类保存指向任何有自由函数T的指针std::size_t do_hash(const T&)

class my_hashable
{
  public:
  template <class T>
  my_hashable(T& x) : self_(std::make_shared<impl_hashable<T>>(&x))
  {}
  friend std::size_t do_hash(const my_hashable& x)
  {
      return x.self_->hash_();
  }
  private:
  struct base_hashable
  {
    virtual ~base_hashable() = default;
    virtual std::size_t hash_() const = 0;
  }; // base_hashable
  template <class T>
  struct impl_hashable final : base_hashable
  {
    impl_hashable(T* x) : data_(x) { }
    std::size_t hash_() const override
    {
        return do_hash(*data_); // call to free function
    }
    T* data_;
  }; // impl_hashable
 std::shared_ptr<const base_hashable> self_;
}; 

接下来,我们唯一的std::hash专门

化类型擦除类:
namespace std
{
  template<>
  struct hash<my_hashable>
  {
    std::size_t operator()(const my_hashable& h) const{return do_hash(h);}
  };
}

工作原理:

  • my_hashable是一个没有虚拟方法的非模板化类(好)。
  • 它唯一的成员是std::shared_ptr<const base_hashable> self_;其中base_hashable是一个private抽象类,要求子级实现一个函数std::size_t _hash() const
  • impl_hashable是这里的主力;一个模板化类,其实例都派生自bash_hashable,并且它们都将其std::size_t hash_() const override函数委托给接受const T&的自由函数
  • 当我们构造一个具有任意类型Tmy_hashable时,我们获取T的地址并使用该指针构造一个impl_hashable<T>。我们将此impl_hashable隐藏在指向基类base_hashable的指针中。
  • 调用do_hash(const my_hashable&)将通过动态调度将调用委托给相应的impl_hashable hash_函数。
  • 我们只需要专门std::hash my_hashable,并把它委托给my_hashabledo_hash朋友功能。

我的方法与 Sean 的方法有点不同,因为类型擦除的对象不拥有我们提供给它的T,而是采用指向预先存在的非拥有指针。这将允许您仅在需要时构建(轻量级)my_hashable

现在我们可以定义我们的自由函数,它仅适用于MyTrait<T>::value true的类型:

template<class T>
std::size_t do_hash(const T& t)
{
    static_assert(MyTrait<T>::value, "Can only call do_hash for types for which MyTrait is true");
    return std::hash<typename T::data_t>{}(t.data);
}

然后,正如我在本文开头所展示的,我们可以定义我们的类并决定哪些满足特征。在这里,T采用FooBar的类型(不是my_hashable,因为我们已经委托给impl_hashable它来恢复我们在构造my_hashable实例时传入的类型)