假设我有一个简单的布尔特征类MyTrait
。也就是说,对于任何类型T
,我都可以执行MyTrait<T>::value
并获得true或false。我想对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
我还尝试在哈希之后放置所有部分特化的东西,但是在explode
处于非推导的上下文中时会出现错误消息。
有没有办法做到这一点?至少有一个关于SO的问题表明不存在:Specializing std::hash to derived classes。
无论是解决方案,还是明确的“不”。然后简要解释将是一个很好的答案。
答案 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 { /*..*/ }
// ...
};
}
答案 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;
请参阅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_hash
为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
个实例)