std :: пара строк как пользовательский ключ unordered_map, определенный в std, завершается с ошибками шаблона - PullRequest
0 голосов
/ 31 мая 2018

Моя карта определена и используется следующим образом

// def.h
struct X{};
struct Y{};
struct myStruct
{
   X x;
   Y y;
};

typedef std::unordered_map<std::pair<std::string, std::string>, myStruct> myMap;
namespace std
{
   template<> struct pair<std::string, std::string>
   {
      std::string s1,s2;
      pair(const std::string& a, const std::string& b):s1(a),s2(b){}
      bool operator < (const pair<std::string,std::string>& r)
      {
         return (0 < r.s1.compare(s1) && (0 < r.s2.compare(s2)));
      }
   };
} 

//use.cpp
class CUse
{
  myMap m;
public:
   CUse():m(0){}
};

Некоторые ошибки, выдаваемые компилятором, извлекаются, как показано ниже

  1. В конструкторе CUse инициализация,

примечание: см. Ссылку на создание экземпляра шаблона функции 'std :: unordered_map, myStruct, std :: hash <_Kty>, std :: equal_to <_Kty>, std :: allocator >> ::unordered_map (unsigned __int64) 'компилируется

При объявлении m в CUse

примечание: см. Ссылку на создание экземпляра шаблона класса 'std :: unordered_map, myStruct, std :: hash <_Kty>, std:: equal_to <_Kty>, std :: allocator >> 'компилируется

Ответы [ 3 ]

0 голосов
/ 31 мая 2018

Как отметили @Bo Persson и @Sean Cline в комментариях, для этого вам понадобится пользовательская хеш-функция / функтор.

LIVE DEMO

#include <unordered_map>
#include <string>
#include <tuple>
#include <functional>
#include <cstddef>
#include <iostream>

struct myStruct  {  int x, y;  };
using Key = std::pair<std::string, std::string>;

namespace something 
{
    struct Compare      //custom hash function/functor
    {
        std::size_t operator()(const Key& string_pair) const
        {
            // just to demonstrate the comparison.
            return std::hash<std::string>{}(string_pair.first) ^
                    std::hash<std::string>{}(string_pair.second);
        }
    };
}
using myMap = std::unordered_map<Key, myStruct, something::Compare>;

int main()
{
    myMap mp =
    {
        { { "name1", "name2" },{ 3,4 } },
        { { "aame1", "name2" },{ 8,4 } },
        { std::make_pair("fame1", "name2"),{ 2,4 } }, // or make pair
        { std::make_pair("fame1", "bame2"),{ 1,2 } }
    };

    for(const auto& it: mp)
    {
        std::cout << it.first.first << " " << it.first.second << " "
                 << it.second.x << " " << it.second.y << std::endl;
    }

    return 0;
}

Однако каждая симметричная пара будет создавать почти одинаковые хэши, что может вызвать коллизии хешей и, следовательно, снизить производительность.Тем не менее, дополнительные специализации для std::pair для составления хэшей доступны в boost.hash


Альтернативным решением может быть использование std::map<>.Там вы также можете указать пользовательскую функцию / функтор для std::pair, чтобы добиться той же структуры карты.Даже при том, что вам не придется сталкиваться с хеш-коллизиями, это будет хорошо отсортировано, чего вы, возможно, не захотите.

LIVE DEMO

#include <map>
#include <string>
#include <tuple>
#include <iostream>

struct myStruct {  int x, y; };
using Key =  std::pair<std::string, std::string>;

namespace something
{
    struct Compare
    {
        bool operator()(const Key& lhs, const Key& rhs) const
        {
            // do the required comparison here
            return std::tie(lhs.first, lhs.second) < std::tie(rhs.first, rhs.second);
        }
    };
}
using myMap = std::map<Key, myStruct, something::Compare>;

Не могли бы вы сказать мне, почему не рекомендуется использовать мой тип данных в std?Мой тип определен в моей собственной программе в любом случае.

Вы не должны делать это под пространством имен std, потому что это может вызвать UB.Здесь четко определены ситуации / исключения, в которых вы можете расширить пространство имен std: https://en.cppreference.com/w/cpp/language/extending_std

0 голосов
/ 31 мая 2018

Ответ на дополнительный вопрос:

Спасибо, но не могли бы вы мне сказать, почему нехорошо использовать мой тип данных в std?Мой тип определен в моей собственной программе в любом случае

Вы чувствуете, что можете определить, что вы хотите в своей программе, верно?(Это впечатление, которое вы произвели, по крайней мере.) Что ж, реализации C ++ чувствуют то же самое по отношению к namespace std - это их пространство имен, и они могут определять в нем все, что захотят (конечно, в соответствии со стандартом C ++).

Если реализации необходимо определить (возможно недокументированную) вспомогательную функцию / класс / что угодно, можно ожидать, что она может быть помещена в namespace std без конфликта с вашей программой.Пример: что произойдет с вашей программой, если ваша библиотека C ++ решит, что ей необходимо определить специализацию шаблона std::pair для std::pair<std::string, std::string>?Насколько мне известно, стандарт не требует и не запрещает такую ​​специализацию, поэтому его существование оставлено на усмотрение разработчика.

Существуют пространства имен для предотвращения конфликтов имен.В частности, существует namespace std для изоляции деталей реализации C ++ от пользовательских программ.Добавление вашего кода в namespace std разрушает эту изоляцию, поэтому стандарт объявляет ее неопределенным поведением.Не делайте этого.

(При этом, ничто не мешает вам написать класс-оболочку вокруг std::pair<std::string, std::string>, чтобы получить необходимую вам функциональность. Просто сделайте это в своем собственном пространстве имен.)

0 голосов
/ 31 мая 2018

Вам необходимо определить специализацию std::hash для типа ключа, например:

#include <unordered_map>
#include <string>


using KeyType = std::pair<std::string, std::string>;

namespace std
{
    template<>
    struct hash<KeyType>
    {
        size_t operator()(KeyType const& kt) const
        {
            size_t hash = 0;
            hash_combine(hash, kt.first);
            hash_combine(hash, kt.second);
            return hash;
        }
        // taken from boost::hash_combine:
        // https://www.boost.org/doc/libs/1_55_0/doc/html/hash/reference.html#boost.hash_combine
        template <class T>
        inline static void hash_combine(std::size_t& seed, const T& v)
        {
            std::hash<T> hasher;
            seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
        }
    };
}

int main()
{
    std::unordered_map<KeyType, int> us;
    return 0;
}
...