106

Contrived example, for the sake of the question:

void MyClass::MyFunction( int x ) const
{
  std::cout << m_map[x] << std::endl
}

This won't compile, since the [] operator is non-const.

This is unfortunate, since the [] syntax looks very clean. Instead, I have to do something like this:

void MyClass::MyFunction( int x ) const
{
  MyMap iter = m_map.find(x);
  std::cout << iter->second << std::endl
}

This has always bugged me. Why is the [] operator non-const?

Luc Hermitte
  • 31,979
  • 7
  • 69
  • 83
Runcible
  • 7,006
  • 12
  • 42
  • 62

6 Answers6

101

For std::map and std::unordered_map, operator[] will insert the index value into the container if it didn't previously exist. It's a little unintuitive, but that's the way it is.

Since it must be allowed to fail and insert a default value, the operator can't be used on a const instance of the container.

http://en.cppreference.com/w/cpp/container/map/operator_at

Adrian W
  • 4,563
  • 11
  • 38
  • 52
Alan
  • 4,897
  • 2
  • 24
  • 17
  • 3
    `std::set` doesn't have `operator[]`. – avakar Sep 25 '09 at 06:20
  • 4
    That is the right answer, but a const version could do the same thing as the "at" member. That is throw an std::out_of_range... – Jean-Simon Brochu Aug 08 '17 at 13:27
  • 3
    When used to read a value there is no default value to provide. `std::vector` has a read operator `[]` that is `const`. `map` should do the same. – wcochran Sep 16 '20 at 18:16
  • 1
    @Jean-SimonBrochu the convention across all containers is that `at` may throw and `[]` does not. I don't think it worth breaking that convention. – Caleth Mar 25 '21 at 12:59
64

Now that with C++11 you can have a cleaner version by using at()

void MyClass::MyFunction( int x ) const
{
  std::cout << m_map.at(x) << std::endl;
}
Deqing
  • 14,098
  • 15
  • 84
  • 131
  • 5
    If `map` have const and non-const `at()`s - why not the same also for `operator[]` ? with the const version not inserting anything but rather throwing? (Or returning an optional, when std::optional makes it into the standard) – einpoklum Dec 10 '15 at 16:19
  • @einpoklum The point of const correctness is mostly static compile-time checking. I'd rather the compiler complain than throw an exception because I didn't use const objects correctly. – Millie Smith Apr 18 '16 at 00:07
  • @einpoklum Very late, but for other readers: having two overloads doing such different things would be terrible. The only reason `at` comes in two flavors is because it does a `return *this;`, and the only difference between the overloads is the `const`-ness of the reference returned. The actual effects of both `at`s are the exact same (that is, no effect). – HTNW Apr 20 '20 at 20:38
30

Note for new readers.
The original question was about STL containers (not specifically about the std::map)

It should be noted there is a const version of operator [] on most containers.
It is just that std::map and std::set do not have a const version and this is a result of the underlying structure that implements them.

From std::vector

reference       operator[](size_type n) 
const_reference operator[](size_type n) const 

Also for your second example you should check for a failure to find the element.

void MyClass::MyFunction( int x ) const
{
    MyMap iter = m_map.find(x);
    if (iter != m_map.end())
    {
        std::cout << iter->second << std::endl
    }
}
Dan Nissenbaum
  • 13,558
  • 21
  • 105
  • 181
Martin York
  • 257,169
  • 86
  • 333
  • 562
2

Since operator[] might insert a new element into the container, it can't possibly be a const member function. Note that the definition of operator[] is extremely simple: m[k] is equivalent to (*((m.insert(value_type(k, data_type()))).first)).second. Strictly speaking, this member function is unnecessary: it exists only for convenience

Satbir
  • 6,358
  • 6
  • 37
  • 52
0

An index operator should only be const for a read-only container (which doesn't really exist in STL per se).

Index operators aren't only used to look at values.

Nick Bedford
  • 4,365
  • 30
  • 36
  • 8
    The question is, why doesn't it have two overloaded versions - one `const`, another non-`const` - as e.g. `std::vector` does. – Pavel Minaev Sep 25 '09 at 01:49
-2

If you declare your std::map member variable to be mutable

mutable std::map<...> m_map;

you can use the non-const member functions of std::map within your const member functions.

Anthony Cramp
  • 4,495
  • 6
  • 26
  • 27
  • 16
    This is a terrible idea, though. – GManNickG Sep 25 '09 at 21:58
  • 7
    The API for your class lies if you do that. The function claims that it's const -- meaning that it won't modify any member variables -- but in reality it might be modifying the m_map data member. – Runcible Mar 18 '10 at 18:33
  • 2
    `mutable` can be used for members like `std::mutex`, caches and debug helpers. If the map is to be used as a cache to speed up a very expensive `const` "getter" function, then `mutable` is acceptable. You need to be careful, but it is not a terrible idea on its own. – Mark Lakata Sep 28 '15 at 23:04