簡體   English   中英

std::pair 字符串作為 std 中定義的 unordered_map 的自定義鍵失敗並出現模板錯誤

[英]std::pair of strings as custom key of unordered_map defined in std fails with template errors

我有一個像這樣定義和使用的地圖

// 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)' 的參考

  1. 在 CUse 中的m聲明處

注意:請參閱正在編譯的類模板實例化 'std::unordered_map,myStruct,std::hash<_Kty>,std::equal_to<_Kty>,std::allocator>>' 的參考

正如@Bo Persson 和@Sean Cline 在評論中提到的那樣,您需要使用自定義哈希函數/函子來做到這一點。

現場演示

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

然而,每個對稱對都會產生幾乎相同的散列,這可能會導致散列沖突,從而降低性能。 盡管如此,在boost.hash 中還提供了用於std::pair組合散列的額外特化


另一種解決方案是使用std::map<> 您還可以在那里為std::pair指定自定義函數/函子,以實現相同的映射結構。 即使在那里您不必面對哈希沖突,但您可能不想要的排序也會很好。

現場演示

#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

回答第二個問題:

謝謝,但你能告訴我為什么在 std 中使用我的數據類型不好嗎? 無論如何,我的類型是在我自己的程序中定義的

你覺得你可以在你的程序中定義你想要的任何東西,對嗎? (至少,這是您給人的印象。)好吧,C++ 實現對namespace std有同樣的感覺——它是他們的命名空間,他們可以在其中定義任何他們想要的東西(當然,受 C++ 標准的約束)。

如果實現需要定義(可能未記錄的)輔助函數/類/任何東西,則期望它可以放置在namespace std而不會與您的程序發生沖突。 舉個例子:如果你的 C++ 庫決定它需要為std::pair<std::string, std::string>定義std::pair模板的特化,你的程序會發生什么? 據我所知,該標准既不要求也不禁止這種專業化,因此它的存在由實現者自行決定。

命名空間的存在是為了防止命名沖突。 特別是, namespace std存在是為了將 C++ 實現細節與用戶程序隔離開來。 將您的代碼添加到namespace std會破壞這種隔離,因此標准將其聲明為未定義的行為。 不要這樣做。

(話雖如此,沒有什么能阻止您圍繞std::pair<std::string, std::string>編寫包裝類以獲得所需的功能。只需在您自己的命名空間中即可。)

您需要為您的密鑰類型定義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;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM