learnvst
learnvst

Reputation: 16195

Using overloaded operator[] via an accessor function

I have an accessor function that returns a const reference to a type (std::map) ... ...

myMap_t const& getMap() const {return paramMap;} 

The type has an overloaded [] operator. What is the syntax to then use the [] operator directly from the getter function in a way like the following, but that actually works.

parameter = contextObj.getMap()[key];

Error message is:

context.cpp:35: error: passing   
  'const std::map<
     std::basic_string<char, std::char_traits<char>, std::allocator<char> >, 
     float, 
     std::less<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, 
     std::allocator<std::pair<
       const std::basic_string<char, std::char_traits<char>, std::allocator<char> >,
       float> > >'
as 'this' argument of 
  '_Tp& std::map<_Key, _Tp, _Compare, _Alloc>::operator[](const _Key&)  
with 
  _Key = std::basic_string<char, std::char_traits<char>, std::allocator<char> >,
  _Tp = float, 
  _Compare = std::less<std::basic_string<char, std::char_traits<char>,  td::allocator<char> > >, 
  _Alloc = std::allocator<std::pair<
    const std::basic_string<char, std::char_traits<char>, std::allocator<char> >, 
    float> >]' 
discards qualifiers

Upvotes: 3

Views: 460

Answers (4)

Karl Knechtel
Karl Knechtel

Reputation: 61509

context.cpp:35: error: passing   
  'const std::map<
     std::basic_string<char, std::char_traits<char>, std::allocator<char> >, 
     float, 
     std::less<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, 
     std::allocator<std::pair<
       const std::basic_string<char, std::char_traits<char>, std::allocator<char> >,
       float> > >'
as 'this' argument of 
  '_Tp& std::map<_Key, _Tp, _Compare, _Alloc>::operator[](const _Key&)  
with 
  _Key = std::basic_string<char, std::char_traits<char>, std::allocator<char> >,
  _Tp = float, 
  _Compare = std::less<std::basic_string<char, std::char_traits<char>,  td::allocator<char> > >, 
  _Alloc = std::allocator<std::pair<
    const std::basic_string<char, std::char_traits<char>, std::allocator<char> >, 
    float> >' 
discards qualifiers

std::basic_string is the template used to implement std::string, and indeed std::string is simply the instantiation based on char. So substitute that in first:

context.cpp:35: error: passing   
  'const std::map<
     std::string, 
     float, 
     std::less<std::string>, 
     std::allocator<std::pair<const std::string, float> >
  >'
as 'this' argument of 
  '_Tp& std::map<_Key, _Tp, _Compare, _Alloc>::operator[](const _Key&)  
with 
  _Key = std::string,
  _Tp = float, 
  _Compare = std::less<std::string>, 
  _Alloc = std::allocator<std::pair<const std::string> >' 
discards qualifiers

We don't really care about the _Compare (comparison function) and _Alloc (allocator) arguments for our map, since we're just using the defaults; so let's ignore those, and let's also substitute the _Key and _Tp values into the template description:

context.cpp:35: error: passing   
  'const std::map<std::string, float>'
as 'this' argument of 
  'float& std::map<std::string, float>::operator[](const std::string&)  
discards qualifiers

There we go, much simpler. We're using the operator[] of our std::map<std::string, float>, and we're trying to use it on a const std::map<std::string, float> (i.e., that's what we're using as the this argument for the call). This "discards qualifiers", specifically, the const qualifier.

The compiler is telling you that the operator[] of the map does not promise to keep the map const, i.e. it is allowed to change the map. And that is a compiler error, because the code is written with the built-in assertion that the map won't change.

Why would the map change? Well, look at the documentation:

If x matches the key of an element in the container, the function returns a reference to its mapped value.

If x does not match the key of any element in the container, the function inserts a new element with that key and returns a reference to its mapped value. Notice that this always increases the map size by one, even if no mapped value is assigned to the element (the element is constructed using its default constructor).

(Emphasis mine).

Inserting an element is certainly a modification.

Why does it do this? Well, we already have a question for that.

Upvotes: 1

Mooing Duck
Mooing Duck

Reputation: 66922

You return the std::map by const reference, but std::map::operator[] is not a const function, because sometimes it needs to alter the map.
To solve this, you should do one of the following:
(A) Use .find instead of []

auto iter = contextObj.getMap().find( key );
if (iter != contextObj.getMap().end())
    param = iter->second;

(B) Return the map as non const (not recommended)
(C) Make a wrapper class (not worth it most of the time)

Upvotes: 3

Grizzly
Grizzly

Reputation: 20191

You return a const myMap_t& from your method getMap(). From your error message myMap_t is a typedef for a std::map. Ther operator[] of std::map needs a modifiable object (can't be called on const), since it can insert items into the map (if no item with the specified key exists, it will insert one into the map and return a reference to that one). To solve that problem you have two options:

  • Use contextObj.getMap().find(key) instead of contextObj.getMap()[key] to get an iterator to the element (or to map.end() if it doesn't exist)
  • return myMap_t& (without const) to get a modifiable object on which operator[] can be called

Upvotes: 1

The problem is that operator[] in a map is a mutating operation, and you cannot call it on a const reference to a map. The reason that it is mutating is that it must return a reference to a value, and for that, if the key was not already present in the container, it will insert a new default constructed value addressed by that key and return the reference to it.

If you have a const reference, and you want to determine whether an element is present (or access it), you must use std::map<>::find that will return an iterator. If the element is not present the value of the iterator would be m.end()

Upvotes: 6

Related Questions