std::unorde...mplace_hint(3)

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

πŸŽ€ std::unordered_set::emplace_hint(3) πŸŽ€

okay so this one's from the C++ Standard Libary manual (that's the manual's name bestie, i'm not making it up, it's on the tin) and it's about std::unordered_set::emplace_hint β€” say that five times fast, i dare you πŸ’…


🌸 NAME 🌸

std::unordered_set::emplace_hint β€” std::unordered_set::emplace_hint

like yeah. the name is the name. it's giving "i contain myself" energy and honestly? iconic.


✨ Synopsis ✨

template< class... Args >
iterator emplace_hint( const_iterator hint, Args&&... args );

(since C++11) ⭐ β€” yes queen, she's been here since 2011.


okay so the tea β˜•: this bad girl inserts a new element into the container, and it takes hint as a suggestion for where the element should go. a suggestion, bestie. a vibe check. a lil nudge. not a demand 🫢

the constructors of the key and mapped value get called with exactly the same arguments you handed to the function, forwarded with std::forward<Args>(args).... no cap, exactly the same ones.

and now the part where you need to actually pay attention 😭 β€” if after the operation the new number of elements is greater than old max_load_factor() * bucket_count(), a rehashing takes place. and if rehashing occurs (because of the insertion), all iterators are invalidated. otherwise (no rehashing), iterators are not invalidated. that's the whole deal, fr fr. don't come crying to me when your iterator ghosted you πŸ’€


πŸ¦‹ Parameters πŸ¦‹

  • hint β€” an iterator, used as a suggestion as to where to insert the new element. only a suggestion!! she's flexible πŸ’…
  • args β€” the arguments you forward to the constructor of the element. all of them. every single one.

🌈 Return value 🌈

an iterator to the inserted element β€” or to the element that prevented the insertion. so either way you get an iterator back, you're never left on read 😌


πŸ’œ Exceptions πŸ’œ

if an exception is thrown for any reason, this function has no effect. that's the strong exception safety guarantee, and she EARNED that adjective πŸ”₯ strong exception safety guarantee. periodt.


⭐ Complexity ⭐

amortized constant on average, worst case linear in the size of the container. so like, usually a slay, occasionally a whole saga 🫠


πŸ“ Example πŸ“

// Run this code

#include <chrono>
#include <cstddef>
#include <functional>
#include <iomanip>
#include <iostream>
#include <unordered_set>

const int n_operations = 100'500'0;

std::size_t set_emplace()
{
    std::unordered_set<int> set;
    for (int i = 0; i < n_operations; ++i)
        set.emplace(i);
    return set.size();
}

std::size_t set_emplace_hint()
{
    std::unordered_set<int> set;
    auto it = set.begin();
    for (int i = 0; i < n_operations; ++i)
    {
        set.emplace_hint(it, i);
        it = set.end();
    }
    return set.size();
}

std::size_t set_emplace_hint_wrong()
{
    std::unordered_set<int> set;
    auto it = set.begin();
    for (int i = n_operations; i > 0; --i)
    {
        set.emplace_hint(it, i);
        it = set.end();
    }
    return set.size();
}

std::size_t set_emplace_hint_corrected()
{
    std::unordered_set<int> set;
    auto it = set.begin();
    for (int i = n_operations; i > 0; --i)
    {
        set.emplace_hint(it, i);
        it = set.begin();
    }
    return set.size();
}

std::size_t set_emplace_hint_closest()
{
    std::unordered_set<int> set;
    auto it = set.begin();
    for (int i = 0; i < n_operations; ++i)
        it = set.emplace_hint(it, i);
    return set.size();
}

double time_it(std::function<std::size_t()> set_test,
               const char* what = nullptr,
               double ratio = 0.0)
{
    const auto start = std::chrono::system_clock::now();
    const std::size_t setsize = set_test();
    const auto stop = std::chrono::system_clock::now();
    const std::chrono::duration<double, std::milli> time = stop - start;
    if (what != nullptr && setsize > 0)
        std::cout << std::setw(8) << time << " for " << what << " (ratio: "
                  << (ratio == 0.0 ? 1.0 : ratio / time.count()) << ")\n";
    return time.count();
}

int main()
{
    std::cout << std::fixed << std::setprecision(2);
    time_it(set_emplace); // cache warmup
    const auto x = time_it(set_emplace, "plain emplace");
    time_it(set_emplace_hint, "emplace with correct hint", x);
    time_it(set_emplace_hint_wrong, "emplace with wrong hint", x);
    time_it(set_emplace_hint_corrected, "corrected emplace", x);
    time_it(set_emplace_hint_closest, "emplace using returned iterator", x);
}

🌟 Possible output 🌟

146.88ms for plain emplace (ratio: 1.00)
168.20ms for emplace with correct hint (ratio: 0.87)
168.78ms for emplace with wrong hint (ratio: 0.87)
166.58ms for corrected emplace (ratio: 0.88)
168.27ms for emplace using returned iterator (ratio: 0.87)
ngl the set_emplace_hint block sets it = set.end() every single loop and the "corrected" version sets it to set.begin() β€” that's literally in the source, i'm not touching it, i just report the lore πŸ™ˆ

πŸ‘‘ See also πŸ‘‘

  • emplace β€” constructs element in-place (public member function)
  • insert β€” inserts elements or nodes (since C++17) (public member function)

that's the whole page bestie, you ate that πŸ’– no rehashing required. this lil number came from http://cppreference.com and it's dated 2024.06.10, so go hydrate and thank the nice people who wrote it 🧚✨

πŸ“¦ where this page comes from