unordered_map for <Pointer, String> in C++ - c++

I am trying to create an unordered_map for <xml_node*,string> pair, where xml_node is an element of xml from pugixml library, and i wish to store its pointer as the key. I have declared the map like this :
unordered_map<xml_node*,string> label_hash;
Now the insert function is working well and good. But whenever I try to find an element from the hash like this :
string lb = string(label_hash.find(node));
I get the following error :
no matching function for call to ‘std::basic_string<char>::basic_string(std::_Hashtable<pugi::xml_node*, std::pair<pugi::xml_node* const, std::basic_string<char> >, std::allocator<std::pair<pugi::xml_node* const, std::basic_string<char> > >, std::_Select1st<std::pair<pugi::xml_node* const, std::basic_string<char> > >, std::equal_to<pugi::xml_node*>, std::hash<pugi::xml_node*>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, false, false, true>::iterator)’|
Now do I need to implement a hash function and equal function for the map? I was trying to implement them like follows but it doesn't work :
struct hashing_func {
unsigned long operator()(const xml_node* key) const {
uintptr_t ad = (uintptr_t)key;
return (size_t)((13 * ad) ^ (ad >> 15));
//return hash<xml_node*>(key);
}
};
struct key_equal_fn {
bool operator()(const xml_node* t1, const xml_node* t2) const {
return (t1 == t2);
}
};
I am a bit new to C++ so a little help would be great!

Please read the documentation: unordered_map::find returns an iterator to pair<xml_node const*, string>. (You can't pass that to the string constructor.) Instead do this:
auto iterator = label_hash.find(node);
if (iterator != label_hash.end()) { // `.find()` returns `.end()` if the key is not in the map
string& lb = iterator->second; // The `&` is optional here, use it if you don't want to deepcopy the whole string.
// use lb
}
else {
// key not in the map
}

I wrote a little test program:
#include <unordered_map>
#include <string>
namespace pugi
{
struct xml_node {};
}
int main()
{
std::unordered_map<pugi::xml_node*, std::string> mymap;
pugi::xml_node n1;
mymap.emplace(&n1, "foo");
auto i = mymap.find(&n1);
i->second;
return 0;
}
This compiles perfectly, indicating that as, suspected, the problem is not with the use of a pointer as a map key, not a lack of custom comparitor and not the lack of a hash function.
unordered_map::find returns an iterator - which points to a key/value pair.

Related

C++ error: expression cannot be used as a function (find_if)

The essence of the program: get a map, where the values are char from the passed string, and the key is the number of these values in the string
using namespace std;
map<char, int> is_merge(const string& s) {
map<char, int> sCount {};
for (auto lp : s) {
if (find_if(sCount.begin(), sCount.end(), lp) != sCount.end()) {
sCount[lp] += 1;
} else {
sCount.insert(make_pair(lp, 0));
}
}
return sCount;
}
int main()
{
string test = "aba";
map <char, int> res = is_merge(test);
for (auto lp : res) {
cout << lp.first << ":" << lp.second << endl;
}
return 0;
}
But an error occurs in the console: /usr/include/c++/12/bits/predefined_ops.h:318:30: error: expression cannot be used as a function 318 | { return bool(_M_pred(*__it)); } | ~~~~~~~^~~~~~~
std::find_if takes a predicate not a value. Hence the error that lp is not a callable. To find a key in a map you should use std::map::find because it is O(logn) compared to O(n) for std::find/std::find_if (as a rule of thumb you can remember: If a container has a member function that does the same as a generic algorithm the member function is at least as effcient, often better).
However, there is not need to check if the key is present via find. The function can be this:
map<char, int> is_merge(const string& s) {
map<char, int> sCount {};
for (auto lp : s) {
++sCount[lp];
}
return sCount;
}
std::map::operator[] already does insert an element when none is found for the given key. You don't need to do that yourself.
PS: And if you do call insert then there is no need for std::make_pair : sCount.insert({lp, 0});. std::make_pair is for when you need to deduce the type of the pair from arguments to std::make_pair, but you don't need that here.
PPS: And if you do use std::find you need to consider that the element type of your map is std::pair<const char, int>, not char.

passing const as this argument discards qualifiers only when using a unordered_map but not a vector

I understand that calling a non-const method to a constant object gives an error as explained here. This question, although deals with the same error, is not a duplicate because it is not about a non-constant method.
This time I have a minimal reproducible example:
// Example program
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
class Something
{
public:
int m_value;
Something(): m_value{0} { myVector.push_back(1); myMap["hello"]=3; }
void setValue(int value) { m_value = value; }
int getValue() { return m_value ; }
//int getValue(const int value){ return myVector[value] ; } //<-- this gives an error (just reference)
int getValue(const int value)const { return myVector[value]; }
//int getValue2(const std::string &name) {return myMap[name]; } //<--- this gives an error (just reference)
int getValue2(const std::string &name) const {return myMap[name]; } //HERE this gives an error (this question)
std::vector<int> myVector;
std::unordered_map<std::string,int> myMap;
};
int main()
{
const Something something{}; // calls default constructor
int l= something.getValue(0);
std::cout<<l<<std::endl;
l= something.getValue2("hello"); //<-- HERE the error
std::cout<<l<<std::endl;
return 0;
}
In comments there are two method declarations that illustrates the point about non-constant methods. I left them there for reference. This question is not above them.
You see the const getValue method that returns a vector element? This works without problem.
Now see the const getValue2 method that should return a unordered map element? Even though it is a constant method it generates an error
In member function 'int Something::getValue2(const string&) const':
17:68: error: passing 'const std::unordered_map<std::basic_string<char>, int>' as 'this' argument of 'std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::mapped_type& std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::operator[](const key_type&) [with _Key = std::basic_string<char>; _Tp = int; _Hash = std::hash<std::basic_string<char> >; _Pred = std::equal_to<std::basic_string<char> >; _Alloc = std::allocator<std::pair<const std::basic_string<char>, int> >; std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::mapped_type = int; std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::key_type = std::basic_string<char>]' discards qualifiers [-fpermissive]
My question is: Why only with unordered maps passing constant as the index generates this error?
EDIT:
Thanks to the very useful answers. I modified the class to
// Example program
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
class Something
{
public:
int m_value;
Something(): m_value{0} { myVector.push_back(1); myMap["hello"]=3; }
void setValue(int value) { m_value = value; }
int getValue() { return m_value ; }
//int getValue(const int value){ return myVector[value] ; } //<-- this gives an error
int getValue(const int value)const { return myVector[value]; }
//int getValue2(const std::string &name) {return myMap[name]; } //<--- this gives an error
//this will generate an exception if name is not in the map
int getValue3(const std::string &name) const {
//return myMap[name];
return myMap.at(name);}
int getValue2(const std::string &name) const {
auto iter = myMap.find(name);
return (iter != myMap.end()) ? iter->second : 0;
}
std::vector<int> myVector;
std::unordered_map<std::string,int> myMap;
};
The const-qualified version of getValue2 only has const access to the members of Something. This means that it will see myMap with the type const std::unordered_map<std::string,int> and you cannot call any non-const member functions on myMap. The operator[] is a non-const member function (it cannot be made const, because it sometimes has to insert a value-initialized entry, namely when the key is not found in the map) so you get the error message about discarding qualifiers. To get around this, you can use .at(name) instead of [name]. This will throw an exception if name is not found in the map.
something is a const object, so getValue(int) and getValue2(string) need to be const-qualified in order to be callable on it. That means the this pointer inside of them will always be pointing at a const object, so all operations on the object's data members need to be const-qualified as well.
Something::getValue(int) calls myVector[value], which works OK because std::vector::operator[] has a const-qualified overload to provide read-only access to the elements of a const std::vector object.
On the other hand, Something::getValue2(string) calls myMap[name], which does not work because std::unordered_map::operator[] is not const-qualified at all, so it can't be called on a const unordered_map object. A std::(unordered_)map's operator[] performs an insertion operation if the specified key is not found, thus the std::(unordered_)map object can't be const when using operator[]. To read an element from a const std::(unordered_)map without inserting a new element, you have to use the map's find() method instead, which is const-qualified, eg:
int getValue2(const std::string &name) const {
auto iter = myMap.find(name);
return (iter != myMap.end()) ? iter->second : 0;
}
A std::vector either has an element at a given index or the index is out-of-bounds, in that case a call to its operator[] invokes undefined behavior.
On the other hand, with the key alone you cannot determine if a std::unordered_map has a value for that key or not. You have to search the map.
To find if a map has a value for a key and use that value:
std::unordered_map<int,int> x;
auto it = x.find(3);
if (it == x.end()) {
// element not found
} else {
auto value = it->second;
}
operator[] does more than that. If the element was not found it does insert a element with default constructed value for the given key and returns a reference to the newly inserted value. It is more or less the same as:
std::unordered_map<int,int> x;
auto it = x.find(3);
if (it == x.end()) {
it = x.emplace(std::make_pair(3,0).first;
// .second is bool to
// indicate wether an element
// was actually inserted.
// In this case we know it wasn't present before
}
auto value = it->second;
// or simpler:
auto value = x[3];
The benefit of operator[] is that you get a valid element in any case, but the price is that operator[] cannot be const.
TL;DR
Accessing a vector out-of-bounds is to be avoided. std::vector::operator[] always returns an existing element (or invokes undefined behavior). On the other hand, looking up elements in a std::unordered_map that are not present is common. In that case you need to choose what to do when the element was not present. One option is operator[] that potentially inserts an element in the map, hence cannot be const.

C++ map::find for pair as key

I have a map of pair as key and bool as value. When i try to find a certain pair that is already included using map::find method, it's not finding any item. What do i do wrong?
I tried to implement a comparator class and set it as key_comp in map, but I'm not sure i did it right. Is this the solve?
I am working in VS12, I attached a photo with the debugging, you shall understand much more from that:
You are calling find with pair<char *, char*>(0x00ffc468, 0x00ffbdb0). There is no such pair in the set, so find returns end.
Your code compares char *'s, which checks if they point to the same place in memory, not whether they point to identical content. You should probably use std::string instead of char * rather than reinventing wheels.
Here's an example of a possible comparison function you could use if you insist on doing so:
bool less(
std::pair<char *, char *> const& p1,
std::pair<char *, char *> const& p2)
{
int i = strcmp (p1.first, p2.first);
if (i < 0)
return true;
if (i > 0)
return false;
return strcmp (p1.second, p2.second) < 0;
}
If you must use std:pair<char*, char*> as key in your map, you can use the following functor while creating the map.
struct MyCompare
{
bool operator()(std::pair<char const*, char const*> const& lhs,
std::pair<char const*, char const*> const& rhs)
{
int n1 = strcmp(lhs.first, rhs.first);
if ( n1 != 0 )
{
return n1 < 0;
}
return (strcmp(lhs.second, rhs.second) < 0);
}
};
typedef std::map<std::pair<char*,char*>, int, MyCompare> MyMap;
MyMap myMap;

Using boost_unordered_hash_map with char* key

I have a mix of C and C++ files that collectively form my program. I am using boost unordered hash map which I initially defined as:
typedef boost::unordered_map<char *, int> my_map;
However, the map was acting weird; find(key) could not find keys that actually existed in the hash map. I, then, changed the definition to:
typedef boost::unordered_map<std::string, int> my_map;
and now the hash map works fine. Nevertheless, it is inconvenient for me to convert my char * to std::string in order to use the map. Is there a way to make the first definition work?
std::unordered_map
Oh aha. Noticed you wanted unordered_map. One of my favourites is to use boost::string_ref to represent strings without copying, so you could do
std::unordered_map<boost::string_ref, int> map;
with a quick & dirty hash implementation like:
namespace std
{
template<>
struct hash<boost::string_ref> {
size_t operator()(boost::string_ref const& sr) const {
return boost::hash_range(sr.begin(), sr.end());
}
};
}
See it Live On Coliru
std::map
You can use a custom comparator:
std::map<char const*, int, std::less<std::string> > map;
Note that this is highly inefficient, but it shows the way.
More efficient would be to wrap/use strcmp:
#include <cstring>
struct less_sz
{
bool operator()(const char* a, const char* b) const
{
if (!(a && b))
return a < b;
else
return strcmp(a, b) < 0;
}
};
And then
std::map<char const*, int, less_sz> map;
See it Live On Coliru

subscript operators for class with std::map member variable

I am trying to make a class that wraps std::map and does checking to make sure the keys are one the of approved valid strings, and also initializes the map to have default values for all the approved valid strings. I am having issues getting the subscript operator to work, specifically the const version of it.
Here is my class prototyping code:
#include <set>
#include <string>
#include <map>
class foo {
public:
foo() {}
const double & operator[](const std::string key) const {
return data[key];
}
private:
static const std::set<std::string> validkeys;
std::map<std::string, double> data;
};
const std::set<std::string> foo::validkeys = {"foo1", "foo2"};
When I compile this (using g++ with -std=c++0x), I get this compilation error:
|| /home/luke/tmp/testmap.cc: In member function 'double& foo::operator[](std::string) const':
testmap.cc|10 col 22 error| passing 'const std::map<std::basic_string<char>, double>' as
'this' argument of 'mapped_type& std::map<_Key, _Tp, _Compare, _Alloc>::operator[](const
key_type&) [with _Key = std::basic_string<char>, _Tp = double, _Compare =
std::less<std::basic_string<char> >, _Alloc = std::allocator<std::pair<const
std::basic_string<char>, double> >, mapped_type = double, key_type =
std::basic_string<char>]' discards qualifiers
Nothing I do seems to fix this. I have tried
making validkeys a std::set and data std::map
using const char * instead of string
returning const double or double instead of const double &
using list and vector instead of set to store the validkeys
I don't know if I'm even approaching this problem correctly so if there is some other simple way to create a class that allows this kind of functionality:
foo a;
a["foo2"] = a["foo1"] = 5.0;
// This would raise a std::runtime_error because I would be checking that
// "foo3" isn't in validkeys
a["foo3"] = 4.0;
Any suggestions greatly appreciated.
SOLUTION
The following works exactly how I want it to, I even have a basic exception when you try to set or get a key that isn't in the set of valid keys:
#include <iostream>
#include <string>
#include <map>
#include <set>
#include <stdexcept>
class myfooexception : public std::runtime_error
{
public:
myfooexception(const std::string & s)
: std::runtime_error(s + " is not a valid key.") {}
};
class foo {
public:
foo() {
for (std::set<std::string>::iterator it = validkeys.begin();
it != validkeys.end();
++it) {
data[*it] = 0.0;
}
}
const double & operator[](const std::string & key) const {
if (data.find(key) == data.end()) {
throw myfooexception(key);
} else {
return data.find(key)->second;
}
}
double & operator[](const std::string & key) {
if (data.find(key) == data.end()) {
throw myfooexception(key);
} else {
return data[key];
}
}
private:
static const std::set<std::string> validkeys;
std::map<std::string, double> data;
};
const std::set<std::string> foo::validkeys = {"foo1", "foo2"};
int main(void)
{
foo a;
a["foo1"] = 2.0;
a["foo1"] = a["foo2"] = 1.5;
// a["foo3"] = 2.3; // raises exception: foo3 is is not a valid key
const foo b;
std::cout << b["foo1"]; // should be ok
// b["foo1"] = 5.0; // compliation error, as expected: b is const.
return 0;
}
The operator [] is not declared const in the std::map, because the operator [] also inserts a new element when the key is not found and returns a reference to its mapped value. You can use the map::find method instead of map::operator[] if you want your operator[] to be const.
The subscript operator for std::map is non-const as it inserts a new element if one does not yet exist. If you want your map to have a const operator[], you need to write one that uses map::find() and tests against map::end(), handling the error case.
you are trying to modify a const object!!
please remove the const of set.const members cannot be modified once they are initialised.
You are trying to assign to the std::map but your function is declared const and also returning const. Remove both const and it should work.