Сделать структуру данных std использовать мою существующую нестатическую хэш-функцию "hashCode ()" по умолчанию

у меня есть кодовая база среднего размера (>200 .cpp), которые используют функцию hashCode() вернуть хэш-номер:-

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
};

я использовал его в разных местах, например, в моей пользовательской структуре данных. Это хорошо работает.

теперь, я хочу, чтобы хэш-алгоритм признан std:: структура данных:-

вот что я должен сделать : - (изменено из cppreference, я назову этот код #D).

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

если я вставлю блок #D (С соответствующей реализацией) в каждом классе (B01,B02,...), Я могу позвонить: -

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

без передача второго аргумента шаблона,
и мой алгоритм хэша (#H01) будет называться. (by по умолчанию)

вопрос

чтобы он узнал все мои B01::hashCode, B02::hashCode, ...,
должен ли я вставить блок #D на все 200+ Bxx.h?

Я Могу просто добавьте один блок #D (в верхнем коллекторе?)
и оттуда ... --50-->изменить маршрут std::anyDataStructure называть hashCode() когда это возможно?

//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;  
            }
        }
    };
}

для меня это звучит как вопрос SFINAE.

Примечание: самый похожий вопрос в так не спрашивал о том, как этого достичь.

изменить (Почему я не могу просто переделать его? ; 3 февраля 2017)

  • я не знайте, является ли рефакторинг грубой силы правильным путем. Думаю, есть способ получше.
  • hashCode() - Это мой дом. Я эмоционально привязан к нему.
  • я хочу, чтобы мой код был коротким и чистым, насколько это возможно. std:: блоки грязные.
  • это может быть просто мое любопытство. Если я упертый, чтобы не рефакторинг мой код, насколько C++ может идти?

5 ответов


решение одно

если вы можете сделать классы B01, B02, ... шаблоны классов с фиктивными параметрами вы можете просто согласиться со специализацией std::hash для шаблона шаблона, который принимает фиктивный параметр шаблона:

#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]

решение два (содержать ошибку/не использовать его)

но я считаю, что то, что вы хотите, больше похоже это:

#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]

(вдохновленный ответ)

однако, пока это может работать на gcc, это действительно не разрешено стандартом c++(но я также не уверен, что это на самом деле буквально запрещено...). См.этой нить в этом контексте.

Edit:

как указал @Barry, это поведение gcc не поручено по стандарту c++ и как таковой нет абсолютно никаких гарантий, что он будет работать даже в следующей версии gcc... Это может быть даже воспринято как ошибка, поскольку она позволяет частичную специализацию шаблона, который на самом деле не специализирует этот шаблон.

решение трех (желаемый)

другой способ может специализироваться 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; } 
};

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;
}

согласно представленному обсуждению здесь это должно быть прекрасно действительный. Он также работает в gcc, лязгом, icc, VS

чтобы иметь возможность использовать код, не вмешиваясь в код классов, я считаю, что мы можем использовать правила ADL, чтобы проверить sfinae, если данный класс не включает пространство имен std. Вы можете найти фон здесь. Кредиты также ура и hth. - АЛФ. Подход может быть изменен как следует:

#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], [clang test], [icc test] [gcc 4.9] [VC]


это не должно быть так, вы также можете иметь функтор:

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);
    }
};

и иметь псевдоним std::unordered_set С MyHash как хэш типа:

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

или более полный, если вы также хотите иметь возможность предоставить равной функтор и распределитель:

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>;

затем используя его (с любым из ваших Bxx), как вы бы использовали 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;
}

концепции

если вы можете использовать концепции, они могут позволить вам получить специализируйтесь std::hash класс, как вы хотите:

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

namespace std {
    template <class T> requires HashCodeConcept <T> 
    struct hash<T> {
        std::size_t operator()(const T& t) const {
            return  t.hashCode();
        }
    };
}

при создании условий по умолчанию хэш-параметра шаблонов контейнеров std для методов-членов групп классов следует избегать введения новых проблем.

  • избыточность
  • проблемы с переносимостью
  • чародейские конструкты

классический объектно-ориентированный подход может потребовать узорного редактирования 200 + классов, чтобы гарантировать, что они предоставляют основы использования контейнера std::hash. Некоторые параметры группового преобразования Ниже приводятся два необходимых метода.

  • открытый хэш-код () определяется в конкретном классе, где он уникален для этого класса или по наследованию, если он следует шаблону, общему для всех классов.
  • определен публичный оператор== ().

Шаблоны

эти два шаблона удалят избыточность и упростят объявление, как указано.

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 {
    ...
    virtual std::size_t hashCode() const {
        return std::hash<std::string>{}(ms1)
                ^ std::hash<std::string>{}(ms2);
    } }

следующее выражение sed может сэкономить время преобразования, предполагая, что код использует { inline. Подобные выражения будут работать с Boost или с использованием скриптового языка, такого как Python.

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

инструмент на основе AST будет более надежным. этой объясняет, как использовать возможности clang для преобразования кода. Есть новые дополнения, такие как этот питон контроллер преобразования кода C++.

Обсуждение

существует несколько вариантов того, где может находиться алгоритм хэша.

  • метод абстрактного класса объявления контейнера std
  • метод конкретного класса (например, #H01 в Примере)
  • шаблон структуры (обычно контрпродуктивный и непрозрачный)
  • по умолчанию std:: hash

вот подборка блок, который обеспечивает чистую демонстрацию классики того, как можно выполнить желаемый дефолт и другие три цели, перечисленные выше, предлагая гибкость в том, где алгоритм хэша определен для любого данного класса. Различные функции могут быть удалены в зависимости от конкретного случая.

#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; };

метод на основе SFINAE типа, который вы искали, требует частичной специализации std::hash. Это можно сделать, если ваши классы Bxx являются шаблонами (что имеет место, если они получены из базы CRTP). Например (обратите внимание, что в 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);
}

производит (используя clang++ на MacOS)

B:: hashvalue (): возврат 42

см. также это связано ответа к аналогичному вопросу.

, концепции являются способом будущего для решения таких проблем, как это.

я придумал что-то, что, кажется, частично работает. Это обходной путь, который позволит вам использовать std::hash на типе, который реализует hashCode. Взгляните:

   //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;
}

код использует hashableконструктор шаблонов и оператор шаблона для извлечения хэша из любого класса, который реализует hashCode. The std::hash специализация ищет экземпляр hashable но шаблонный конструктор позволяет создавать экземпляр из класса это имеет hasCode.

единственное, что здесь есть, это то, что вам нужно будет написать unordered_set, а не std::unordered_set чтобы использовать его, и вам придется убедиться, что std::unordered_set никоим образом не вводится в область применения. Таким образом, вы не сможете иметь ничего подобного using namespace std или using std::unordered_set в свой источник. Но помимо нескольких gotchas в использовании это может сработать для вас.

конечно, это просто пластырь по реальной проблеме... что было бы нежеланием пройти через боль правильно специализируясь std::hash для каждого из ваших типов. (Я не виню тебя)

я также хотел бы отметить, что с этим кодом замещение ошибка... если вы предпочитаете SFINAE, ему потребуется модификация.

EDIT:

после попытки запуска:

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

я заметил, что были некоторые ошибки компилятора.

я обновил мой test класс equality comparable добавил:

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

до test что сейчас делает:

//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();
    }
};