Associative containers The art of inserting gracefully Jean Guegant - - PowerPoint PPT Presentation

associative containers
SMART_READER_LITE
LIVE PREVIEW

Associative containers The art of inserting gracefully Jean Guegant - - PowerPoint PPT Presentation

Associative containers The art of inserting gracefully Jean Guegant Conditional insertion: if not already in there Overlookuping : overlooking the lookups std::unordered_map<std::string, aclass> cache; auto it = cache.find(key); if (it


slide-1
SLIDE 1

Associative containers

The art of inserting gracefully

Jean Guegant

slide-2
SLIDE 2

Conditional insertion: if not already in there

slide-3
SLIDE 3

Overlookuping : overlooking the lookups

std::unordered_map<std::string, aclass> cache; auto it = cache.find(key); if (it == cache.end()) { cache[key] = aclass(desc); } return cache[key];

slide-4
SLIDE 4

Overlookuping : overlooking the lookups

std::unordered_map<std::string, aclass> cache; auto it = cache.find(key); if (it == cache.end()) { cache[key] = aclass(desc); } return cache[key];

slide-5
SLIDE 5

Overlookuping : overlooking the lookups

std::unordered_map<std::string, aclass> cache; auto it = cache.find(key); if (it == cache.end()) { cache[key] = aclass(desc); } return cache[key];

SLOW

slide-6
SLIDE 6

C++98

auto result = cache.insert( std::pair<std::string, aclass>( key, desc ) ); return result.first->second;

slide-7
SLIDE 7

C++98

auto result = cache.insert( std::pair<std::string, aclass>( key, desc ) ); return result.first->second;

SLOW

slide-8
SLIDE 8

C++11

auto result = cache.emplace(key, desc); return result.first->second;

slide-9
SLIDE 9

C++11

auto result = cache.emplace(key, desc); return result.first->second;

SLOW

slide-10
SLIDE 10

The amazing standard quoting interlude

Effects: Inserts a value_ type object t constructed with std::forward<Args>(args)... if and only if there is no element in the container with key equivalent to the key of t. The bool component of the returned pair is true if and only if the insertion takes place, and the iterator component of the pair points to the element with key equivalent to the key of t.

What about the failure case?

Cppreference: The element may be constructed even if there already is an element with the key in the container, in which case the newly constructed element will be destroyed immediately.

What the F***... Am I reading?

slide-11
SLIDE 11

HOMO CPLUSPLUS COMMITUS

slide-12
SLIDE 12

C++17

auto [it, success] = cache.try_emplace(key, desc); return it->second;

slide-13
SLIDE 13

C++17

auto [it, success] = cache.try_emplace(key, desc); return it->second;

FINE

slide-14
SLIDE 14

> Smart pointers joins the game!

slide-15
SLIDE 15

Back to square one

std::unordered_map<std::string, std::unique_ptr<aclass>> cache; auto [it, success] = cache.try_emplace(key, std::make_unique<aclass>(desc)); Allocate & construct

slide-16
SLIDE 16

Back to square one

std::unordered_map<std::string, std::unique_ptr<aclass>> cache; auto [it, success] = cache.try_emplace(key, std::make_unique<aclass>(desc)); Allocate & construct

SLOW

slide-17
SLIDE 17

auto [it, success] = cache.try_emplace(key, nullptr); if (success) { it->second = std::make_unique<aclass>(desc); }

Exception safety

slide-18
SLIDE 18

auto [it, success] = cache.try_emplace(key, nullptr); if (success) { it->second = std::make_unique<aclass>(desc); }

Exception safety

What if there is an exception?

slide-19
SLIDE 19

auto [it, success] = cache.try_emplace(key, nullptr); if (success) { it->second = std::make_unique<aclass>(desc); }

Exception safety

What if there is an exception?

DANGEROUS

slide-20
SLIDE 20

So... I had an affair

slide-21
SLIDE 21

Lazy arguments à la D

slide-22
SLIDE 22

Lazy arguments à la D

template<class Factory> struct lazy_arg { using result_type = std::invoke_result_t<const Factory&>; constexpr lazy_arg(Factory&& factory) : factory(std::move(factory)) { } constexpr operator result_type() const noexcept(std::is_nothrow_invocable_v<const Factory&>) { return factory(); } Factory factory; };

slide-23
SLIDE 23

Lazy arguments à la D

template<class Factory> struct lazy_arg { using result_type = std::invoke_result_t<const Factory&>; constexpr lazy_arg(Factory&& factory) : factory(std::move(factory)) { } constexpr operator result_type() const noexcept(std::is_nothrow_invocable_v<const Factory&>) { return factory(); } Factory factory; };

Call a callable and return its result

slide-24
SLIDE 24

Lazy arguments à la D

auto arg = lazy_arg([&desc](){ return std::make_unique<aclass>(desc); }); cache.try_emplace(key, std::move(arg));

Award: works with C++17

slide-25
SLIDE 25

Factory method à la Rust

auto factory = [&desc](){ return std::make_unique<desc>(desc) }; cache.try_emplace_with(key, std::move(factory));

Award: neat but unavailable

slide-26
SLIDE 26

in_place constructors for smart pointers

cache.try_emplace(key, proposal::allocate_in_place<aclass>{}, desc);

Award: can be used with CTAD (Class Template Argument Deduction)

auto ptr = std::unique_ptr(proposal::allocate_in_place<aclass>{}, desc); == auto ptr = std::make_unique<aclass>(desc);

slide-27
SLIDE 27

Thanks

slide-28
SLIDE 28

Charming the committee

slide-29
SLIDE 29

A recipe for bugs

std::string key = "fiction"; auto result = cache.emplace(std::move(key), desc); if (!result.second) { std::cout << "There was an issue with " << key; } return result.first->second;

slide-30
SLIDE 30

A recipe for bugs

std::string key = "fiction"; auto result = cache.emplace(std::move(key), desc); if (!result.second) { std::cout << "There was an issue with " << key; } return result.first->second;

SLOW & DANGEROUS

slide-31
SLIDE 31

Conditional insertion

Associative containers (such as std::map, std::unordered_map...) have seen their interface (or concept) evolve quite a bit along the C++ standards: a lot more lookup and modifiers member functions are now available in C++20 than in C++98. While some of these operations were added mostly for convenience, quite a few of them brought more expressiveness and improved performance

  • alongside. For example: try_emplace (C++17) has more guarantees than

emplace (C++11) on what happen to a r-value key , emplace_hint (C++11) can be more efficient with the help of the user, et cetera. During this lightning talk, we will have a quick recap on some changes added along the years and the best practices when using associative containers. We will slowly reach the conclusion that the current interface for associative containers still has limitations. An associative container storing values of type unique_ptr make it difficult to express conditional insertion of such unique_ptrs into it in an efficient and elegant way! We will then see how to palliate this problem in a pure C++17 manner. Then, we will explore how the standard could potentially evolve the associative container interface one more time to help us. We will also compare how other languages tackle that issue in their own way.