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.
Related
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.
I have a Container class that is meant to store a vector of shared pointers. Whenever an item is appended to the Container, I want it to assume ownership of that item. In other words, when the Container is deconstructed, all of the elements inside it should also be deconstructed.
template <typename T>
class Container
{
private:
const std::vector<std::shared_ptr<T>> vec_;
public:
void append(std::shared_ptr<T> item)
{
vec_.push_back(std::move(item));
}
void printElements()
{
for (int i = 0; i < vec_.size(); i++) { std::cout << vec_[i] << std::endl; }
}
};
int main(int argc, char** argv) {
std::unique_ptr<Container<std::string>> c = std::make_unique<Container<std::string>>();
c->append(std::make_shared<std::string>("hello"));
return 0;
}
The problem is, I get the following error on vec_.push_back(std::move(item)).
No matching member function for call to 'push_back'
I'm not sure why this error is occurring.
Original answer:
Your std::vector, vec_ is const. Seems to me that would forcefully remove or disable any method, such as push_back(), which tries to modify the const vector.
Additions added 10 May 2020:
I think it's worth expounding upon this comment from #Carpetfizz in the comments under his question, too:
#GabrielStaples thanks, removing const did the trick. I thought const only protected against reassignment of vector_ but allowed for it to be mutated?
My response:
No, const here applies to the contents of the vector. The vector isn't a pointer, but what you're talking about could be done with pointers, by making the pointer itself const instead of the contents of what it points to const. Also, const and mutable are opposites. One undoes the other. You cannot have both in effect at the same time. By definition, something constant is immutable (unchangeable), and something mutable is non-constant.
And how might one make a pointer const but not the contents of what it points to?
First, consider the original code (with some minor modifications/fixes I did to it):
Run it yourself online here: https://onlinegdb.com/SyMqoeU9L
1) cpp_template_const_vector_of_smart_ptrs_test_BEFORE.cpp:
#include <iostream>
#include <memory>
#include <vector>
template <typename T>
class Container
{
private:
// const std::vector<std::shared_ptr<T>> vec_; // does NOT work
std::vector<std::shared_ptr<T>> vec_; // works!
public:
void append(std::shared_ptr<T> item)
{
vec_.push_back(std::move(item));
}
void printElements()
{
for (int i = 0; i < vec_.size(); i++)
{
// Don't forget to dereference the pointer with `*` in order to
// obtain the _contens of the pointer_ (ie: what it points to),
// rather than the pointer (address) itself
std::cout << *vec_[i] << std::endl;
}
}
};
int main(int argc, char** argv)
{
std::unique_ptr<Container<std::string>> c = std::make_unique<Container<std::string>>();
c->append(std::make_shared<std::string>("hello"));
c->append(std::make_shared<std::string>("world"));
c->printElements();
return 0;
}
Output:
hello
world
And here's the new code demonstrating how to make a constant pointer to a non-const vector. See my comments here, and study the changes:
Run it yourself online here: https://onlinegdb.com/HyjNx-L5U
2) cpp_template_const_vector_of_smart_ptrs_test_AFTER.cpp
#include <iostream>
#include <memory>
#include <vector>
template <typename T>
class Container
{
private:
// const std::vector<std::shared_ptr<T>> vec_; // does NOT work
// Create an alias to this type just to make the creation below less
// redundant in typing out the long type
using vec_type = std::vector<std::shared_ptr<T>>;
// NON-const object (vector)--so it can be changed
vec_type vec_;
// const pointer to NON-const object--so, vec_p_ can NOT be re-assigned to
// point to a new vector, because it is `const`! But, **what it points to**
// CAN be changed because it is NOT const!
vec_type * const vec_p_ = &vec_;
// This also does NOT work (in place of the line above) because it makes
// the **contents of what you're pointing to const**, which means again
// that the contents of the vector can NOT be modified.
// const vec_type * const vec_p_ = &vec_; // does NOT work
// Here's the compile-time error in gcc when compiling for C++17:
// main.cpp: In instantiation of ‘void Container<T>::append(std::shared_ptr<_Tp>) [with T = std::basic_string<char>]’:
// <span class="error_line" onclick="ide.gotoLine('main.cpp',78)">main.cpp:78:53</span>: required from here
// main.cpp:61:9: error: passing ‘const vec_type {aka const std::vector >, std::allocator > > >}’ as ‘this’ argument discards qualifiers [-fpermissive]
// vec_p_->push_back(std::move(item));
// ^~~~~~
// In file included from /usr/include/c++/7/vector:64:0,
// from main.cpp:22:
// /usr/include/c++/7/bits/stl_vector.h:953:7: note: in call to ‘void std::vector<_Tp, _Alloc>::push_back(std::vector<_Tp, _Alloc>::value_type&&) [with _Tp = std::shared_ptr >; _Alloc = std::allocator > >; std::vector<_Tp, _Alloc>::value_type = std::shared_ptr >]’
// push_back(value_type&& __x)
// ^~~~~~~~~
// To prove that vec_p_ can NOT be re-assigned to point to a new vector,
// watch this:
vec_type vec2_;
// vec_p_ = &vec2_; // COMPILE-TIME ERROR! Here is the error:
// main.cpp:44:5: error: ‘vec_p_’ does not name a type; did you mean ‘vec_type’?
// vec_p_ = &vec2_; // COMPILE-TIME ERROR!
// ^~~~~~
// vec_type
// BUT, this works just fine:
vec_type * vec_p2_ = &vec2_; // non-const pointer to non-const data
public:
void append(std::shared_ptr<T> item)
{
vec_p_->push_back(std::move(item));
}
void printElements()
{
for (int i = 0; i < vec_p_->size(); i++)
{
// Notice we have to use a double de-reference here now!
std::cout << *(*vec_p_)[i] << std::endl;
}
}
};
int main(int argc, char** argv)
{
std::unique_ptr<Container<std::string>> c = std::make_unique<Container<std::string>>();
c->append(std::make_shared<std::string>("hello"));
c->append(std::make_shared<std::string>("world"));
c->printElements();
return 0;
}
Output:
hello
world
I am trying to get a constant reference back by a method for the mapped value of an unordered_map. The unordered_map is a class member. However, the code below does not work and raises the error stated in the title.
I tried to change const umap_int::mapped_type & to const int & which did not work either. The standard example of returning a const reference to a variable of simple datatype (int, double, ...) worked.
#include <unordered_map>
using namespace std;
typedef unordered_map<int, int> umap_int;
class class_A{
public:
class_A(){
for(int k=0; k<3;++k)
M[k] = k;
}
const umap_int::mapped_type & get_M(int key) const{
return M[key];
}
private:
umap_int M;
};
int main(){
class_A A;
return 0;
}
Inside const method you can only call for M its const member functions. Both unordered_map::operator[] overloads are non-const - reference. So you cannot use it inside const get_M. You could remove const qualifier from get_M signature, or use find which has const overload but then you need to handle a case when mapped value doesn't exist for the passed key:
const umap_int::mapped_type & get_M(int key) const {
//return M[key];
auto it = M.find(key);
if (it != M.end())
return it->second;
// do sth here ...
// throw exception
// make static variable with default value which will be accessed
}
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.
Let take map for this example.
I set the map object: map<const char*, int, compare> a, as compare is the following:
struct compare : public std::binary_function<const char*, const char*, bool>
{
bool operator() (const char* a, const char* b) {
return strcmp(a, b) < 0;
}
};
What have I done here? How did I overload this operator? Isn't that unary operator?
It's working, but I'm not sure that I really know what I've written here.
this is the complete code:
#include <set>
#include <map>
#include <string>
#include <iostream>
using namespace std;
struct compare : public std::binary_function<const char*, const char*, bool>
{
bool operator() (const char* a, const char* b) {
return strcmp(a, b) < 0;
}
};
int main() {
map<const char*, int, compare> a;
a["Mike"] = 5;
a["Tre"] = 3;
a["Billie"] = 20;
for(map<const char*, int, compare>::iterator it = a.begin(); it != a.end(); ++it) {
cout << (*it).first << endl;
}
cin.get();
}
Your compare definition permits the following:
compare cmp;
bool result = cmp("foo", "bar"); // Two arguments, therefore not unary!
Thus std::map can use it to determine the relative ordering of pairs of elements. This is required in order to construct a binary search tree behind-the-scenes.
What have I done here?
You created a function object - an object that provides an implementation for the operator (), and supplied its class to instantiate the std::map template.
How did I overload this operator?
You provided its public implementation (note that struct makes its members public by default).
Isn't that unary operator?
No. A unary operator takes one operand; your operator takes two operands, and is, therefore, binary.