Reputation: 9912
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;
};
Upvotes: 4
Views: 3272
Reputation: 122238
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
.
Upvotes: 0
Reputation: 595837
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;
}
Upvotes: 4
Reputation: 119124
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.
Upvotes: 10