11

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?
Community
  • 1
  • 1
javaLover
  • 6,347
  • 2
  • 22
  • 67
  • 1
    What would be the purpose of SFINAE? I don't see any here. – O'Neil Jan 29 '17 at 03:34
  • @O'Neil From above pseudo code ... **My objective:** If T has `hashCode()`, std will call `hashCode()`, otherwise it should call other appropriate things (if any). **In your solution**, `my_unordered_set` will have compile error if T doesn't have `hashCode()` (e.g. T=std::string). That is where "not an error" come to play. .... I think it is the only way to make it "default" hash. (right?) – javaLover Jan 29 '17 at 03:38
  • @W.F. Your solution is useful and has a unique strong point. It is actually a perfect solution for gcc and have a great link. It's a pity that you deleted it. – javaLover Feb 02 '17 at 03:52
  • 1
    @Douglas Daseeco It is roughly how many locations of code need to be refactored. For example, in your solution, I have to insert : `public AbstractB` for every class `Bxx`, so I have to edit it = `B` places. Therefore, the cost of refactoring = `someConstant*B`. Is the measurement criteria obscure/bad? If so, please say! I don't mind deleting the whole last part. – javaLover Feb 02 '17 at 12:01
  • @Douglas Daseeco No!! I get your point. It is my bad that I didn't review the code carefully. I will delete the last part temporary. Thank for starting this constructive argument! – javaLover Feb 02 '17 at 12:31
  • 1
    @FauChristian Thank for sharing. :) Refactoring itself is not hard. I agree. That most scariest is this situation : "I have spent 3-7 days of the tiresome and exhaustive refactoring and testing, just to found that it is totally useless." That is a tragedy, at least for me. My underlying objective is to reduce amount of such tragedies per year, so I usually try to make my code neutral/general as possible. – javaLover Feb 02 '17 at 16:06
  • :) I've been there. I honor you for wanting to improve the development velocity. Too many people are not willing to look at lost time, find its root cause of causes, and look for tools and techniques to reduce it for future projects. Perhaps, though, had you not spent the 3-7 days the code might have been completely discarded later because no one could figure it out in 3-7 months. Your time may actually be the opposite of waste. – Douglas Daseeco Feb 02 '17 at 20:27
  • @javaLover I've updated the answer and undelete it. I've also add the motivation that led me to delete it. – W.F. Feb 03 '17 at 11:37
  • @Douglas Daseeco If it is not a hard work and not disturb you too much, may you provide a O(1) solution, please? There are currently no one (including me) that can do O(1) && also work on VC (not just gcc). – javaLover Feb 04 '17 at 08:10
  • 1
    @Douglas Daseeco Using `SNIFAE` is usually/often `anti-pattern`, is it what you mean? (... fast comment upvote, I assume that it is from you ...) Ok, get it! Nonetheless, I love `SNIFAE`. – javaLover Feb 04 '17 at 10:25
  • 1
    @javaLover I think I found a promising [way](http://stackoverflow.com/questions/34974844/check-if-a-type-is-from-a-particular-namespace) to use my third approach in O(1) – W.F. Feb 04 '17 at 11:52
  • 1
    @javaLover please see the edit – W.F. Feb 04 '17 at 12:42
  • 1
    @javaLover to be precise it is O(K) where K is the number of namespaces containing Bxx classes. But when there is only one namespace or finite constant number it became O(1) – W.F. Feb 04 '17 at 15:01
  • @W.F. Thank. I am *a little* prefer O(C) where C is amount of type of std::datastructure. My project is currently have 1 namespace, but I don't know how much it will be in the future. However, I am so impressed that it is very different and unique! I will surely test it. :) – javaLover Feb 04 '17 at 15:05
  • 1
    @javaLover you might be interested - I opened a new [question](http://stackoverflow.com/questions/42042563/is-it-possible-to-create-a-trait-to-answer-if-a-type-comes-from-std) to find out if one can create a trait to answer if given type is from std namespace. If there is a way O(1) is still reachable :) – W.F. Feb 04 '17 at 16:33
  • @Douglas Daseeco It is the first time that my question receives a lot of good answers. I feel there is invisible pressure that I must be honest to the question - I should award the most direct solution. I accept that I agree with you about SFINAE. I still believe just using it a few times (<4) in a 30K+line 1-person project can't cause serious harm. As a beginner, I can be seriously wrong though. I will read your diagram link in my bedtime because I can concentrate it. :) – javaLover Feb 05 '17 at 08:12

5 Answers5

11

It doesn't have to be that way, you can also have a functor:

struct MyHash {
    template <class T>
    auto hashCode(const T & t, int) const -> decltype(t.hashCode()) {
        return t.hashCode();
    }
    template <class T>
    auto hashCode(const T & t, long) const -> decltype(std::hash<T>{}(t)) {
        return std::hash<T>{}(t);
    }
    
    template <class T>
    auto operator()(const T & t) const -> decltype(hashCode(t,42)) {
        return hashCode(t,42);
    }
};

And have an alias of std::unordered_set with MyHash as hash type:

template <class Key>
using my_unordered_set = std::unordered_set<Key, MyHash>;

or more complete if you also want to be able to provide Equal functor and allocator:

template<
    class Key,
    class KeyEqual = std::equal_to<Key>,
    class Allocator = std::allocator<Key>
>
using my_unordered_set = std::unordered_set<Key, MyHash, KeyEqual, Allocator>;

Then using it (with any of your Bxx) like you'd use std::unordered_set:

int main() {
    my_unordered_set<B01> b01s;
    my_unordered_set<B02> b02s;

    // or lonely with your type:
    B01 b01{/*...*/};
    std::cout << MyHash{}(b01) << std::endl;

    // or any other:
    std::string str{"Hello World!"};
    std::cout << MyHash{}(str) << std::endl;
}

Concepts

If you can use concepts, they can allow you to specialize std::hash class the way you want:

template <class T>
concept HashCodeConcept = requires(T const & t)
{
    {t.hashCode()} -> std::same_as<std::size_t>;
};

namespace std {
    template <HashCodeConcept T>
    struct hash<T> {
        std::size_t operator()(const T& t) const {
            return  t.hashCode();
        }
    };
}
O'Neil
  • 3,790
  • 4
  • 16
  • 30
  • Thank. That is a kind of answer I am looking for. In this solution, according to [a reference](http://en.cppreference.com/w/cpp/utility/hash), I have to pass `MyHash` as a second template argument of `std::unordered_set`. It is not so convenient. Is there a way to make `MyHash` to be a "default" hash? – javaLover Jan 26 '17 at 05:23
  • 1
    @javaLover I'm afraid I don't see any. I think the best you can do is to use an alias type. – O'Neil Jan 26 '17 at 05:31
  • That is the alias that you mentioned. If you happen to know how to make it "default", feel free to ping me again. :) – javaLover Jan 29 '17 at 03:25
  • After the edit, it sounds nice. `int/long` (near 42) is a trick, right? However, with my limited capability, I can barely understand the logic. I can't make it compile. http://coliru.stacked-crooked.com/a/cadc4a4aace80110 – javaLover Jan 29 '17 at 04:05
  • 1
    Don't forget the `const` qualifier of your `hashCode()` member function. [DEMO](http://coliru.stacked-crooked.com/a/b204ed83b477b2d5) – O'Neil Jan 29 '17 at 04:07
  • Wow, it works! You really use `int/long` trick. Where can I learn more about these fitness-tricks? You mimic only 1 if-else branch, is it possible to create a more complex nest (around 10) from the technique? What keywords should I search for? Is it a kind of SFINAE? – javaLover Jan 29 '17 at 04:21
  • 1
    It uses SFINAE (auto suffix/decltype) + overload resolution (int/long). As I pass the 42 `int`, overload with `int` is prefered (match against `int` -> `long` conversion), and SFINAE discards it (or not) to fall for the `long` one. If more branches, you'd rather prefer to use *tag dispatching* (see the 4 parts [here](http://foonathan.net/blog/2015/10/16/overload-resolution-1.html)) – O'Neil Jan 29 '17 at 04:39
  • 1
    @javaLover see [this](http://stackoverflow.com/questions/31213516/stdhash-specialization-using-sfinae/31213703#31213703) but [be careful](http://stackoverflow.com/questions/33257292/specializing-stdhash-for-derived-classes-works-in-gcc-not-clang) – W.F. Feb 01 '17 at 09:59
  • @FauChristian Concepts are currently only available with GCC, with option `-fconcepts`. See [DEMO](http://coliru.stacked-crooked.com/a/44334b3910fa64fe). – O'Neil Feb 01 '17 at 18:03
  • 1
    @FauChristian Yes, using GCC 6.3.0 with MinGW. Coliru is also currently using GCC 6.3.0. Both with `__cpp_concepts` macro = 201500. – O'Neil Feb 01 '17 at 20:13
  • 1
    I can't make it compile for C++11, but C++14 is ok. Is this code for C++14 and above? The location of error is around "auto" as declared return type of the 2nd function in `MyHash`. Test it here: http://cpp.sh/4vmtc Here is another backup https://ideone.com/rke61d – javaLover Feb 04 '17 at 03:58
  • 1
    @javaLover It's C++14 and just missing the suffix type for C++11. Add `-> decltype(std::hash{}(t))` after the `const` qualifier and it will compile for C++11 (edited). – O'Neil Feb 04 '17 at 07:04
  • 1
    @FauChristian See 7th comment. 42 is just a random int. – O'Neil Feb 05 '17 at 01:36
8

While creating conditions to default the hash parameter of std container templates to member methods of groups of classes, one should avoid introducing new issues.

  • Redundancy
  • Portability problems
  • Arcane constructs

The classic object oriented approach may require a patterned edit of the 200+ classes to ensure they provide the basics of std::hash container use. Some options for group transformation are given below to provide the two needed methods.

  • A public hashCode() is defined in the concrete class where it is unique to that class or by inheritance if it follows a pattern common across classes.
  • A public operator==() is defined.

The Two Templates

These two templates will remove the redundancy and simplify the declaration as indicated.

template <typename T>
    struct HashStruct {
        std::size_t operator()(const T & t) const {
            return t.hashCode();
        } };
template <class T>
    using SetOfB = std::unordered_set<T, HashStruct<T>>;

Saving Integration Time

An example super-class:

class AbstractB {
    ...
    virtual std::size_t hashCode() const {
        return std::hash<std::string>{}(ms1)
                ^ std::hash<std::string>{}(ms2);
    } }

The following sed expression may save transformation time, assuming the code uses { inline. Similar expressions would work with Boost or using a scripting language like Python.

"s/^([ \t]*class +B[a-zA-Z0-9]+ *)(:?)(.*)$"
        + "/\1 \2 : public AbstractB, \3 [{]/"
        + "; s/ {2,}/ /g"
        + "; s/: ?:/:/g"

An AST based tool would be more reliable. This explains how to use clang capabilities for code transformation. There are new additions such as this Python controller of C++ code transformation.

Discussion

There are several options for where the hash algorithm can reside.

  • A method of a std container declaration's abstract class
  • A method of a concrete class (such as #H01 in the example)
  • A struct template (generally counterproductive and opaque)
  • The default std::hash

Here's a compilation unit that provides a clean demonstration of the classic of how one might accomplish the desired defaulting and the other three goals listed above while offering flexibility in where the hash algorithm is defined for any given class. Various features could be removed depending on the specific case.

#include <string>
#include <functional>
#include <unordered_set>

template <typename T>
    struct HashStructForPtrs {
        std::size_t operator()(const T tp) const {
            return tp->hashCode(); } };
template <class T>
    using SetOfBPtrs = std::unordered_set<T, HashStructForPtrs<T>>;

template <typename T>
    struct HashStruct {
        std::size_t operator()(const T & t) const {
            return t.hashCode(); } };
template <class T>
    using SetOfB = std::unordered_set<T, HashStruct<T>>;

class AbstractB {
    protected:
        std::string ms;
    public:
        virtual std::size_t hashCode() const {
            return std::hash<std::string>{}(ms); }
        // other option: virtual std::size_t hashCode() const = 0;
        bool operator==(const AbstractB & b) const {
            return ms == b.ms; } };

class B01 : public AbstractB {
    public:
        std::size_t hashCode() const {
            return std::hash<std::string>{}(ms) ^ 1; } };

class B02 : public AbstractB {
    public:
        std::size_t hashCode() const {
            return std::hash<std::string>{}(ms) ^ 2; } };

int main(int iArgs, char * args[]) {

    SetOfBPtrs<AbstractB *> setOfBPointers;
    setOfBPointers.insert(new B01());
    setOfBPointers.insert(new B02());

    SetOfB<B01> setOfB01;
    setOfB01.insert(B01());

    SetOfB<B02> setOfB02;
    setOfB02.insert(B02());

    return 0; };
Douglas Daseeco
  • 3,475
  • 21
  • 27
  • 2
    I have to add `template<> struct hash` for every `Bxx` I currently have, so it is still very tedious. But, hey, thank for trying and sharing! – javaLover Feb 01 '17 at 06:56
  • 2
    Thank. I thought I am just dumb that can't find a way to solve this issue elegantly. It is also good to know that some languages don't have this issue. I can somehow rest in peace. :) – javaLover Feb 01 '17 at 07:20
  • Yes, this is better. Thank. The downside (not specific only to this answer) is that I can't use multiple inheritance easily e.g. `class B03: public B01 , public B02{}`. https://ideone.com/dBElyN ('Set' is ambiguous) – javaLover Feb 02 '17 at 02:40
  • 1
    Agree, it is just a really minor disadvantage. When I select a solution, I tend to be (too) skeptical. Actually, I don't think my design really need it. I just don't have time to add "really minor" to my previous comment, sorry. – javaLover Feb 02 '17 at 03:43
  • The solution (revision 9) is similar as ONeil's answer, right? Also, as you mention about trying a O(1) solution, please make sure that it works for Visual C++ (i.e. not just gcc). W.F. (a deleted answer) have already tried it, but it works only on gcc. – javaLover Feb 02 '17 at 12:41
  • 1
    This answer is our choice. We are adapting the idiom to fix an issue in one of our embedded libraries that uses a non std container. ---- The answer's example code compiles without error or warning with -std=c++11 -Wall for compilers g++ 6.3.1, x86_64-w64-mingw32-g++ 6.3.0, clang-x86_64++ 3.8.0, Visual C++ 2015 Community, and the Atmel cross compiler we use. ---- It's lightweight, easily extensible, and works with or without a base class. Thank you, Douglas Daseeco. – Douglas Daseeco Feb 05 '17 at 07:04
7

A SFINAE based method of the type you were looking for requires partial specialisation of std::hash. This could be done if your classes Bxx are templates (which is the case if they are derived from a CRTP base). For example (note fleshed out in edit)

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

template<typename T = void>
struct B {
  B(int i) : x(i) {}
  std::size_t hashCode() const
  {
    std::cout<<"B::hashCode(): return "<<x<<std::endl;
    return x;
  }
  bool operator==(B const&b) const
  { return x==b.x; }
private:
  int x;
};

template<typename T,
         typename = decltype(std::declval<T>().hashCode())> 
using enable_if_has_hashCode = T;

namespace std {
  template<template<typename...> class T, typename... As> 
  struct hash<enable_if_has_hashCode<T<As...>>> 
  {
    std::size_t operator()(const T<As...>& x) const
    { return x.hashCode(); }
  };
  // the following would not work, as its not a partial specialisation
  //    (some compilers allow it, but clang correctly rejects it)
  // tempate<typename T>
  // struct hash<enable_if_hashCode<T>>
  // { /* ... */ }; 
}

int main()
{
  using B00 = B<void>;
  B00 b(42);
  std::unordered_set<B00> set;
  set.insert(b);
}

produces (using clang++ on MacOS)

B::hashvalue(): return 42

see also this related answer to a similar question of mine.

However, concepts are the way of the future to solve problems like this.

Community
  • 1
  • 1
Walter
  • 44,150
  • 20
  • 113
  • 196
  • @FauChristian Sorry, that `TT` was left from a previous version, forgot to change it in editing. fixed now. I hope its clearer now. – Walter Feb 04 '17 at 11:20
4

I have come up with something that appears to partially work. It is a workaround that will allow you to use std::hash on a type that implements hashCode. Take a look:

   //some class that implements hashCode
struct test
{
    std::size_t hashCode() const
    {
        return 0;//insert your has routine
    }
};
//helper class
struct hashable
{
    hashable():value(0){}
    template<typename T>
    hashable(const T& t):value(t.hashCode())
    {}
    template<typename T>
    std::size_t operator()(const T& t) const
    {
        return t.hashCode();
    }

    std::size_t value;
};


//hash specialization of hashable
namespace std {
    template<>
    struct hash<hashable>
    {
        typedef hashable argument_type;
        typedef std::size_t result_type;
        result_type operator()(const argument_type& b) const {
            return b.value;
        }
    };
}
//helper alias so you dont have to specify the hash each time.
template<typename T, typename hash = hashable>
using unordered_set = std::unordered_set<T,hash>;

int main(int argc, char** argv)
{
    unordered_set<test> s;
    test t;
    std::cout<<std::hash<hashable>{}(t)<<std::endl;
}

The code takes advantage of hashable's template constructor and template operator to retrieve the hash from any class that implements hashCode. The std::hash specialization is looking for an instance of hashable but the templated constructor allows an instance to be constructed from a class that has hasCode.

The only gotcha here is that you will have to write unordered_set rather than std::unordered_set to use it and you will have to make sure that std::unordered_set is not brought into scope in any way. So you wont be able to have anything like using namespace std or using std::unordered_set in your source. But besides the few gotchas in the usage this could work for you.

Of course this is just a band-aid on the real issue... which would be not wanting to go through the pain of properly specializing std::hash for each of your types. (I don't blame you)

I would also like to note that with this code substitution is an error... if you would prefer SFINAE it will need modification.

EDIT:

After trying to run:

unordered_set<test> s;
test t;
s.insert(t);

I noticed there were some compiler errors.

I've updated my test class to be equality comparable by adding:

bool operator==(const test& other) const
{
    return hashCode() == other.hashCode();
}

to test which now makes:

//some class that implements hashCode
struct test
{
    std::size_t hashCode() const
    {
        return 0;//insert your has routine
    }
    bool operator==(const test& other) const
    {
        return hashCode() == other.hashCode();
    }
};
Alex Zywicki
  • 2,263
  • 1
  • 19
  • 34
  • Although it is not SFINAE, it would be somehow useful. Can you make it run in ideone.com? I am testing it, and analyzing how this solution work. – javaLover Jan 28 '17 at 09:01
  • I just understand what "alias" mean from your code, thank! In this solution, I can't `s.insert(t);`, right? If I add only this single statement, I will get compile error. – javaLover Jan 28 '17 at 09:44
  • I didn't test it out beyond getting what I posted to compile. The fact that an alias is used shouldn't have an effect on inserting elements into the set. It is possible that the workaround I provided may only be enough to allow the container to default construct itself. there may be more needed to make sure that that the containers other functions work properly. Unfortunately since `unordered_map` is a template you will have to try each of the classes members one by one to make sure they work, because each function isn't actually compiled unless it gets used somewhere. – Alex Zywicki Jan 28 '17 at 09:52
  • 1
    after my edit you should now be able to call insert using my example code. It looks like `std::unordered_map` may require that its elements are equality comparable in order to prevent hash collision or something like that. Adding the `operator==` to the `test` did the trick – Alex Zywicki Jan 28 '17 at 09:59
  • It would be worth trying to look up the type requirement for elements of an `unordered_se`t to make sure that your types meet the requirements – Alex Zywicki Jan 28 '17 at 10:05
  • Just to make sure, in this solution: I also have to "alias" type by type (e.g. `unordered_set`, `unordered_map`, ... so on) manually ... correct? – javaLover Jan 28 '17 at 11:11
  • 1
    You do have to manually provide the alias, but only one per container you plan to use rather than one per class that you will use in the containers. So there should be a lot less work to do. – Alex Zywicki Jan 28 '17 at 13:30
3

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]

Community
  • 1
  • 1
W.F.
  • 13,888
  • 2
  • 34
  • 81
  • 1
    Just ask for confirmation, because `B01` is now a template class, many (usually all) implementation of `B01`'s function will have to move from `.cpp` to header, right? – javaLover Feb 01 '17 at 10:46
  • In the 2nd version, `std::unordered_set` will cause compile error, right? http://melpon.org/wandbox/permlink/kusRAUV1Ui3w0Dhf – javaLover Feb 01 '17 at 10:55
  • Ha ha, nice attempt. Thank. +1 The rant in the link is also useful. – javaLover Feb 01 '17 at 10:58
  • 2
    It's literally disallowed - partial specializations have to be more specialized, and yours isn't. – Barry Feb 01 '17 at 15:38
  • @Barry so you're saying it is a gcc bug? – W.F. Feb 01 '17 at 16:16
  • @Barry my understanding of specialization is that it accept fewer types... so in this terms hash presented in example 2 is actually specialization... no? – W.F. Feb 01 '17 at 16:32
  • @Barry but some `T` were filtered by sfinae – W.F. Feb 01 '17 at 16:37
  • 1
    @W.F. Yes, the argument list of your specialization is still `template struct hash { ... }`, that's identical to the primary. The reason the `void_t` trick works is that the primary is really `template `, so the specialization on `` is specialized. – Barry Feb 01 '17 at 16:37
  • Now, I understand why you deleted it. Thank for recovering it back. This answer is like a simple beauty is a glass globe of another world that worth caring. If it was deleted again, I will not nag you - I understand. :( – javaLover Feb 03 '17 at 12:30
  • @Barry Hi Barry, may you provide some references, please? I believe it will greatly extend my knowledge about template specialization. – javaLover Feb 03 '17 at 12:38
  • You have never stopped surprising me. After a fast glimpse, if this version (revision 6) works, it will be a very good (if not the best) solution. I will test it more, thank. .... By the way, why all of your snippets have `(void)us;`? What is it mean? I have been curious. – javaLover Feb 03 '17 at 15:59
  • @javaLover it is just to avoid unused variable warning – W.F. Feb 03 '17 at 16:01
  • @FauChristian ops yes I forgot this is c++11 not c++14 question it should be easily fixable, thanks! – W.F. Feb 03 '17 at 20:17
  • @FauChristian fixed – W.F. Feb 03 '17 at 20:22
  • 2
    @FauChristian according to c++ standard 17.6.4.2.1 `A program may add a template specialization for any standard library template to namespace std only if the declaration depends on a user-defined type and the specialization meets the standard library requirements for the original template and is not explicitly prohibited.` This code fragment meet the requirements... I actually added the discussion on this in a link below the code – W.F. Feb 03 '17 at 20:24
  • @FauChristian Fair enough, you think my edit would be sufficient to fix the issue? – W.F. Feb 03 '17 at 20:33
  • 2
    Certainly the C++11 issue is fixed. Since class isB() isn't connected with Bxx classes, an accurate name would be specializeUnorderedSet(). When the standards committee wrote, "depends on a user-defined type," did not extend the exception to, "the statements following a location in a compilation unit." If you connect your specialization to a B super-type, the code will be on solid ground and transparent. Risk of misuse would be low. No bad precedence would be established. ... Agile (prudently) encourages design modification over introducing somewhat obscure constructs to avoid it. – Douglas Daseeco Feb 03 '17 at 21:22
  • 1
    Yes, and, although it introduces the need to make some changes to the header files, that can be done with some code processor. Now any redundancy across all Bs, such as a default B-specific hash or == operator or pure virtual versions of them or some other thing that would otherwise be redundant across Bs can be added. Most importantly, any future development using an IDE or code navigation tools can trace the connection between Bs and the B specialization with ease. – Douglas Daseeco Feb 03 '17 at 23:28
  • Just ask for a confirmation after an extensive test. .... In this version (revision 9), the cost of refactoring now linearly depends on amount of types of `std::datastructure` that I want to support. The old shiny feature (revision 5 : one code block works for every `std::datastructure`) was killed. [This ideone](https://ideone.com/YzF6a8) is the evidence. Correct? – javaLover Feb 04 '17 at 02:14
  • 1
    @javaLover Unfortunetly yes but it covers corner cases like when std would implement hashCode in some of its class. It is very unlikely this is why I give a bet revision 5 would be sufficient, but to be politicly correct I added the revisions... – W.F. Feb 04 '17 at 03:10
  • W.F.'s quote of C++ 17.6.4.2.1 above refers to a boundary set by the C++ standards committee well known in JavaScript and Ruby circles. One can change the behavior of standard entities with good intention, but what was once reliable may later become the cause of obscure and confounding bugs. Even worse, once a precedence is established, the habit of restraint begins to erode. Back when C++ was criticized for promoting poor habits, Sun promoted Java stating that they solved the problem of sloppy programming by restricting the language. W.F.'s revisions were practical and wise. – Douglas Daseeco Feb 04 '17 at 07:49
  • @FauChristian on the other hand if the standard library committee will decide to use public hashCode method to count hash in unordered collections none of these approaches from answers would be even needed :) – W.F. Feb 04 '17 at 08:43
  • 1
    Very true. :) I'm not saying that committees are infallible and I'm certainly not claiming that committee work is free of politics. I will say that, in the specific sentence you quoted, the standards body position is widely held across many languages and design philosophies and makes practical sense. That is the hope, right? That eventually the politics will give way to statistically sound best practices. Even if not, companies that hire sloppy programmers eventually go bankrupt trying to maintain their buggy IT infrastructure. I have older colleagues that tell those stories. – Douglas Daseeco Feb 04 '17 at 08:59
  • @FauChristian by utilizing ADL rules I think one can test if the class is in a given namespace without additional inheritance. What do you think? – W.F. Feb 04 '17 at 12:54
  • In the last version, I can't get it work for std::string. http://cpp.sh/7a3oc insert(std::string) = compile error. – javaLover Feb 05 '17 at 03:39
  • The more I tested your solutions, the more I think that my question is bad. (http://rextester.com/NRR39285 , https://ideone.com/m5oMiO) Your solution is now a Frankenstein - my VS mark "insert" as red, yet it works! Moreover, I read [your new good question](http://stackoverflow.com/q/42042563/3577745) ..... OK, I surrender now. It is just a bad practice. I will accept your answer, as long as no one can beat it. Thank for your very hard work. I am looking forward to your next innovation. Meanwhile, I will read other posts of yours. – javaLover Feb 06 '17 at 11:04
  • @javaLover yep probably due to this I got deserved downvotes. As the approach is built on several workarounds some people may find it tricky and as such - bad. Lets face it - c++ designers blocked this functionality on purpose. However my intentions are pure - I was not attempted to cheat the c++ design but rather to discover the c++ limits and I believe I'm not the only one here :) Hope Frankenstein Monster won't do any harm ;) – W.F. Feb 06 '17 at 11:23