如何使用type_traits生成依赖于类专门化的代码?

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,如果Tlong 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)

由于第二个参数0int,上面的第一个 尝试调用hash_impl哪个使用std::hash- 如果std::hash<U>().hash(u)不是有效表达式(表达式SFINAE),则此尝试可能会失败.如果失败,则hash_impl调用第二个.


Die*_*ühl 5

您可以测试是否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来确定是否已经定义了可用的哈希函数.

  • 你可以让重载返回std :: true_type和std :: false_type,这会让一切变得更好.像大蒜. (5认同)

Dan*_*rey 5

如果您需要测试表达式是否有效,我更喜欢以下实现:

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)

实例