Создайте 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) будет вызван. ( по умолчанию)

Вопрос

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

Можно ли просто добавить одиночный блок #D (в верхнем заголовке?)
 и оттуда повторите маршрут 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 для меня.

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

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

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

Ответ 1

Решение

Если вы можете создавать классы 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, это не разрешено стандартом С++ (но я также не уверен, что на самом деле это буквально запрещено). См. этот поток в этом контексте.

Edit:

Как указано @Barry, это поведение gcc не, заданное стандартом С++, и как таковое нет абсолютно никаких гарантий, он будет работать даже в следующей версии 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, clang, icc, VS

Чтобы иметь возможность использовать код без вмешательства в код классов, я считаю, что мы можем использовать правила ADL, чтобы сделать проверку sfinae, если данный класс не содержит пространства имен std. Здесь вы можете найти фон . Кредиты также относятся к Cheers и 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 test], [clang test], [icc test] [gcc 4.9] [VC]

Ответ 2

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

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

Ответ 3

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

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

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

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

Два шаблона

Эти два шаблона удаляют избыточность и упрощают декларацию, как указано.

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]+ *)(:?)(.*)$"
        + "/\1 \2 : public AbstractB, \3 [{]/"
        + "; s/ {2,}/ /g"
        + "; s/: ?:/:/g"

Инструмент, основанный на AST, будет более надежным. В этом объясняется, как использовать возможности clang для преобразования кода. Появились новые дополнения, такие как этот Python-контроллер для преобразования кода на С++.

Обсуждение

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

  • Метод абстрактного класса объявления 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; };

Ответ 4

Метод на основе SFINAE типа, который вы искали, требует частичной специализации std::hash. Это можно сделать, если ваши классы Bxx являются шаблонами (что происходит, если они получены из базы CRTP). Например (примечание, сфокусированное в редакции)

#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(): return 42

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

Однако понятия - это путь будущего для решения таких проблем.

Ответ 5

Я придумал что-то, что частично работает. Это временное решение, которое позволит вам использовать 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. Специализация std::hash ищет экземпляр hashable, но шаблонный конструктор позволяет создать экземпляр из класса с hasCode.

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

Конечно, это всего лишь групповая помощь по реальной проблеме... которая не хотела бы испытывать боль, особенно специализируясь на 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();
    }
};