专门针对类满足条件的“std::hash”
Specializing `std::hash` for classes meeting condition
假设我有一个简单的布尔特征类,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&
的自由函数 - 当我们构造一个具有任意类型
T
的my_hashable
时,我们获取T
的地址并使用该指针构造一个impl_hashable<T>
。我们将此impl_hashable
隐藏在指向基类base_hashable
的指针中。 - 调用
do_hash(const my_hashable&)
将通过动态调度将调用委托给相应的impl_hashable
hash_
函数。 - 我们只需要专门
std::hash
my_hashable
,并把它委托给my_hashable
的do_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
采用Foo
和Bar
的类型(不是my_hashable
,因为我们已经委托给impl_hashable
它来恢复我们在构造my_hashable
实例时传入的类型)
- C++std::hash实现总是确定性的吗
- std::hash for std::chrono::duration
- 为什么不区分大小写适用于 std::unordered_set的 std::hash 函数?
- 错误:未使用不完整的类型'struct std::hash<>'无效
- 无法专门化 std::hash 以unordered_map存储自定义类型
- 为模板类嵌套类定义 std::hash 时出现编译错误
- Return value of std::hash ofr (x86/x64)
- STD :: HASH :: operator() - 例外保证
- STD :: HASH专业化仍然由STD :: Unordered_map未使用
- C++11:是否有理由为什么某些常规类型不应该专门使用“std::hash”
- 重新定义 std::hash 模板结构
- 为什么std::hash不专门用于std::reference_wrapper
- std::hash 是否保证在 stdlib 发行版中相同
- 专门针对类满足条件的“std::hash”
- 如何使用 std::hash 对缓冲区进行哈希处理
- 为类模板的枚举成员定义 std::hash
- 无法对指向成员使用 std::hash 的专用化
- c++11 std::hash函数对象类的线程安全性
- 我可以重写std::hash吗
- const和nonconst类型的部分std::hash专用化