std::map::operator[](3)

πŸ“¦ version 2024.07.05, in nixpkgs unstable, opensuse-leap 16.0

🌸 NAME

Okay bestie, this is std::map::operator[] from the C++ Standard Libary manual β€” page 3, and yes the header spelling is literally β€œLibary” and we are not touching it πŸ’…. It’s the std::map operator that looks up a key, inserts a default value if the key isn’t there, and hands you back a reference. Iconic behavior ✨

✨ Synopsis

T& operator[]( const Key& key ); (1)
T& operator[]( Key&& key );      (2) (since C++11)
template< class K >              (3) (since C++26)
T& operator[]( K&& x );

This returns a reference to the value that is mapped to a key equivalent to key or x respectively, and it performs an insertion if such a key does not already exist. Slay πŸ’–

Overload (1): Inserts value_type(key, T()) if the key does not exist.

  • key_type must meet the requirements of CopyConstructible.
  • mapped_type must meet the requirements of CopyConstructible and, until C++11, DefaultConstructible.

If an insertion is performed, the mapped value is value-initialized (default-constructed for class types, zero-initialized otherwise) and a reference to it is returned.

Overload (1) also has a C++17-and-later description: it inserts a value_type object constructed in-place from std::piecewise_construct, std::forward_as_tuple(key), std::tuple<>() if the key does not exist. It’s equivalent to:

return this->try_emplace(key).first->second;

(since C++17) When the default allocator is used, this results in the key being copy constructed from key and the mapped value being value-initialized.

  • Since C++11, value_type must be EmplaceConstructible from std::piecewise_construct, std::forward_as_tuple(key), std::tuple<>(). When the default allocator is used, this means that key_type must be CopyConstructible and mapped_type must be DefaultConstructible.

Overload (2): Inserts a value_type object constructed in-place from std::piecewise_construct, std::forward_as_tuple(std::move(key)), std::tuple<>() if the key does not exist. It’s equivalent to:

return this->try_emplace(std::move(key)).first->second;

(since C++17) When the default allocator is used, this results in the key being move constructed from key and the mapped value being value-initialized.

  • value_type must be EmplaceConstructible from std::piecewise_construct, std::forward_as_tuple(std::move(key)), std::tuple<>(). When the default allocator is used, this means that key_type must be MoveConstructible and mapped_type must be DefaultConstructible.

Overload (3): Inserts a value_type object constructed in-place if there is no key that transparently compares equivalent to the value x. It’s equivalent to:

return this->try_emplace(std::forward<K>(x)).first->second;

This overload participates in overload resolution only if the qualified-id Compare::is_transparent is valid and denotes a type. It allows calling this function without constructing an instance of Key.

No iterators or references are invalidated. Periodt 🌈

🧸 Parameters

  • key β€” the key of the element to find.
  • x β€” a value of any type that can be transparently compared with a key.

πŸŽ€ Return value

  • 1, 2 β€” a reference to the mapped value of the new element if no element with key key existed. Otherwise, a reference to the mapped value of the existing element whose key is equivalent to key.
  • 3 β€” a reference to the mapped value of the new element if no element with key that compares equivalent to the value x existed. Otherwise, a reference to the mapped value of the existing element whose key compares equivalent to x.

πŸ’… Exceptions

If an exception is thrown by any operation, the insertion has no effect. The map stays unbothered, as it should. πŸ’…

πŸ¦‹ Complexity

Logarithmic in the size of the container.

🧚 Notes

Back in the published C++11 and C++14 standards, this function was specified to require mapped_type to be DefaultInsertable and key_type to be CopyInsertable or MoveInsertable into *this. That spec was defective, bestie β€” it got fixed by LWG issue 2469, and the description above already incorporates the resolution of that issue.

However, one implementation (libc++) is known to construct the key_type and mapped_type objects via two separate allocator construct() calls, as arguably required by the standards as published, rather than emplacing a value_type object.

operator[] is non-const because it inserts the key if it doesn’t exist. If this behavior is undesirable, or if the container is const, at may be used.

insert_or_assign returns more information than operator[] and does not (since C++17) require default-constructibility of the mapped type.

Feature-test macro vibes:

  • Feature-test macro: __cpp_lib_associative_heterogeneous_insertion
  • Value: 202311L
  • Std: (C++26)
  • Feature: Heterogeneous overloads for the remaining member functions in ordered and unordered associative containers. (3)

πŸ“ Example

The page has a // Run this code link; here’s the whole thing, untouched:

#include <iostream>
#include <string>
#include <map>

void println(auto const comment, auto const& map)
{
    std::cout << comment << '{';
    for (const auto& pair : map)
        std::cout << '{' << pair.first << ": " << pair.second << '}';
    std::cout << "}\n";
}

int main()
{
    std::map<char, int> letter_counts{{'a', 27}, {'b', 3}, {'c', 1}};

    println("letter_counts initially contains: ", letter_counts);

    letter_counts['b'] = 42; // updates an existing value
    letter_counts['x'] = 9;  // inserts a new value

    println("after modifications it contains: ", letter_counts);

    // count the number of occurrences of each word
    // (the first call to operator[] initialized the counter with zero)
    std::map<std::string, int>  word_map;
    for (const auto& w : {"this", "sentence", "is", "not", "a", "sentence",
                          "this", "sentence", "is", "a", "hoax"})
        ++word_map[w];
    word_map["that"]; // just inserts the pair {"that", 0}

    for (const auto& [word, count] : word_map)
        std::cout << count << " occurrence(s) of word '" << word << "'\n";
}

🌈 Output

letter_counts initially contains: {{a: 27}{b: 3}{c: 1}}
after modifications it contains: {{a: 27}{b: 42}{c: 1}{x: 9}}
2 occurrence(s) of word 'a'
1 occurrence(s) of word 'hoax'
2 occurrence(s) of word 'is'
1 occurrence(s) of word 'not'
3 occurrence(s) of word 'sentence'
0 occurrence(s) of word 'that'
2 occurrence(s) of word 'this'

πŸ’€ Defect reports

The following behavior-changing defect reports were applied retroactively to previously published C++ standards:

  • DR: LWG 334 β€” Applied to: C++98 β€” Behavior as published: the effect of overload (1) was simply returning (*((insert(std::make_pair(x, T()))).first)).second β€” Correct behavior: provided its own description instead.

πŸ‘‘ See also

  • at β€” access specified element with bounds checking (public member function).
  • insert_or_assign (C++17) β€” inserts an element or assigns to the current element if the key already exists (public member function).
  • try_emplace (C++17) β€” inserts in-place if the key does not exist, does nothing if the key exists (public member function).

Okay that’s the whole page, bestie 🫢. Source: http://cppreference.com Β· version/date: 2024.06.10 Β· page: std::map::operator[](3). Go forth and index responsibly πŸ’–.

πŸ“¦ where this page comes from