Нужна помощь в определении хеш-функции для пользовательского класса в C++

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

struct Identifier {
    
    
Identifier(const std::string &domain, 
           const std::string &name,
           const std::string &version)
: domain(domain), name(name), version(version) {}
    
/// The domain associated with the identifier.
  std::string domain;
    
/// The name associated with the identifier.
   std::string name;

  /// The version associated with the identifier.
  std::string version;
};


inline bool operator==(const Identifier& lhs, const Identifier& rhs){
    return ((lhs.domain == rhs.domain) && (lhs.name == rhs.name) && (lhs.version == rhs.version));
}

У меня есть интерфейс ModuleInterface.h, который зависит от идентификатора и выглядит следующим образом:

class ModuleInterface {
public:

  using Identifiers = std::unordered_set<Identifier>;

  virtual Identifiers getSupportedIdentifiers() = 0;
  
};

Существует класс Manager, который имеет метод RegisterModule, который принимает ModuleInterfaces, показанный ниже.

class Manager{
public:

    bool registerModule(const std::shared_ptr<ModuleInterface> &module);


private:
    std::map<Identifier, std::shared_ptr<ModuleInterface>> m_requestHandlers;
};

Реализация класса Manager.cpp выглядит следующим образом.

bool Manager::registerModule(const std::shared_ptr<ModuleInterface>& module){
    /*
    for(auto& requestIdentifier : module->getSupportedIdentifiers()){
        //m_requestHandlers[requestIdentifier] = module;
    } */

    for (auto itr = module->getSupportedIdentifiers().begin(); itr != module->getSupportedIdentifiers().end(); ++itr) {
     m_requestHandlers[*itr] = module;
    }


    return true;
}

Теперь, когда я пытаюсь скомпилировать его, я вижу ошибку времени компиляции, и ошибка выглядит следующим образом:

foo/src/Manager.cpp:17:21: warning: object backing the pointer will be destroyed at the end of the full-expression [-Wdangling-gsl]
    for (auto itr = module->getSupportedIdentifiers().begin(); itr != module->getSupportedIdentifiers().end(); ++itr) {
                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from foo/src/Manager.cpp:2:
In file included from foo/include/Registration/Manager.h:10:
In file included from bar/usr/include/c++/v1/map:550:
In file included from bar/usr/include/c++/v1/functional:515:
In file included from bar/usr/include/c++/v1/__functional/boyer_moore_searcher.h:25:
In file included from bar/usr/include/c++/v1/unordered_map:523:
bar/usr/include/c++/v1/__hash_table:838:5: error: static assertion failed due to requirement 'integral_constant<bool, false>::value': the specified hash does not meet the Hash requirements
    static_assert(__check_hash_requirements<_Key, _Hash>::value,
    ^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bar/usr/include/c++/v1/__hash_table:853:1: note: in instantiation of template class 'std::__enforce_unordered_container_requirements<Identifier, std::hash<Identifier>, std::equal_to<Identifier>>' requested here
typename __enforce_unordered_container_requirements<_Key, _Hash, _Equal>::type
^
bar/usr/include/c++/v1/unordered_set:614:30: note: while substituting explicitly-specified template arguments into function template '__diagnose_unordered_container_requirements' 
        static_assert(sizeof(__diagnose_unordered_container_requirements<_Value, _Hash, _Pred>(0)), "");
                             ^
foo/src/Manager.cpp:17:29: note: in instantiation of member function 'std::unordered_set<Identifier>::~unordered_set' requested here
    for (auto itr = module->getSupportedIdentifiers().begin(); itr != module->getSupportedIdentifiers().end(); ++itr) {
                            ^
1 warning and 1 error generated.
ninja: build stopped: subcommand f

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

---обновить публикацию минимального примера воспроизведения ---

Идентификатор.h

#pragma once

#include <string>

struct Identifier {


    Identifier(const std::string &domain,
               const std::string &name,
               const std::string &version)
            : domain(domain), name(name), version(version) {}

    /// The domain associated with the identifier.
    std::string domain;

    /// The name associated with the identifier.
    std::string name;

    /// The version associated with the identifier.
    std::string version;
};


inline bool operator==(const Identifier& lhs, const Identifier& rhs){
    return ((lhs.domain == rhs.domain) && (lhs.name == rhs.name) && (lhs.version == rhs.version));
}

МодульИнтерфейс.h

#include "Identifier.h"
#include <unordered_set>

class ModuleInterface {
public:

    using Identifiers = std::unordered_set<Identifier>;

    virtual Identifiers getSupportedIdentifiers() = 0;

};

Менеджер.h

#pragma once

#include "Identifier.h"
#include "ModuleInterface.h"
#include <map>
namespace test {
class Manager {
public:
    Manager() = default;
    bool registerModule(const std::shared_ptr<ModuleInterface>& module);

private:
    std::map<Identifier, std::shared_ptr<ModuleInterface>> m_requestHandlers;
};

}

Менеджер.cpp

#include "Manager.h"

namespace test{

bool Manager::registerModule(const std::shared_ptr<ModuleInterface>& module){

    for(auto& requestIdentifier : module->getSupportedIdentifiers()){
        m_requestHandlers[requestIdentifier] = module;
    }
    /*
    for (auto itr = module->getSupportedIdentifiers().begin(); itr != module->getSupportedIdentifiers().end(); ++itr) {
        m_requestHandlers[*itr] = module;
    }*/


    return true;
}

}

Ошибка

/Library/Developer/CommandLineTools/SDKs/MacOSX14.4.sdk/usr/include/c++/v1/__hash_table:697:5: error: static assertion failed due to requirement 'integral_constant<bool, false>::value': the specified hash does not meet the Hash requirements
    static_assert(__check_hash_requirements<_Key, _Hash>::value,
    ^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Library/Developer/CommandLineTools/SDKs/MacOSX14.4.sdk/usr/include/c++/v1/__hash_table:712:1: note: in instantiation of template class 'std::__enforce_unordered_container_requirements<Identifier, std::hash<Identifier>, std::equal_to<Identifier>>' requested here
typename __enforce_unordered_container_requirements<_Key, _Hash, _Equal>::type
^
/Library/Developer/CommandLineTools/SDKs/MacOSX14.4.sdk/usr/include/c++/v1/unordered_set:715:30: note: while substituting explicitly-specified template arguments into function template '__diagnose_unordered_container_requirements' 
        static_assert(sizeof(std::__diagnose_unordered_container_requirements<_Value, _Hash, _Pred>(0)), "");
                             ^
/Users/test/src/Manager.cpp:7:43: note: in instantiation of member function 'std::unordered_set<Identifier>::~unordered_set' requested here
    for(auto& requestIdentifier : module->getSupportedIdentifiers()){

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

bourne 07.08.2024 12:01

Да, это не относится к вашему примеру.

user12002570 07.08.2024 12:01

Я не вижу в вашем коде никакой хеш-функции

Yksisarvinen 07.08.2024 12:02

@Yksisarvinen Я предоставил реализацию для booloperator==(const Identifier& lhs, const Identifier& rhs), разве этого недостаточно, а если нет, мне нужно реализовать дополнительный метод.

bourne 07.08.2024 12:06

Опубликуйте минимально воспроизводимый пример . .

user12002570 07.08.2024 12:06
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
55
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это не имеет ничего общего с указателями или вашим дизайном Manager или ModuleInterface. Проблема сводится к следующему:

struct Identifier {
  // side note: better to take args by value, and then std::move
  Identifier(const std::string &d, const std::string &n,
             const std::string &v)
      : domain(d), name(n), version(v) {}

  std::string domain;
  std::string name;
  std::string version;
  auto operator<=>(const Identifier&) const = default;  // to avoid boilerplate code, since C++20
};

// this is OK
std::map<Identifier, int> myMap; // value type doesn't really matter here
// this will not compile
std::unordered_set<Identifier> mySet;

Компаратором для ключей std::map является std::less<Key>, поэтому в вашем случае std::less<Identifier>, что можно легко решить, установив по умолчанию оператор сравнения.

С другой стороны, std::unordered_set нужен std::equal_to<Key> (который у вас есть) и std::hash<Key> (которого у вас нет). Один из простых способов сделать ваш Identifier «хешируемым» — просто скопировать и вставить пример пользовательской специализации std::hash, введенной в namespace stdиз cppreference:

template<>
struct std::hash<Identifier>
{
    std::size_t operator()(const Identifier& id) const noexcept
    {
        std::size_t h1 = std::hash<std::string>{}(id.domain);
        std::size_t h2 = std::hash<std::string>{}(id.name);
        std::size_t h3 = std::hash<std::string>{}(id.version);
        return h1 ^ (h2 << 1) ^ (h3 << 2);
    }
};

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

Правильное объединение хешей — это отдельная наука; ваша реализация… не очень хороша. Несколько лучшие реализации можно найти здесь: stackoverflow.com/q/2590677/1968 … (к сожалению, это не так) сбивает с толку то, что C++ до сих пор не реализует частичную std::hash специализацию для std::tuple: в противном случае мы могли бы просто написать return std::hash<T>()(std::tie(id.domain, id.name, id.version)); (с подходящий T, поскольку CTAD здесь использовать нельзя… грр.).

Konrad Rudolph 07.08.2024 12:21

@KonradRudolph, да, хорошая мысль. Хэш примера подходит для демонстрации, но не для производства.

pptaszni 07.08.2024 12:25

Примечание: я бы использовал умножение на некоторые относительные простые числа. (Но это компромисс между скоростью и возможными столкновениями в конечном итоге)

Pepijn Kramer 07.08.2024 12:35

Или просто используйте Boost.ContainerHash на tuple<domain,name,version>...

Useless 07.08.2024 12:48

Другие вопросы по теме