std::unorde...::operator=(3)

๐Ÿ“ฆ version 2024.07.05, in nixpkgs unstable, opensuse-leap 16.0

๐Ÿ’… std::unordered_multimap::operator= โ€” the C++ Standard Library glow-up, bestie โœจ

okay so real talk, this one lives in the C++ Standard Library (cppreference.com vibes, we'll get to that at the end ๐ŸŽ€) and it's the assignment operator for std::unordered_multimap. three overloads. three whole personalities. let's get into it ๐Ÿ’–


๐ŸŒธ NAME ๐ŸŒธ

std::unordered_multimap::operator= โ€” literally the assignment operator for std::unordered_multimap, no notes ๐Ÿ’…


โœจ Synopsis โœจ

okay so here's the whole menu, and yes there are three of them ๐Ÿซถ

unordered_multimap& operator=( const                 (1) (since
unordered_multimap& other );
unordered_multimap& operator=( unordered_multimap&&
(since C++11)
other );
(until C++17)
unordered_multimap& operator=( unordered_multimap&&  (2)
(since C++17)
other ) noexcept(/* see below */);
unordered_multimap& operator=(                           (3)
(since C++11)
std::initializer_list<value_type> ilist );

so clean it up in your head like this:

  • (1) โ€” unordered_multimap& operator=( const unordered_multimap& other ); โ€” since C++11
  • (2) โ€” unordered_multimap& operator=( unordered_multimap&& other ); โ€” since C++11, and until C++17 it looks exactly like that. from C++17 onward it's unordered_multimap& operator=( unordered_multimap&& other ) noexcept(/* see below */);
  • (3) โ€” unordered_multimap& operator=( std::initializer_list<value_type> ilist ); โ€” since C++11

and what do they all do? replaces the contents of the container. that's the whole assignment vibe, periodt ๐Ÿ”ฅ


๐Ÿ‘‘ the three overloads, explained like we're on facetime ๐Ÿ‘‘

1) Copy assignment operator. it replaces the contents with a copy of the contents of other.

and here's the part where the allocator gremlins show up ๐Ÿ™ˆ โ€” if std::allocator_traits<allocator_type>::propagate_on_container_copy_assignment::value is true, then the allocator of *this gets replaced by a copy of other's allocator. and if the allocator of *this after assignment would compare unequal to its old value, then the old allocator is used to deallocate the memory, and then the new allocator is used to allocate it, and only then are the elements copied. otherwise? the memory owned by *this may just get reused when possible. and in any case, the elements originally belonging to *this may be either destroyed or replaced by element-wise copy-assignment. iykyk ๐Ÿ’€

2) Move assignment operator. it replaces the contents with those of other using move semantics (i.e. the data in other is moved from other into this container). after that, other is in a valid but unspecified state โ€” so like, she's fine, but don't ask her questions ๐Ÿ˜ญ

same allocator drama but move-flavoured: if std::allocator_traits<allocator_type>::propagate_on_container_move_assignment::value is true, the allocator of *this is replaced by a copy of that of other. but if it's false and the allocators of *this and other do not compare equal, then *this cannot take ownership of the memory owned by other and has to move-assign each element individually, allocating additional memory using its own allocator as needed ๐Ÿซ  and in any case, all elements originally belonging to *this are either destroyed or replaced by element-wise move-assignment. no shortcuts, no cap.

3) replaces the contents with those identified by initializer list ilist. short, sweet, iconic ๐ŸŽ€


๐Ÿฆ‹ Parameters ๐Ÿฆ‹

  • other โ€” another container to use as data source ๐Ÿ’œ
  • ilist โ€” initializer list to use as data source ๐ŸŒธ

โญ Return value โญ

*this

that's it. that's the return value. she returns herself, main character energy ๐Ÿ‘‘


๐ŸŒˆ Complexity ๐ŸŒˆ

  1. Linear in the size of *this and other.
  2. Linear in the size of *this โ€” unless the allocators do not compare equal and do not propagate, in which case it's linear in the size of *this and other. ๐Ÿ˜ฎโ€๐Ÿ’จ
  3. Linear in the size of *this and ilist.

๐Ÿ’€ Exceptions ๐Ÿ’€

until C++17:

  • 1-3) May throw implementation-defined exceptions.

since C++17:

  • 1,3) May throw implementation-defined exceptions.
  • 2) noexcept specification:
noexcept(std::allocator_traits<Allocator>::is_always_equal::value
&& std::is_nothrow_move_assignable<Hash>::value
&& std::is_nothrow_move_assignable<Pred>::value)

that noexcept clause is long, but she's worth it ๐Ÿซถ


๐Ÿ“ Notes ๐Ÿ“

after container move assignment (overload (2)), unless element-wise move assignment is forced by incompatible allocators, references, pointers, and iterators (other than the end iterator) to other remain valid โ€” but they refer to elements that are now in *this. so they didn't die, they just moved in with you ๐Ÿ’…

the current standard makes this guarantee via the blanket statement in [container.reqmts]/67, and a more direct guarantee is under consideration via LWG issue 2321. keep that in your back pocket for trivia night โœจ


๐Ÿงš Example ๐Ÿงš

the following code uses operator= to assign one std::unordered_multimap to another:

// Run this code

#include <initializer_list>
#include <iostream>
#include <iterator>
#include <unordered_map>
#include <utility>

void print(auto const comment, auto const& container)
{
    auto size = std::size(container);
    std::cout << comment << "{ ";
    for (auto const& [key, value] : container)
        std::cout << '{' << key << ',' << value << (--size ? "}, " : "} ");
    std::cout << "}\n";
}

int main()
{
    std::unordered_multimap<int, int> x{{1,1}, {2,2}, {3,3}}, y, z;
    const auto w = {std::pair<const int, int>{4,4}, {5,5}, {6,6}, {7,7}};

    std::cout << "Initially:\n";
    print("x = ", x);
    print("y = ", y);
    print("z = ", z);

    std::cout << "Copy assignment copies data from x to y:\n";
    y = x;
    print("x = ", x);
    print("y = ", y);

    std::cout << "Move assignment moves data from x to z, modifying both x and z:\n";
    z = std::move(x);
    print("x = ", x);
    print("z = ", z);

    std::cout << "Assignment of initializer_list w to z:\n";
    z = w;
    print("w = ", w);
    print("z = ", z);
}

๐ŸŒŸ Possible output ๐ŸŒŸ

Initially:
x = { {3,3}, {2,2}, {1,1} }
y = { }
z = { }
Copy assignment copies data from x to y:
x = { {3,3}, {2,2}, {1,1} }
y = { {3,3}, {2,2}, {1,1} }
Move assignment moves data from x to z, modifying both x and z:
x = { }
z = { {3,3}, {2,2}, {1,1} }
Assignment of initializer_list w to z:
w = { {4,4}, {5,5}, {6,6}, {7,7} }
z = { {7,7}, {6,6}, {5,5}, {4,4} }

notice how the order is all over the place? that's an unordered multimap for you bestie, she does what she wants ๐Ÿฆ‹ (hence "possible output" โ€” don't come for me ๐Ÿ™ˆ)


๐ŸŽ€ See also ๐ŸŽ€

  • constructor โ€” constructs the unordered_multimap (public member function) โœจ

๐Ÿท๏ธ Category ๐Ÿท๏ธ

  • conditionally noexcept

ok so that's the whole page!! copy it, move it, initializer-list it, live ur truth ๐Ÿ’–

made with love, allocated with care, and yes the allocator propagated ๐Ÿซถ

this little beauty came to you from http://cppreference.com, dated 2024.06.10, and it's page std::unorde...::operator=(3) of the C++ Standard Library docs โ€” go drink some water and then go assign something, mwah ๐Ÿ’…๐ŸŽ€

๐Ÿ“ฆ where this page comes from