kfm*_*e04 7 c++ templates type-traits c++11
背景
我试着写一个class template Hasher
将以两种不同的方式实现,具体取决于是否std::hash<T>
已经为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() ); }
};
Run Code Online (Sandbox Code Playgroud)
如果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的解决方案
我刚刚尝试了Nawaz在gcc 4.8.1上的解决方案,因为他是第一个进入的,实际上对我来说是最容易阅读和理解的(更重要的).
#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;
}
Run Code Online (Sandbox Code Playgroud)
测试丹尼尔弗雷的解决方案
丹尼尔的实施也非常有趣.通过首先计算我们是否有哈希,我能够使用tag-dispatch来选择我想要的实现.我们有一个很好的模式/关注点分离,这导致非常干净的代码.
但是,在执行中has_hash<>
,decltype
起初让我困惑的论点.实际上,它不应该作为参数阅读.相反,它是一个表达式列表(逗号分隔的表达式).我们需要遵循这里表达的规则.
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;
}
Run Code Online (Sandbox Code Playgroud)
Naw*_*waz 10
您可以使用C++ 11引入的Expression SFINAE.
以下是如何实现它的一个示例:
template<typename T>
struct Hasher
{
auto hash( T t ) const -> decltype(hash_impl(t,0))
{
return hash_impl(t, 0);
}
private:
template<typename U>
static auto hash_impl(U const & u, int) -> decltype(std::hash<U>().hash(u))
{
return std::hash<U>().hash(u);
}
template<typename U>
static auto hash_impl(U const & u, ... ) -> std::string
{
return u.to_string();
}
};
Run Code Online (Sandbox Code Playgroud)
请注意,这hash_impl
是一个重载的函数模板.所以当你写这个:
return hash_impl(t, 0);
Run Code Online (Sandbox Code Playgroud)
由于第二个参数0
是int
,上面的第一个 尝试调用hash_impl
哪个使用std::hash
- 如果std::hash<U>().hash(u)
不是有效表达式(表达式SFINAE),则此尝试可能会失败.如果失败,则hash_impl
调用第二个.
您可以测试是否std::hash<T>()(...)
可以使用相关类型调用.如果是这样,您将返回一个类型decltype()
,可以在SFINAE表达式中使用该类型来确定返回类型的大小:
template <typename T>
struct has_hash
{
template <typename S>
static char (&test(S*, decltype(std::hash<S>()(std::declval<T&>()))* = 0))[1];
static char (&test(...))[2];
static constexpr bool value = 1 == sizeof(test(static_cast<T*>(0)));
};
Run Code Online (Sandbox Code Playgroud)
在此基础上,您可以使用它has_hash<T>::value
来确定是否已经定义了可用的哈希函数.
如果您需要测试表达式是否有效,我更喜欢以下实现:
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() ) >
// ^^ expression here ------------------^^
: std::true_type {};
Run Code Online (Sandbox Code Playgroud)