linofex
linofex

Reputation: 340

can't I use map [ ] operator with const function keyword?

I have got this function:

bool Table::FindCard(const int& i, const Card& card) const{
  std::vector<Card>::const_iterator iter = table[i].begin();
  for(; iter != table[i].end() ; ++iter){ 
    if(*iter == card){
        return true;
    }
  }

  return false;
}

where Card is a class and Table is a class with:

std::map<int, std::vector<Card> >  table;

If I run the code I get this error:

Table.cc: In member function ‘bool Table::FindCard(const int&, const      Card&) const’:
Table.cc:42:50: error: passing ‘const t_map {aka const std::map<int,      std::vector<Card> >}’ as ‘this’ argument discards qualifiers [-fpermissive]
std::vector<Card>::const_iterator iter = table[i].begin();

But if I delete const keyword:

bool Table::FindCard(const int& i, const Card& card){
...  
}

all works.

The reason is because operator[] isn't const? I know that a const function can call only other const function.

Thanks

Upvotes: 0

Views: 1616

Answers (5)

Richard Hodges
Richard Hodges

Reputation: 69864

There's a philosophical question at the root of this, as with all c++ library decisions.

In a vector, operator[] is defined for both const and non-const cases. This is sensible because for a vector of length N, operator[](0...N-1) will always have meaning, whether const or not. The element will exist.

With this as a baseline, what should a map do? There is no absolute criterion to determine whether any subscript (key) exists at all.

With mutable operator[], choosing to default-construct the element is reasonable - after all the caller referenced it, he expects it to exist, and we can make it exist. This is the path of least surprise.

Now what about the immutable case? The element may or may not exist, and if it does not, we cannot mutate the map since that would violate the spirit of const.

Neither should we throw an exception, because this might be surprising to someone who is used to dealing with vector, where no exception is expected (or possible!). We would then have two similar-looking interfaces with very dissimilar behaviour.

The answer is to not provide mutable operator[] at all. Developers (like you) may be surprised to find that it does not exist, but they have a chance to look in the documentation and realise that they are barking up the wrong tree.

As mentioned, we have at() (throws exceptions if subscript does not exist, like a vector) and we have find().

With a little help from boost (and soon, c++17) we can give ourselves a utility function:

#include <map>
#include <string>
#include <utility>
#include <type_traits>
#include <iostream>
#include <boost/optional.hpp>


template<class Map>
auto maybe_get_impl(Map& map, typename Map::key_type const& key)
{
    using reference_type = std::conditional_t<
    std::is_const<std::remove_reference_t<Map>>::value,
    typename Map::mapped_type const&,
    typename Map::mapped_type&>;
    boost::optional<reference_type> result;
    auto ifind = map.find(key);
    if (ifind != map.end())
    {
        result = ifind->second;
    }
    return result;
}

template<class K, class V, class Comp, class A>
auto maybe_get(std::map<K, V, Comp, A> const& map, K const& key)
{
    return maybe_get_impl(map, key);
}

template<class K, class V, class Comp, class A>
auto maybe_get(std::map<K, V, Comp, A>& map, K const& key)
{
    return maybe_get_impl(map, key);
}

int main()
{
    std::map<int, std::string> mymap;
    mymap.emplace(1, "hello");
    mymap.emplace(2, "world");

    // note: non-const because we're taking a reference from a mutable map;
    std::string part = std::string("goodbye, cruel world");

    std::cout << maybe_get(mymap, 1).value_or(part) << std::endl;
    std::cout << maybe_get(mymap, 2).value_or(part) << std::endl;
    std::cout << maybe_get(mymap, 0).value_or(part) << std::endl;
}

expected output:

hello
world
goodbye, cruel world

Upvotes: 0

BiagioF
BiagioF

Reputation: 9705

As already said: operator[] is a no-cont member, you can replace with at.

I just want to add to the previous answers the compilable code, in order to give you a visual example.

bool Table::FindCard(const int& i, const Card& card) const{
  std::vector<Card>::const_iterator iter = table.at(i).begin();
  for(; iter != table.at(i).end() ; ++iter){ 
    if(*iter == card){
        return true;
    }
  }

  return false;
}

Upvotes: 0

Sergei
Sergei

Reputation: 550

operator [] will insert an element into the map if the key doesn't exist yet. It is not a const operation. Use map.at() to preserve constness.

Upvotes: 2

Chad
Chad

Reputation: 19032

For std::map, operator[] will return a reference to the value associated with the given key. If that key is not present, it will be inserted. Since this obviously changes the map, this function is not const, and cannot be called against a const object.

Upvotes: 0

Matteo Italia
Matteo Italia

Reputation: 126787

The reason is because operator[] isn't const? I know that a const function can call only other const function.

Yes; operator[] is specified to create a new default-constructed element if the key doesn't exist, and this wouldn't be sensible on a const map, thus it's not marked as const.

Of course you could specify the const version to throw an exception (as at does) or - I don't know - call terminate if the key isn't found, but alas the standard doesn't say that. You'll either have to use at or (ugh) find.

Upvotes: 3

Related Questions