C ++ 0x ekler hash<...>(...)
.
Ben bulamadım hash_combine
sunulduğu gibi, çalışmakla birlikte boost . Böyle bir şeyi uygulamanın en temiz yolu nedir? Belki C ++ 0x kullanarak xor_combine
?
Yanıtlar:
Pekala, sadece destekçilerin yaptığı gibi yapın:
template <class T>
inline void hash_combine(std::size_t& seed, const T& v)
{
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
}
std::pair
(veya tuple
hatta) olması gerekliliği olacaktır . Her bir öğenin karmasını hesaplar ve ardından bunları birleştirir. (Ve standart kitaplığın ruhuna uygun olarak, tanımlanmış bir uygulama şeklinde.)
Bu çözümü arayanlar için faydalı olabileceği için burada paylaşacağım: @KarlvonMoor cevabından başlayarak, birkaç değeri bir araya getirmeniz gerekiyorsa kullanımında terser olan değişken bir şablon sürümü burada:
inline void hash_combine(std::size_t& seed) { }
template <typename T, typename... Rest>
inline void hash_combine(std::size_t& seed, const T& v, Rest... rest) {
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
hash_combine(seed, rest...);
}
Kullanım:
std::size_t h=0;
hash_combine(h, obj1, obj2, obj3);
Bu, orijinal olarak, özel türleri kolayca hashable hale getirmek için değişken bir makro uygulamak için yazılmıştır (ki bu, bir hash_combine
işlevin birincil kullanımlarından biridir ):
#define MAKE_HASHABLE(type, ...) \
namespace std {\
template<> struct hash<type> {\
std::size_t operator()(const type &t) const {\
std::size_t ret = 0;\
hash_combine(ret, __VA_ARGS__);\
return ret;\
}\
};\
}
Kullanım:
struct SomeHashKey {
std::string key1;
std::string key2;
bool key3;
};
MAKE_HASHABLE(SomeHashKey, t.key1, t.key2, t.key3)
// now you can use SomeHashKey as key of an std::unordered_map
Bu, aşağıdaki gibi bir değişken şablon kullanılarak da çözülebilir:
#include <functional>
template <typename...> struct hash;
template<typename T>
struct hash<T>
: public std::hash<T>
{
using std::hash<T>::hash;
};
template <typename T, typename... Rest>
struct hash<T, Rest...>
{
inline std::size_t operator()(const T& v, const Rest&... rest) {
std::size_t seed = hash<Rest...>{}(rest...);
seed ^= hash<T>{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
return seed;
}
};
Kullanım:
#include <string>
int main(int,char**)
{
hash<int, float, double, std::string> hasher;
std::size_t h = hasher(1, 0.2f, 2.0, "Hello World!");
}
Kesinlikle bir şablon işlevi yapılabilir, ancak bu bazı kötü tür çıkarımlarına neden olabilir, örneğin hash("Hallo World!")
dizge yerine işaretçi üzerinde bir karma değeri hesaplar. Muhtemelen standardın bir yapı kullanmasının nedeni budur.
Birkaç gün önce bu cevabın biraz geliştirilmiş versiyonunu buldum (C ++ 17 desteği gereklidir):
template <typename T, typename... Rest>
void hashCombine(uint& seed, const T& v, Rest... rest)
{
seed ^= ::qHash(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
(hashCombine(seed, rest), ...);
}
Yukarıdaki kod, kod üretimi açısından daha iyidir. Kodumda Qt'den qHash işlevini kullandım, ancak başka bir karma kullanmak da mümkün.
(int[]){0, (hashCombine(seed, rest), 0)...};
C ++ 11'de de çalışacaktır.
Vt4a2h'nin cevabından C ++ 17 yaklaşımını gerçekten seviyorum , ancak bir sorundan muzdarip : Değere göre aktarılırken , Rest
bunları const referanslarıyla aktarmak daha cazip olurdu (eğer olacaksa bir zorunluluktur) yalnızca taşıma türleriyle kullanılabilir).
İşte hala bir katlama ifadesi kullanan (C ++ 17 veya daha üstünü gerektirmesinin nedeni budur) ve kullanan std::hash
(Qt hash işlevi yerine ) uyarlanmış sürüm :
template <typename T, typename... Rest>
void hash_combine(std::size_t& seed, const T& v, const Rest&... rest)
{
seed ^= std::hash<T>{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
(hash_combine(seed, rest), ...);
}
Tamlık aşkına: bu sürümüyle kullanılabilir olacaktır her türlü hash_combine
olması gerekir şablon ihtisas için hash
enjekte std
ad.
Misal:
namespace std // Inject hash for B into std::
{
template<> struct hash<B>
{
std::size_t operator()(B const& b) const noexcept
{
std::size_t h = 0;
cgb::hash_combine(h, b.firstMember, b.secondMember, b.andSoOn);
return h;
}
};
}
Dolayısıyla, B
yukarıdaki örnekteki bu tür A
, aşağıdaki kullanım örneğinin gösterdiği gibi başka bir tür içinde de kullanılabilir :
struct A
{
std::string mString;
int mInt;
B mB;
B* mPointer;
}
namespace std // Inject hash for A into std::
{
template<> struct hash<A>
{
std::size_t operator()(A const& a) const noexcept
{
std::size_t h = 0;
cgb::hash_combine(h,
a.mString,
a.mInt,
a.mB, // calls the template specialization from above for B
a.mPointer // does not call the template specialization but one for pointers from the standard template library
);
return h;
}
};
}
Hash
, std
ad alanına enjekte etmek yerine, özel hasherinizi belirtmek için standart kapsayıcıların şablon argümanlarını kullanmak daha iyidir .
Vt4a2h tarafından cevap kesinlikle güzel ama C ++ 17 kat ifadesini kullanan ve herkesin kolayca yeni toolchain geçmek yapabiliyor. Aşağıdaki sürüm, bir katlama ifadesini taklit etmek için genişletici hilesini kullanır ve C ++ 11 ve C ++ 14'te de çalışır.
Ek olarak, işlevi işaretledim inline
ve çeşitli şablon argümanları için mükemmel iletimi kullandım.
template <typename T, typename... Rest>
inline void hashCombine(std::size_t &seed, T const &v, Rest &&... rest) {
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
(int[]){0, (hashCombine(seed, std::forward<Rest>(rest)), 0)...};
}