专门为符合条件的类提供`std :: hash`

时间:2016-04-21 15:47:33

标签: c++ templates c++11

假设我有一个简单的布尔特征类MyTrait。也就是说,对于任何类型T,我都可以执行MyTrait<T>::value并获得true或false。我想对std::hashT的所有类型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

我还尝试在哈希之后放置所有部分特化的东西,但是在explode处于非推导的上下文中时会出现错误消息。

有没有办法做到这一点?至少有一个关于SO的问题表明不存在:Specializing std::hash to derived classes

无论是解决方案,还是明确的“不”。然后简要解释将是一个很好的答案。

3 个答案:

答案 0 :(得分:5)

不确定这是合法的,但是使用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 { /*..*/ }
        // ...
    };

}

Demo

答案 1 :(得分:2)

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

为了特殊化某些类型T的std :: hash,如果特征为真,我们可以天真地写这样的东西(注意:不起作用):

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;
}

答案 2 :(得分:1)

他们说,计算机科学中的所有问题都可以通过另一种间接解决方案来解决。

如果愿意,我们可以实现Sean Parent的Runtime Polymorphism技术,该技术使用类型擦除和少量的多态性来委派给自由函数。我们可以将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;

Demo

请参阅Sean's talk,以深入了解该方法,但这是代码(后面附有我的简短解释)。

首先,我们的类型擦除类持有指向任何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进行 only 专门化:

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&的自由函数
  • 当我们构造具有任意类型my_hashable的{​​{1}}时,我们将获取T的地址,并使用该指针构造一个T。我们将此impl_hashable<T>隐藏在指向基类impl_hashable的指针中。
  • 调用base_hashable将通过动态调度将调用委派给适当的do_hash(const my_hashable&)的{​​{1}}函数。
  • 我们只需要对impl_hashable上的hash_进行专门化,并将其委托给std::hash的{​​{1}}朋友功能。

我的方法与Sean有所不同,因为类型擦除的对象不拥有我们赋予它的my_hashable,而是采用了一个非所有者的指针来指向已存在的对象。这将使您仅在需要时才构造(轻型)my_hashable

现在我们可以定义自由功能,该功能仅适用于do_hashT的类型:

my_hashable

然后,正如我在本文开头所显示的,我们可以定义我们的类并确定哪些类满足该特征。在这里,MyTrait<T>::value具有truetemplate<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的类型,因为我们已经委托给Foo来恢复构造构造函数时传入的类型。 Bar个实例)