使std的数据结构使用我现有的非静态散列函数“hashCode()”。默认 [英] Make std's data-structure use my existing non-static hash function "hashCode()" by default

查看:135
本文介绍了使std的数据结构使用我现有的非静态散列函数“hashCode()”。默认的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个中等大小的代码库(> 200 .cpp ),它使用函数 hashCode()来返回散列号: -

pre $ b $ {b class {
// ..... .....复杂的东西。 ...
public:size_t hashCode(){/ *散列算法#H01 * /}
};
class B02 {//只是另一个不相关的类
// .....复杂的东西....
public:size_t hashCode(){/ *#H02 * /} //这与
}的名字相同;

我在不同的地方使用过它,例如在我的自定义数据结构中。它很好。

现在,我想让 std :: 数据结构识别散列算法: -



这是我应该做的: - (从 cppreference ,我会称此代码 #D )。

  //#D 
namespace std {
template<>结构散列< B01> {
std :: size_t operator()(const B01& b)const {
/ *散列算法#H01 * /
}
};
}

如果我插入块 #D B01 B02 ,...)中都有c $ c>调用: -

  std :: unordered_set< B01> b01s; 
std :: unordered_set< B02> b02s;

without 传递第二个模板参数,

和我的哈希算法(#H01 )将被调用。 (默认



问题



让它识别我的所有 B01 :: hashCode,B02 :: hashCode,...

我必须插入块 #D 到所有200+ Bxx.h



我可以只需添加一个单个 #D (在顶部标题中?)

,然后从 re -route std :: anyDataStructure 在任何可能的情况下调用 hashCode()

  //伪代码
名称空间std {
模板<>结构散列< X> {
std :: size_t operator()(const X& x)const {// std :: enable_if ??
if(X has hashCode()){//例如T = B01或B02
使此模板具有最高优先级//如何?
返回hashCode();
} else {//例如T = std :: string
不匹配此模板;
}
}
};
}

这听起来像是一个SFINAE问题。



附注: 在SO中最类似的问题并没有问及如何实现这一点。



编辑我是否重构了它?2017年2月3日)




  • 我不知道蛮力重构是否正确路径。我想可能有更好的办法。

  • hashCode()是我的家。我情绪上重视它。

  • 我想尽可能保持我的代码简洁。 std :: 块很脏。

  • 这可能只是我的好奇心。如果我顽固地不重构我的代码,C ++可以走多远?
  • >解决方案1 ​​



    如果您可以创建类 B01 B02 ,...具有虚拟参数的类模板,您可以简单地为具有虚拟模板参数的模板模板使用 std :: hash 的特化。 :

      #include< iostream> 
    #include< unordered_set>

    struct Dummy {};

    模板< class = Dummy>
    class B01 {
    public:size_t hashCode()const {return 0; }
    };
    模板< class = Dummy>
    class B02 {
    public:size_t hashCode()const {return 0; }
    };

    namespace std {
    template< template< class> class TT> struct hash< TT< Dummy>> {
    std :: size_t operator()(const TT< Dummy>& x)const {
    return x.hashCode();
    }
    };
    }

    int main(){
    std :: unordered_set< B01<>>我们;
    (void)us;
    }

    [live demo]

    解决方案二(包含错误/不使用它)

    但我相信你的愿望看起来更像这样:

      #include< iostream> 
    #include< unordered_set>

    class B01 {
    public:size_t hashCode()const {return 0; }
    };

    class B02 {
    public:size_t hashCode()const {return 0; }
    };

    模板< class T,class>
    使用enable_hash = T;

    namespace std {
    template< class T> struct hash {< enable_hash< T,decltype(std :: declval< T>()。hashCode())>> {
    std :: size_t operator()(const T& x)const {
    return x.hashCode();
    }
    };
    }

    int main(){
    std :: unordered_set< B01>我们;
    (void)us;
    }

    [live demo]
    $ b (启发自这个答案

    然而,只要这可以在gcc上工作,它不是真正被c ++标准允许的(但是我也不确定它是否实际上被禁止......)。在这种情况下请参阅线程。

    编辑:

    正如@Barry所指出的,这个gcc行为是 not 由c ++标准强制执行,因此绝对没有担保,即使在下一个gcc版本中它也能工作......它甚至可以被视为一个bug,因为它允许模板的部分特化,事实上没有专门的模板。



    解决方案三(优先)

    特殊化 std :: unordered_set 而不是 std :: hash

      #include< iostream> 
    #include< type_traits>
    #include< unordered_set>

    class specializeUnorderedSet {};

    class B01:public specializeUnorderedSet {
    public:size_t hashCode()const {return 0; }
    };

    class B02:public specializeUnorderedSet {
    public:size_t hashCode()const {return 0; }
    };

    模板< class T>
    struct my_hash {
    std :: size_t operator()(const T& x)const {
    return x.hashCode();
    }
    };

    模板< class ...>
    使用voider = void;

    模板< class T,class = void>
    struct hashCodeTrait:std :: false_type {};

    模板< class T>
    struct hashCodeTrait< T,voider< decltype(std :: declval< T>()。hashCode())>:std :: true_type {};

    namespace std {

    template< class T>
    struct unordered_set< T,typename std :: enable_if< hashCodeTrait< T> :: value&& std :: is_base_of< specializeUnorderedSet,T> :: value,std :: hash< T> :: type,std :: equal_to< T>,std :: allocator< T>:
    unordered_set< T,my_hash< T>,std :: equal_to< T>,std :: allocator< T> {};



    int main(){
    std :: unordered_set< B01>我们;
    (void)us;
    }

    根据这里,它应该是完全有效的。它也可以在 gcc clang icc VS



    为了能够在不干扰的情况下使用代码类的代码我相信我们可以利用ADL规则做出sfinae检查,如果给定的类不涉及std命名空间。你可以在这里找到一个背景 。也感谢干杯和热情。 - Alf 。这种方法可以改变如下:

      #include< utility> 
    #include< unordered_set>
    #include< string>
    #include< type_traits>
    #include< functional>

    模板<班级类型>
    void ref(Type&&&& amp;& amp;班级类型>
    constexpr auto involve_std()
    - > bool
    {
    使用std :: is_same;
    使用std :: declval;
    返回不是is_same< void,decltype(ref(declval< Type&>()))> :: value;
    }

    class B01 {
    public:size_t hashCode()const {return 0; }
    };

    class B02 {
    public:size_t hashCode()const {return 0; }
    };

    模板< class T>
    struct my_hash {
    std :: size_t operator()(const T& x)const {
    return x.hashCode();
    }
    };

    模板< class ...>
    struct voider {
    using type = void;
    };

    模板< class T,class = void>
    struct hashCodeTrait:std :: false_type {};

    模板< class T>
    struct hashCodeTrait< T,typename voider< decltype(std :: declval< T>()。hashCode())> :: type>:std :: true_type {};

    namespace std {

    template< class T>
    struct unordered_set< T,typename std :: enable_if< hashCodeTrait< T> :: value&& < T>,std :: hash< T> :: type,std :: equal_to< T>,std :: allocator< T>:
    unordered_set< T,my_hash< T> ;,std :: equal_to< T>,std :: allocator< T>> {};



    int main(){
    std :: unordered_set< B01> USB01;
    std :: unordered_set< std :: string> USS;
    static_assert(std :: is_base_of< std :: unordered_set< B01,my_hash< B01>>,std :: unordered_set< B01>> :: value,!);
    static_assert(!std :: is_base_of< std :: unordered_set< std :: string,my_hash< std :: string>>,std :: unordered_set< std :: string>> :: value,! );
    (void)usb01;
    (void)uss;
    }

    [gcc test] [clang test] [icc test] [gcc 4.9] [VC]


    I have a moderate-size codebase (>200 .cpp) that use a function hashCode() to return hash number:-

    class B01{  //a class
        //..... complex thing ....
        public: size_t hashCode(){ /* hash algorithm #H01 */}  
    };
    class B02{  //just another unrelated class
        //..... complex thing ....
        public: size_t hashCode(){/* #H02 */}  //This is the same name as above
    };
    

    I have used it in various locations, e.g. in my custom data-structure. It works well.

    Now, I want to make the hash algorithm recognized by std:: data structure:-

    Here is what I should do :- (modified from cppreference, I will call this code #D).

    //#D
    namespace std {
        template<> struct hash<B01> {
            std::size_t operator()(const B01& b) const {
                /* hash algorithm #H01 */
            }
        };
    }
    

    If I insert the block #D (with appropriate implementation) in every class (B01,B02,...), I can call :-

    std::unordered_set<B01> b01s;
    std::unordered_set<B02> b02s;
    

    without passing the second template argument,
    and my hash algorithm (#H01) will be called. (by default)

    Question

    To make it recognize all of my B01::hashCode, B02::hashCode, ...,
    do I have to insert the block #D into all 200+ Bxx.h?

    Can I just add a single block #D (in a top header?)
    and, from there, re-route std::anyDataStructure to call hashCode() whenever possible?

    //pseudo code
    namespace std{
        template<> struct hash<X>   {
            std::size_t operator()(const X& x) const { // std::enable_if??
                if(X has hashCode()){    //e.g. T=B01 or B02       
                    make this template highest priority   //how?
                    return hashCode();
                }else{                   //e.g. T=std::string
                    don't match this template;  
                }
            }
        };
    }
    

    It sounds like a SFINAE question to me.

    Side note: The most similar question in SO didn't ask about how to achieve this.

    Edit (Why don't I just refactor it? ; 3 Feb 2017)

    • I don't know if brute force refactoring is a right path. I guess there might be a better way.
    • hashCode() is my home. I emotionally attach to it.
    • I want to keep my code short and clean as possible. std:: blocks are dirty.
    • It may be just my curiosity. If I stubborn not to refactor my code, how far C++ can go?

    解决方案

    Solution one

    If you can make classes B01, B02, ... class templates with dummy parameters you could simply go along with the specialization of the std::hash for template template that takes dummy template parameter:

    #include <iostream>
    #include <unordered_set>
    
    struct Dummy {};
    
    template <class = Dummy>
    class B01{ 
        public: size_t hashCode() const { return 0; }  
    };
    template <class = Dummy>
    class B02{ 
        public: size_t hashCode() const { return 0; } 
    };
    
    namespace std{
        template<template <class> class TT> struct hash<TT<Dummy>>   {
            std::size_t operator()(const TT<Dummy>& x) const { 
                return x.hashCode();
            }
        };
    }
    
    int main() {
        std::unordered_set<B01<>> us;
        (void)us;
    }
    

    [live demo]

    Solution two (contain error/don't use it)

    But I believe what you desire looks more like this:

    #include <iostream>
    #include <unordered_set>
    
    class B01{ 
        public: size_t hashCode() const { return 0; }  
    };
    
    class B02{ 
        public: size_t hashCode() const { return 0; } 
    };
    
    template <class T, class>
    using enable_hash = T;
    
    namespace std{
        template<class T> struct hash<enable_hash<T, decltype(std::declval<T>().hashCode())>>   {
            std::size_t operator()(const T& x) const { 
                return x.hashCode();
            }
        };
    }
    
    int main() {
        std::unordered_set<B01> us;
        (void)us;
    }
    

    [live demo]

    (Inspired by this answer)

    However as long this can work on gcc it isn't really allowed by the c++ standard (but I'm also not sure if it is actually literally disallowed...). See this thread in this context.

    Edit:

    As pointed out by @Barry this gcc behaviour is not mandated by c++ standard and as such there is absolutely no guaranties it will work even in the next gcc version... It can be even perceived as a bug as it allows partial specialization of a template that in fact does not specialize that template.

    Solution three (preffered)

    Another way could be to specialize std::unordered_set instead of std::hash:

    #include <iostream>
    #include <type_traits>
    #include <unordered_set>
    
    class specializeUnorderedSet { };
    
    class B01: public specializeUnorderedSet { 
        public: size_t hashCode() const { return 0; }  
    };
    
    class B02: public specializeUnorderedSet { 
        public: size_t hashCode() const { return 0; } 
    };
    
    template <class T>
    struct my_hash {
        std::size_t operator()(const T& x) const { 
            return x.hashCode();
        }
    };
    
    template <class...>
    using voider = void;
    
    template <class T, class = void>
    struct hashCodeTrait: std::false_type { };
    
    template <class T>
    struct hashCodeTrait<T, voider<decltype(std::declval<T>().hashCode())>>: std::true_type { };
    
    namespace std{
    
        template <class T>
        struct unordered_set<T, typename std::enable_if<hashCodeTrait<T>::value && std::is_base_of<specializeUnorderedSet, T>::value, std::hash<T>>::type, std::equal_to<T>, std::allocator<T>>:
               unordered_set<T, my_hash<T>, std::equal_to<T>, std::allocator<T>> { };
    
    }
    
    int main() {
        std::unordered_set<B01> us;
        (void)us;
    }
    

    According to the discussion presented here it should be perfectly valid. It also work in gcc, clang, icc, VS

    To be able to use the code without interfering in the code of classes I believe we can utilize the ADL rules to make sfinae check if given class does not involve std namespace. You can find a background here. Credits also to Cheers and hth. - Alf. The approach could be change as follows:

    #include <utility>
    #include <unordered_set>
    #include <string>
    #include <type_traits>
    #include <functional>
    
    template< class Type >
    void ref( Type&& ) {}
    
    template< class Type >
    constexpr auto involve_std()
       -> bool
    {
        using std::is_same;
        using std::declval;
        return not is_same< void, decltype( ref( declval<Type &>() ) )>::value;
    }
    
    class B01 { 
        public: size_t hashCode() const { return 0; }  
    };
    
    class B02 { 
        public: size_t hashCode() const { return 0; } 
    };
    
    template <class T>
    struct my_hash {
        std::size_t operator()(const T& x) const { 
            return x.hashCode();
        }
    };
    
    template <class...>
    struct voider {
        using type = void;
    };
    
    template <class T, class = void>
    struct hashCodeTrait: std::false_type { };
    
    template <class T>
    struct hashCodeTrait<T, typename voider<decltype(std::declval<T>().hashCode())>::type>: std::true_type { };
    
    namespace std{
    
        template <class T>
        struct unordered_set<T, typename std::enable_if<hashCodeTrait<T>::value && !involve_std<T>(), std::hash<T>>::type, std::equal_to<T>, std::allocator<T>>:
               unordered_set<T, my_hash<T>, std::equal_to<T>, std::allocator<T>> { };
    
    }
    
    int main() {
        std::unordered_set<B01> usb01;
        std::unordered_set<std::string> uss;
        static_assert(std::is_base_of<std::unordered_set<B01, my_hash<B01>>, std::unordered_set<B01>>::value, "!");
        static_assert(!std::is_base_of<std::unordered_set<std::string, my_hash<std::string>>, std::unordered_set<std::string>>::value, "!");
        (void)usb01;
        (void)uss;
    }
    

    [gcc test], [clang test], [icc test] [gcc 4.9] [VC]

    这篇关于使std的数据结构使用我现有的非静态散列函数“hashCode()”。默认的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆