【发布时间】:2013-12-26 17:08:37
【问题描述】:
背景
我正在尝试编写一个class template Hasher,它将以两种不同的方式实现,具体取决于是否为 T 实现了std::hash<T>:
template<typename T>
struct Hasher
{
std::size_t hash( T t ) const;
// implement as A { std::hash<T> h; return h( t ); }
// or B { std::hash<std::string> h; return h( t.to_string() ); }
};
如果std::hash<T>已经专门化了,我想用它。如果没有,我希望T 有一个to_string() 函数来返回一个密钥供我散列。
例如,根据cppreference,如果T 是long long、指针或std::string,我想要版本A。如果它不是列出的标准之一,并且如果用户没有专门针对他自己的类型的std::hash<T>,我希望T 有一个std::string to_string() const 给我打电话。在这种情况下,我想生成版本 B。
问题
如何使用 C++11/type_traits/no-SFINAE 生成正确的实现?
附录
另一种思考方式:
这几乎就像我希望版本 B 成为默认行为(即,如果不存在专门化,则使用版本 B)。
测试 NAWAZ 的解决方案
我刚刚在 gcc 4.8.1 上试用了 Nawaz 的解决方案,因为他首先出现,实际上是我最容易阅读和理解的(更重要的)。
#include <functional>
#include <cassert>
template<typename T>
class Hasher
{
// overloading rules will select this one first... ...unless it's not valid
template<typename U>
static auto hash_impl(U const& u, int)
-> decltype(std::hash<U>().operator()( u ))
{
return std::hash<U>().operator()( u );
}
// as a fallback, we will pick this one
template<typename U>
static auto hash_impl(U const& u, ... )
-> std::size_t
{
return std::hash<std::string>().operator()(u.to_string());
}
public:
auto hash( T t ) const -> decltype( hash_impl(t,0) )
{
return hash_impl( t, 0 );
}
};
struct Foo
{
std::string m_id;
std::string to_string() const { return m_id; }
};
int
main( int argc, char** argv )
{
std::string s{ "Bar" };
Foo f{ s };
long long l{ 42ll };
Hasher<long long> hl;
Hasher<Foo> hf;
Hasher<std::string> hs;
assert( hl.hash( l )==l );
assert( hf.hash( f )==hs.hash( s ));
return 0;
}
测试丹尼尔·弗雷的解决方案
Daniel 的实现也很有趣。通过首先计算我们是否有哈希,我可以使用标签调度来选择我想要的实现。我们有一个很好的模式/关注点分离,这导致代码非常干净。
然而,在has_hash<> 的实现中,decltype 的参数一开始让我很困惑。事实上,它不应该被解读为论点。相反,它是一个表达式列表(逗号分隔的表达式)。我们需要遵守here所表达的规则。
C++ 确保每个表达式都被计算并且它的边 效果发生。但是,整个逗号分隔的值 表达式只是最右边表达式的结果。
另外,void() 的使用起初对我来说是个谜。当我将其更改为 double() 以查看会发生什么时,很清楚为什么它真的应该是 void()(因此我们不需要传入第二个模板参数)。
#include <functional>
#include <cassert>
template< typename, typename = void >
struct has_hash
: std::false_type {};
template< typename T >
struct has_hash< T, decltype( std::hash< T >()( std::declval< T >() ), void() ) >
: std::true_type {};
template<typename T>
class Hasher
{
static std::size_t hash_impl(T const& t, std::true_type::type )
{
return std::hash<T>().operator()( t );
}
static std::size_t hash_impl(T const& t, std::false_type::type )
{
return std::hash<std::string>().operator()(t.to_string());
}
public:
std::size_t hash( T t ) const
{
return hash_impl( t, typename has_hash<T>::type() );
}
};
struct Foo
{
std::string m_id;
std::string to_string() const { return m_id; }
};
int
main( int argc, char** argv )
{
std::string s{ "Bar" };
Foo f{ s };
long long l{ 42ll };
Hasher<long long> hl;
Hasher<Foo> hf;
Hasher<std::string> hs;
assert( hl.hash( l )==l );
assert( hf.hash( f )==hs.hash( s ));
return 0;
}
【问题讨论】:
标签: c++ templates c++11 typetraits