[英]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){}
};
編譯器發出的一些錯誤提取如下
CUse
初始化時,注意:請參閱正在編譯的函數模板實例化 'std::unordered_map,myStruct,std::hash<_Kty>,std::equal_to<_Kty>,std::allocator>>::unordered_map(unsigned __int64)' 的參考
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.