It seems as if C++ does not have a hash function for strings in the standard library. Is this true?
What is a working example of using a string as a key in an unordered_map that will work with any c++ compiler?
C++ STL provides template specializations of std::hash for the various string classes. You could just specify std::string as key type for std::unordered_map:
#include <string>
#include <unordered_map>
int main()
{
std::unordered_map<std::string, int> map;
map["string"] = 10;
return 0;
}
I ran into this today (actually with wstring, not string, but it's the same deal): using wstring as a key in an unordered_map generates an error about no hash function being available for that type.
The solution for me was to add:
#include <string>
Believe it or not, without the #include directive I still had the wstring type available but apparently NOT the ancillary functions like the hash. Simply adding the include above fixed it.
Actually, there is std::hash<std::string>
But there it is how you can use another hash function:
struct StringHasher {
size_t operator()(const std::string& t) const {
//calculate hash here.
}
}
unordered_map<std::string, ValueType, StringHasher>
If you have a CustomType and you want to plug into the STL infrastructure this is what you could do.
namespace std
{
//namespace tr1
//{
// Specializations for unordered containers
template <>
struct hash<CustomType> : public unary_function<CustomType, size_t>
{
size_t operator()(const CustomType& value) const
{
return 0;
}
};
//} // namespace tr1
template <>
struct equal_to<CustomType> : public unary_function<CustomType, bool>
{
bool operator()(const CustomType& x, const CustomType& y) const
{
return false;
}
};
} // namespace std
If you then want to create say a std::unordered_map<CustomType> the STL will find the hash and equal_to functions without you having to do anything more with the template. This is how I like to write my custom equality comparer that support unordered data structures.
In my case it was really distraction.
I had a type X for which I implemented hashing for const& X an utilized it somewhere with
std::unordered_map<const X, int> m_map;
Then I wanted to have another map which key are of the type X and did:
std::unordered_map<X, int> map_x;
Notice the LACK of const on the second case.
Related
I am wondering if it is possible to have a std::unordered_map use a fixed size array as a key. For example, here is a simple cache that holds strings as the value but needs a uint8_t[] as the key:
using UserKeyV1 = uint8_t[16];
using UserKeyV2 = uint8_t[32];
template <typename T>
class StringCache
{
public:
bool addString(const T &userKey, const std::string &value)
{
auto [it, result] = m_cache.try_emplace(userKey, value);
// ^^^^^^^ this line won't compile: array initializer must be an initializer list or string literal
}
private:
struct EqualToFn {
bool operator()(const T &left, const T &right) const {
return std::memcmp(&left[0],
&right[0],
sizeof(T)) == 0;
}
};
struct HashFn {
size_t operator()(const T &k) const {
return std::_Hash_impl::hash(k);
}
};
std::unordered_map<T, std::string, HashFn, EqualToFn> cache_;
}
And in use would be something like:
StringCache<UserKey1> cache1;
uint8_t uniqueKey[16]; // this key was provided by 3rd party lib
cache1.addString(uniqueKey, strUsername)
This won't compile due to the error listed above but I'm not sure why. I created the custom hasher and equality functions for the array so that it knew how to handle such a key. I could probably solve this with std::array and copy the key to it first but wanted to avoid that if possible as it would involve a copy and this code will be potentially called 1000s of times a second.
Is what I am trying to achieve possible or do I just use std::array as a key?
it works fine with std::array
#include <cstdlib>
#include <iostream>
#include <unordered_map>
#include <cstring>
#include <array>
using UserKeyV1 = std::array<unsigned char,16>;
template <typename T>
class StringCache
{
public:
bool addString(const T &userKey, const std::string &value)
{
auto [it, result] = m_cache.try_emplace(userKey, value);
// ^^^^^^^ this line won't compile: array initializer must be an initializer list or string literal
return result;
}
private:
struct EqualToFn {
bool operator()(const T &left, const T &right) const {
return std::memcmp(&left[0],
&right[0],
sizeof(T)) == 0;
}
};
struct HashFn {
size_t operator()(const T &k) const {
return std::_Hash_impl::hash(k);
}
};
std::unordered_map<T, std::string, HashFn, EqualToFn> m_cache;
};
int main()
{
StringCache<UserKeyV1> cache1;
//uint8_t uniqueKey[16]; // this key was provided by 3rd party lib
UserKeyV1 uniqueKey;
std::string strUsername = "username";
cache1.addString(uniqueKey, strUsername);
}
I think if you want to avoid copy you can just use std::string_view class, the code becomes simpler.
#include <unordered_map>
#include <string_view>
using UserKeyV1 = std::string_view;
template <typename T>
class StringCache
{
public:
bool addString(const T &userKey, const std::string &value)
{
auto [it, result] = m_cache.try_emplace(userKey, value);
return result;
}
std::unordered_map<T, std::string> m_cache;
};
int main()
{
StringCache<UserKeyV1> cache1;
uint8_t uniqueKey[16]; // this key was provided by 3rd party lib
std::string strUsername = "username";
cache1.addString(std::string_view{(const char*)uniqueKey, sizeof(uniqueKey)}, strUsername);
}
for c++20 you can probably use std::span, or to implement your own wrapper for earlier standards.
I think the best way of doing this (without copying the value as you requested) would be to calculate an hash using the uint8_t[16] array and use the hash as key. You can choose one among the many hashing algorithms already existing (xxhash, md5, sha32, crc32 etc...).
The best match with your case would be crc32.
Any of these usually takes a const void* and a size_t to calculate the memory hash, you can pass your key address and size without copying it as you requested.
An example would be to use crc32 hash (basically an uint32_t) and to use it as key for the unordered_map.
So you can still keep your class, by adding one line in your example.
StringCache<uint32_t> cache1;
uint8_t uniqueKey[16];
cache1.addString(GetArrayHash(uniqueKey), strUsername);
You can't use this solution if you need to get back the uint8_t[16] from your cache. Based on your class code you posted there are no uses of the actual key value.
Note: an unordered_map with uint32_t as key will use an identify function as hash algorithm, in short by using crc32 hashing you are probably not losing efficiency but this is not guaranteed by the standard since it does not define exactly which algorithm to use to hash an array of uint8_t (but the msvc and gcc implementations I have been able to test use crc32).
using this approach you can definitely remove your HashFn and EqualToFn to your StringCache class.
template <typename T>
class StringCache
{
public:
bool addString(const T &userKey, const std::string &value)
{
auto [it, result] = m_cache.try_emplace(userKey, value);
}
private:
std::unordered_map<T, std::string> cache_;
}
I have an unordered_map that uses a string-type as a key:
std::unordered_map<string, value> map;
A std::hash specialization is provided for string, as well as a
suitable operator==.
Now I also have a "string view" class, which is a weak pointer into an existing string, avoiding heap allocations:
class string_view {
string *data;
size_t begin, len;
// ...
};
Now I'd like to be able to check if a key exists in the map using a string_view object. Unfortunately, std::unordered_map::find takes a Key argument, not a generic T argument.
(Sure, I can "promote" one to a string, but that causes an allocation I'd like to avoid.)
What I would've liked instead was something like
template<class Key, class Value>
class unordered_map
{
template<class T> iterator find(const T &t);
};
which would require operator==(T, Key) and std::hash<T>() to be suitably defined, and would return an iterator to a matching value.
Is there any workaround?
P0919R2 Heterogeneous lookup for unordered containers has been merged in the C++2a's working draft!
The abstract seems a perfect match w.r.t. my original question :-)
Abstract
This proposal adds heterogeneous lookup support to the unordered associative containers in the C++ Standard Library. As a result, a creation of a temporary key object is not needed when different (but compatible) type is provided as a key to the member function. This also makes unordered and regular associative container interfaces and functionality more compatible with each other.
With the changes proposed by this paper the following code will work without any additional performance hits:
template<typename Key, typename Value>
using h_str_umap = std::unordered_map<Key, Value, string_hash>;
h_str_umap<std::string, int> map = /* ... */;
map.find("This does not create a temporary std::string object :-)"sv);
As mentioned above, C++14 does not provide heterogeneous lookup for std::unordered_map (unlike std::map). You can use Boost.MultiIndex to define a fairly close substitute for std::unordered_map that allows you to look up string_views without allocating temporary std::strings:
Live Coliru Demo
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/member.hpp>
#include <string>
using namespace boost::multi_index;
struct string_view
{
std::string *data;
std::size_t begin,len;
};
template<typename T,typename Q>
struct mutable_pair
{
T first;
mutable Q second;
};
struct string_view_hash
{
std::size_t operator()(const string_view& v)const
{
return boost::hash_range(
v.data->begin()+v.begin,v.data->begin()+v.begin+v.len);
}
std::size_t operator()(const std::string& s)const
{
return boost::hash_range(s.begin(),s.end());
}
};
struct string_view_equal_to
{
std::size_t operator()(const std::string& s1,const std::string& s2)const
{
return s1==s2;
}
std::size_t operator()(const std::string& s1,const string_view& v2)const
{
return s1.size()==v2.len&&
std::equal(
s1.begin(),s1.end(),
v2.data->begin()+v2.begin);
}
std::size_t operator()(const string_view& v1,const std::string& s2)const
{
return v1.len==s2.size()&&
std::equal(
v1.data->begin()+v1.begin,v1.data->begin()+v1.begin+v1.len,
s2.begin());
}
};
template<typename Q>
using unordered_string_map=multi_index_container<
mutable_pair<std::string,Q>,
indexed_by<
hashed_unique<
member<
mutable_pair<std::string,Q>,
std::string,
&mutable_pair<std::string,Q>::first
>,
string_view_hash,
string_view_equal_to
>
>
>;
#include <iostream>
int main()
{
unordered_string_map<int> m={{"hello",0},{"boost",1},{"bye",2}};
std::string str="helloboost";
auto it=m.find(string_view{&str,5,5});
std::cout<<it->first<<","<<it->second<<"\n";
}
Output
boost,1
I faced an equal problem.
We need two structs:
struct string_equal {
using is_transparent = std::true_type ;
bool operator()(std::string_view l, std::string_view r) const noexcept
{
return l == r;
}
};
struct string_hash {
using is_transparent = std::true_type ;
auto operator()(std::string_view str) const noexcept {
return std::hash<std::string_view>()(str);
}
};
For unordered_map:
template <typename Value>
using string_unorderd_map = std::unordered_map<std::string, Value, string_hash, string_equal>;
For unordered_set:
using string_unorderd_set = std::unordered_set<std::string, string_hash, string_equal>;
Now using string_view is possible.
It looks like only as recently as C++14 did even the basic map get such a templated find for is_transparent types in the comparison. Most likely the correct implementation for hashed containers was not immediately evident.
As far as I can see your two options are:
Just do the allocation and profile to see if maybe it's not actually a problem.
Take a look at boost::multi_index (http://www.boost.org/doc/libs/1_60_0/libs/multi_index/doc/index.html) and have both string and string_view indexes into the container.
This solution has drawbacks, which may or may not make it unviable for your context.
You can make a wrapper class:
struct str_wrapper {
const char* start, end;
};
And change your map to use str_wrapper as its key. You'd have to add 2 constructors to str_wrapper, one for std::string and one for your string_view. The major decision is whether to make these constructors perform deep or shallow copies.
For example, if you use std::string only for inserts and str_view only for lookups, you'd make the std::string constructor deep and the str_view one shallow (this can be enforced at compile time if you use a custom wrapper around unordered_map). If you care to avoid memory leaks on the deep copy you would need additional fields to support proper destruction.
If your usage is more varied, (looking up std::string's or inserting by str_view), there will be drawbacks, which again, might make the approach too distasteful so as to be unviable. It depends on your intended usage.
Yet another option is to split the lookup and the data management by using multiple containters:
std::unordered_map<string_view, value> map;
std::vector<unique_ptr<const char[]>> mapKeyStore;
Lookups are done using string_views without the need of allocations.
Whenever a new key is inserted we need to add a real string allocation first:
mapKeyStore.push_back(conv(str)); // str can be string_view, char*, string... as long as it converts to unique_ptr<const char[]> or whatever type
map.emplace(mapKeyStore.back().get(), value)
It would be much more intuitive to use std::string in the mapKeyStore. However, using std::string does not guarantee unchanging string memory (e.g. if the vector resizes). With the unique_ptr this is enforced. However, we need some special conversion/allocation routine, called conv in the example. If you have a custom string container which guarantees data consistency under moves (and forces the vector to use moves), then you can use it here.
The drawback
The disadvantage of the above method is that handling deletions is non-trivial and expensive if done naive. If the map is only created once or only growing this is a non-issue and the above pattern works quite well.
Running example
The below example includes a naive deletion of one key.
#include <vector>
#include <unordered_map>
#include <string>
#include <string_view>
#include <iostream>
#include <memory>
#include <algorithm>
using namespace std;
using PayLoad = int;
unique_ptr<const char[]> conv(string_view str) {
unique_ptr<char[]> p (new char [str.size()+1]);
memcpy(p.get(), str.data(), str.size()+1);
return move(p);
}
int main() {
unordered_map<string_view, PayLoad> map;
vector<unique_ptr<const char[]>> mapKeyStore;
// Add multiple values
mapKeyStore.push_back(conv("a"));
map.emplace(mapKeyStore.back().get(), 3);
mapKeyStore.push_back(conv("b"));
map.emplace(mapKeyStore.back().get(), 1);
mapKeyStore.push_back(conv("c"));
map.emplace(mapKeyStore.back().get(), 4);
// Search all keys
cout << map.find("a")->second;
cout << map.find("b")->second;
cout << map.find("c")->second;
// Delete the "a" key
map.erase("a");
mapKeyStore.erase(remove_if(mapKeyStore.begin(), mapKeyStore.end(),
[](const auto& a){ return strcmp(a.get(), "a") == 0; }),
mapKeyStore.end());
// Test if verything is OK.
cout << '\n';
for(auto it : map)
cout << it.first << ": " << it.second << "\n";
return 0;
}
Of course, the two containers can be put into a wrapper which handles the insertion and deletion for its own.
I'll just present one variation I found on github, it involves defining a new map class that wraps the std.
Redefining some key API to intercept the adaptors we want, and use a static string to copy the key.
It's not necessary a good solution, but it's interesting to know it exists for people who deems it enough.
original:
https://gist.github.com/facontidavide/95f20c28df8ec91729f9d8ab01e7d2df
code gist:
template <typename Value>
class StringMap: public std::unordered_map<std::string, Value>
{
public:
typename std::unordered_map<string,Value>::iterator find(const nonstd::string_view& v )
{
tmp_.reserve( v.size() );
tmp_.assign( v.data(), v.size() );
return std::unordered_map<string, Value>::find(tmp_);
}
typename std::unordered_map<std::string,Value>::iterator find(const std::string& v )
{
return std::unordered_map<std::string, Value>::find(v);
}
typename std::unordered_map<std::string,Value>::iterator find(const char* v )
{
tmp_.assign(v);
return std::unordered_map<std::string, Value>::find(v);
}
private:
thread_local static std::string tmp_;
};
credits:
Davide Faconti
Sorry for answering this very old question, but it still comes up in search engine results...
In this case your unordered_map is using the string type as its key, the find method is looking for a reference to a string which will not generate an allocation. Your string_view class stores a pointer to a string. Therefore your string_view class can dereference the pointer into a ref of the type needed for your map without causing an allocation. The method would look like this...
string &string_view::getRef() const
{
return *_ptr;
}
and to use the string_view with the map it would look like this
auto found=map.find(string_view_inst.getRef());
note that this will not work for the c++17 string_view class as it does not internally store a std::string object
ps.
Your string_view class is probably not great for cpu caches as it stores a pointer to a string allocated somewhere on the heap, and the string itself stores a pointer to the actual data located somewhere else on the heap. Every time you access your string_view it will result in a double dereference.
You could allow your view to be implicitly convertible to a std::string:
class StringView {
// ...
operator std::string() const
{
return data->substr(begin, len);
}
// ...
};
I have an unordered_map that uses a string-type as a key:
std::unordered_map<string, value> map;
A std::hash specialization is provided for string, as well as a
suitable operator==.
Now I also have a "string view" class, which is a weak pointer into an existing string, avoiding heap allocations:
class string_view {
string *data;
size_t begin, len;
// ...
};
Now I'd like to be able to check if a key exists in the map using a string_view object. Unfortunately, std::unordered_map::find takes a Key argument, not a generic T argument.
(Sure, I can "promote" one to a string, but that causes an allocation I'd like to avoid.)
What I would've liked instead was something like
template<class Key, class Value>
class unordered_map
{
template<class T> iterator find(const T &t);
};
which would require operator==(T, Key) and std::hash<T>() to be suitably defined, and would return an iterator to a matching value.
Is there any workaround?
P0919R2 Heterogeneous lookup for unordered containers has been merged in the C++2a's working draft!
The abstract seems a perfect match w.r.t. my original question :-)
Abstract
This proposal adds heterogeneous lookup support to the unordered associative containers in the C++ Standard Library. As a result, a creation of a temporary key object is not needed when different (but compatible) type is provided as a key to the member function. This also makes unordered and regular associative container interfaces and functionality more compatible with each other.
With the changes proposed by this paper the following code will work without any additional performance hits:
template<typename Key, typename Value>
using h_str_umap = std::unordered_map<Key, Value, string_hash>;
h_str_umap<std::string, int> map = /* ... */;
map.find("This does not create a temporary std::string object :-)"sv);
As mentioned above, C++14 does not provide heterogeneous lookup for std::unordered_map (unlike std::map). You can use Boost.MultiIndex to define a fairly close substitute for std::unordered_map that allows you to look up string_views without allocating temporary std::strings:
Live Coliru Demo
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/member.hpp>
#include <string>
using namespace boost::multi_index;
struct string_view
{
std::string *data;
std::size_t begin,len;
};
template<typename T,typename Q>
struct mutable_pair
{
T first;
mutable Q second;
};
struct string_view_hash
{
std::size_t operator()(const string_view& v)const
{
return boost::hash_range(
v.data->begin()+v.begin,v.data->begin()+v.begin+v.len);
}
std::size_t operator()(const std::string& s)const
{
return boost::hash_range(s.begin(),s.end());
}
};
struct string_view_equal_to
{
std::size_t operator()(const std::string& s1,const std::string& s2)const
{
return s1==s2;
}
std::size_t operator()(const std::string& s1,const string_view& v2)const
{
return s1.size()==v2.len&&
std::equal(
s1.begin(),s1.end(),
v2.data->begin()+v2.begin);
}
std::size_t operator()(const string_view& v1,const std::string& s2)const
{
return v1.len==s2.size()&&
std::equal(
v1.data->begin()+v1.begin,v1.data->begin()+v1.begin+v1.len,
s2.begin());
}
};
template<typename Q>
using unordered_string_map=multi_index_container<
mutable_pair<std::string,Q>,
indexed_by<
hashed_unique<
member<
mutable_pair<std::string,Q>,
std::string,
&mutable_pair<std::string,Q>::first
>,
string_view_hash,
string_view_equal_to
>
>
>;
#include <iostream>
int main()
{
unordered_string_map<int> m={{"hello",0},{"boost",1},{"bye",2}};
std::string str="helloboost";
auto it=m.find(string_view{&str,5,5});
std::cout<<it->first<<","<<it->second<<"\n";
}
Output
boost,1
I faced an equal problem.
We need two structs:
struct string_equal {
using is_transparent = std::true_type ;
bool operator()(std::string_view l, std::string_view r) const noexcept
{
return l == r;
}
};
struct string_hash {
using is_transparent = std::true_type ;
auto operator()(std::string_view str) const noexcept {
return std::hash<std::string_view>()(str);
}
};
For unordered_map:
template <typename Value>
using string_unorderd_map = std::unordered_map<std::string, Value, string_hash, string_equal>;
For unordered_set:
using string_unorderd_set = std::unordered_set<std::string, string_hash, string_equal>;
Now using string_view is possible.
It looks like only as recently as C++14 did even the basic map get such a templated find for is_transparent types in the comparison. Most likely the correct implementation for hashed containers was not immediately evident.
As far as I can see your two options are:
Just do the allocation and profile to see if maybe it's not actually a problem.
Take a look at boost::multi_index (http://www.boost.org/doc/libs/1_60_0/libs/multi_index/doc/index.html) and have both string and string_view indexes into the container.
This solution has drawbacks, which may or may not make it unviable for your context.
You can make a wrapper class:
struct str_wrapper {
const char* start, end;
};
And change your map to use str_wrapper as its key. You'd have to add 2 constructors to str_wrapper, one for std::string and one for your string_view. The major decision is whether to make these constructors perform deep or shallow copies.
For example, if you use std::string only for inserts and str_view only for lookups, you'd make the std::string constructor deep and the str_view one shallow (this can be enforced at compile time if you use a custom wrapper around unordered_map). If you care to avoid memory leaks on the deep copy you would need additional fields to support proper destruction.
If your usage is more varied, (looking up std::string's or inserting by str_view), there will be drawbacks, which again, might make the approach too distasteful so as to be unviable. It depends on your intended usage.
Yet another option is to split the lookup and the data management by using multiple containters:
std::unordered_map<string_view, value> map;
std::vector<unique_ptr<const char[]>> mapKeyStore;
Lookups are done using string_views without the need of allocations.
Whenever a new key is inserted we need to add a real string allocation first:
mapKeyStore.push_back(conv(str)); // str can be string_view, char*, string... as long as it converts to unique_ptr<const char[]> or whatever type
map.emplace(mapKeyStore.back().get(), value)
It would be much more intuitive to use std::string in the mapKeyStore. However, using std::string does not guarantee unchanging string memory (e.g. if the vector resizes). With the unique_ptr this is enforced. However, we need some special conversion/allocation routine, called conv in the example. If you have a custom string container which guarantees data consistency under moves (and forces the vector to use moves), then you can use it here.
The drawback
The disadvantage of the above method is that handling deletions is non-trivial and expensive if done naive. If the map is only created once or only growing this is a non-issue and the above pattern works quite well.
Running example
The below example includes a naive deletion of one key.
#include <vector>
#include <unordered_map>
#include <string>
#include <string_view>
#include <iostream>
#include <memory>
#include <algorithm>
using namespace std;
using PayLoad = int;
unique_ptr<const char[]> conv(string_view str) {
unique_ptr<char[]> p (new char [str.size()+1]);
memcpy(p.get(), str.data(), str.size()+1);
return move(p);
}
int main() {
unordered_map<string_view, PayLoad> map;
vector<unique_ptr<const char[]>> mapKeyStore;
// Add multiple values
mapKeyStore.push_back(conv("a"));
map.emplace(mapKeyStore.back().get(), 3);
mapKeyStore.push_back(conv("b"));
map.emplace(mapKeyStore.back().get(), 1);
mapKeyStore.push_back(conv("c"));
map.emplace(mapKeyStore.back().get(), 4);
// Search all keys
cout << map.find("a")->second;
cout << map.find("b")->second;
cout << map.find("c")->second;
// Delete the "a" key
map.erase("a");
mapKeyStore.erase(remove_if(mapKeyStore.begin(), mapKeyStore.end(),
[](const auto& a){ return strcmp(a.get(), "a") == 0; }),
mapKeyStore.end());
// Test if verything is OK.
cout << '\n';
for(auto it : map)
cout << it.first << ": " << it.second << "\n";
return 0;
}
Of course, the two containers can be put into a wrapper which handles the insertion and deletion for its own.
I'll just present one variation I found on github, it involves defining a new map class that wraps the std.
Redefining some key API to intercept the adaptors we want, and use a static string to copy the key.
It's not necessary a good solution, but it's interesting to know it exists for people who deems it enough.
original:
https://gist.github.com/facontidavide/95f20c28df8ec91729f9d8ab01e7d2df
code gist:
template <typename Value>
class StringMap: public std::unordered_map<std::string, Value>
{
public:
typename std::unordered_map<string,Value>::iterator find(const nonstd::string_view& v )
{
tmp_.reserve( v.size() );
tmp_.assign( v.data(), v.size() );
return std::unordered_map<string, Value>::find(tmp_);
}
typename std::unordered_map<std::string,Value>::iterator find(const std::string& v )
{
return std::unordered_map<std::string, Value>::find(v);
}
typename std::unordered_map<std::string,Value>::iterator find(const char* v )
{
tmp_.assign(v);
return std::unordered_map<std::string, Value>::find(v);
}
private:
thread_local static std::string tmp_;
};
credits:
Davide Faconti
Sorry for answering this very old question, but it still comes up in search engine results...
In this case your unordered_map is using the string type as its key, the find method is looking for a reference to a string which will not generate an allocation. Your string_view class stores a pointer to a string. Therefore your string_view class can dereference the pointer into a ref of the type needed for your map without causing an allocation. The method would look like this...
string &string_view::getRef() const
{
return *_ptr;
}
and to use the string_view with the map it would look like this
auto found=map.find(string_view_inst.getRef());
note that this will not work for the c++17 string_view class as it does not internally store a std::string object
ps.
Your string_view class is probably not great for cpu caches as it stores a pointer to a string allocated somewhere on the heap, and the string itself stores a pointer to the actual data located somewhere else on the heap. Every time you access your string_view it will result in a double dereference.
You could allow your view to be implicitly convertible to a std::string:
class StringView {
// ...
operator std::string() const
{
return data->substr(begin, len);
}
// ...
};
I have a funny error occuring where I have a custom class I am using as keys in a std::unordered_map. I have implemented a hash function for this custom class.
If I create an std::unordered_map like so everything runs fine:
std::unordered_map <Component, int> myMap;
But if I create a std::unordered_map where the keys are references it complains there is no implemented hash function for this type:
std::unordered_map <Component&, int> myMap;
Error 3 error C2338: The C++ Standard doesn't provide a hash for this type.
Any idea how I can get this to work?
class Component
{
public:
GUID id;
Component()
{
generateGUID(&id);
}
bool operator== (const Component& other) const
{
return compareGUID(id, other.id);
}
};
namespace std
{
template<>
struct hash<Component>
{
std::size_t operator()(const Component& cmp) const
{
using std::size_t;
using std::hash;
return hash<GUID>()(cmp.id); // GUID hash has also been implemented
}
};
}
std::unordered_map<Component, int> cMap1; // compiles ok
std::unordered_map<Component&, int> cMap2; // causes compiler error
The problem is not how to correctly override hash for Component&. The problem is that STL containers are meant to store objects and references are not objects. Mind that the term object includes primitives and pointers, since they require storage, while a reference does not.
To overcome your problem you should look into std::reference_wrapper, which is able to wrap a reference inside an object, then the code would become something like:
#include <iostream>
#include <unordered_map>
#include <functional>
using namespace std;
struct Component
{
size_t id;
};
namespace std
{
template<>
struct hash<reference_wrapper<Component>>
{
size_t operator()(const reference_wrapper<Component>& cmp) const
{
return cmp.get().id;
}
};
}
unordered_map<reference_wrapper<Component>, int> mapping;
If you don't want to use reference_wrapper then you should use pointers (Component*) as keys.
I'm using a std::unordered_set for the first time and have a question about the hash function. As far as I understand, if you don't specify a hash function it will default to std::hash<Key>.
I have a mySet member in one of my classes:
typedef std::unordered_set<MyClass> USetType;
USetType mySet;
When I try to build, I get the following error:
error C2440: 'type cast' : cannot convert from 'const MyClass' to 'size_t'
Is it necessary to define a conversion function (to size_t) if you want to use unordered_set with a custom class? Is there any way to avoid writing your own hash function and just using the default?
If you don't specify your own hash functor as template argument, it will default to std::hash<MyClass>, which does not exist unless you define it.
Best define your own specialization of std::hash inside namespace std:
namespace std {
template <>
struct hash<MyClass>
{
typedef MyClass argument_type;
typedef std::size_t result_type;
result_type operator()(const MyClass & t) const
{
/* ..calculate hash value for t */
}
};
}
And make sure you include this code before the declaration of your hash. This way you can declare the hash simply as std::unordered_set<MyClass> with no need for further template arguments.
You didn't specify what MyClass looks like inside, but a typical situation is that your user-defined type simply consists of several simple-type members, for which a default hash function exists. In this case, you will probably want to combine the hash values for the individual types to a hash value for the entire combination. The Boost library provides a function called hash_combine for this purpose. Of course, there is no guarantee that it will work well in your particular case (it depends on the distribution of data values and the likelihood of collisions), but it provides a good and easy-to-use starting point.
Here is an example of how to use it, assuming MyClass consists of two string members:
#include <unordered_set>
#include <boost/functional/hash.hpp>
struct MyClass
{
std::string _s1;
std::string _s2;
};
namespace std {
template <>
struct hash<MyClass>
{
typedef MyClass argument_type;
typedef std::size_t result_type;
result_type operator()(const MyClass & t) const
{
std::size_t val { 0 };
boost::hash_combine(val,t._s1);
boost::hash_combine(val,t._s2);
return val;
}
};
}
int main()
{
std::unordered_set<MyClass> s;
/* ... */
return 0;
}
I'd like to expand on the answer given by jogojapan. As mentioned in a comment by CashCow on that answer, you also have to either overload the equality comparison operator (operator==) for MyClass or define a separate comparison function and provide it to the unordered_set. Otherwise, you will get another error message. For example, VS 2013 throws:
error C2678: binary '==' : no operator found which takes a left-hand operand of type 'const MyClass' (or there is no acceptable conversion)
Moreover, you can use lambda expressions instead of defining the hash and comparison functions. If you don't want to use Boost, then you can also handcraft a hash function. I understand, that you want to use some default function, but a compiler doesn't know how to calculate a meaningful hash for a custom class. However, you can use std::hash for the members of your class. If you put everything together, then your code could be written as follows:
class MyClass {
public:
int i;
double d;
std::string s;
};
int main() {
auto hash = [](const MyClass& mc){
return (std::hash<int>()(mc.i) * 31 + std::hash<double>()(mc.d)) * 31 + std::hash<std::string>()(mc.s);
};
auto equal = [](const MyClass& mc1, const MyClass& mc2){
return mc1.i == mc2.i && mc1.d == mc2.d && mc1.s == mc2.s;
};
std::unordered_set<MyClass, decltype(hash), decltype(equal)> mySet(8, hash, equal);
return 0;
}
Code on Ideone