Reputation: 1040
I am working on porting my c++ application from GCC4.7 to GCC7 and ran into an issue where std::hash_map
find()
function returns null
result for keys which are present in map.
Existing code:
struct eqfunc {
bool operator()(const char* const &s1, const char* const &s2) const {
std::cout << "eqfunc in action " << s1 << " - " << s2 << std::endl;
return strcmp(s1,s2) == 0;
}
};
template <typename T> class customMap : public std::hash_map<const char*,T,std::hash<const char*>,eqfunc> {};
customMap<const char*> cstmMap;
std::cout << "Insert abc" << std::endl;
cstmMap["abc"] = "ABC";
std::cout << "Insert def" << std::endl;
cstmMap["def"] = "DEF";
std::cout << "Insert xyz" << std::endl;
cstmMap["xyz"] = "XYZ";
std::cout << "Find def in cstmMap" << std::endl;
string findString("def");
customMap<const char*>::iterator ptr = cstmMap.find((char *)findString.c_str());
LOG_INFO("output ptr %s", ptr);
This was working fine in GCC4.7 platform. When I ported the code to GCC7, I noticed the behaviour where find() returns null
result even for keys which are present in the map.
Sample run output in GCC7
Insert abc
Insert def
Insert xyz
Find def in cstmMap
output ptr (null)
Updated std::hash_map
to std::unordered_map
doesn't work either:
template <typename T> class customMap : public std::unordered_map<const char*,T,std::hash<const char*>,eqfunc> {};
Another weird behaviour I noticed using std::unordered_map
is that eqfunc
is not executed in consistent pattern across multiple runs
Sample 1 run
Insert abc
Insert def
eqfunc in action def - abc
Insert xyz
Find def in cstmMap
eqfunc in action def - xyz
output ptr (null)
Sample 2 run
Insert abc
Insert def
eqfunc in action def - abc
Insert xyz
Find def in cstmMap
output ptr (null)
NOTE: This is very large code base and changing const char *
to std::string
is not straightforward and requires a lot of work.
I am wondering if there are any work arounds to get it working with existing const char *
data type for the map's key. Any help on this would be appreciated.
Upvotes: 2
Views: 559
Reputation: 117318
You've discovered that std::hash<const char*>
hashes the actual pointer - not the C string it points at. Sometimes "def"
and the second "def"
will actually have the same pointer value. It depends on how the compiler optimized it.
To use C strings, you need to provide a hash functor for C strings. Here's one example:
#include <string_view>
struct cstring_hash {
size_t operator()(std::string_view str) const {
return std::hash<std::string_view>{}(str);
}
};
And redefine the container:
template <typename T>
class customMap : public std::unordered_map<const char*, T, cstring_hash, eqfunc> {
// To be able to use ctors:
using std::unordered_map<const char*, T, cstring_hash, eqfunc>::unordered_map;
};
The added using
of the unordered_map
s constructors makes it possible to construct the map in a simpler way:
int main() {
customMap<const char*> cstmMap{
{"abc", "ABC"},
{"def", "DEF"},
{"xyz", "XYZ"},
};
std::string findString("def");
auto ptr = cstmMap.find(findString.c_str());
std::cout << ptr->second << '\n'; // prints DEF
}
If you are using a C++ version prior to C++17, you could replace cstring_hash
by picking a good enough hash function. Here's one that probably does the job:
namespace detail {
static const auto S = // shift constant
sizeof(size_t) < sizeof(uint64_t) ? 16u : 32u;
static const auto C = // multiplication constant
sizeof(size_t) < sizeof(uint64_t) ? 23456789u : 0xBB67AE8584CAA73Bull;
}
#if __cpp_constexpr >= 201304L
#define RELAXEDCONSTEXPR constexpr
#else
#define RELAXEDCONSTEXPR
#endif
struct cstring_hash {
RELAXEDCONSTEXPR size_t operator()(const char *s) const {
size_t h = 0;
for(; *s; ++s) {
h = h * detail::C + static_cast<unsigned char>(*s);
h ^= h >> detail::S;
}
return h *= detail::C;
}
};
Upvotes: 3