From be7c63e1bf3672e7476b1d1da7d6376263f730e4 Mon Sep 17 00:00:00 2001 From: Braden Ganetsky Date: Tue, 6 Feb 2024 23:36:27 -0600 Subject: [PATCH 1/9] Add foa and fca tests for emplace(k,v) --- test/helpers/count.hpp | 5 +- test/unordered/emplace_smf_tests.cpp | 136 ++++++++++++++++++++++++++- 2 files changed, 138 insertions(+), 3 deletions(-) diff --git a/test/helpers/count.hpp b/test/helpers/count.hpp index 5a86ae9c..b53c0f33 100644 --- a/test/helpers/count.hpp +++ b/test/helpers/count.hpp @@ -147,6 +147,8 @@ namespace test { static smf_count count; static void reset_count() { count.reset(); } + smf_counted_object(int index) : smf_counted_object() { index_ = index; } + smf_counted_object() : index_(++running_index) { count.default_construct(); @@ -184,9 +186,10 @@ namespace test { return boost::hash()(x.index_); } + int index_; + private: static int running_index; - int index_; }; template smf_count smf_counted_object::count = {}; template int smf_counted_object::running_index = 0; diff --git a/test/unordered/emplace_smf_tests.cpp b/test/unordered/emplace_smf_tests.cpp index 368e1cf5..b67bfe59 100644 --- a/test/unordered/emplace_smf_tests.cpp +++ b/test/unordered/emplace_smf_tests.cpp @@ -12,12 +12,30 @@ namespace emplace_smf_tests { using test::smf_count; using test::smf_counted_object; - using counted_key = smf_counted_object; - using counted_value = smf_counted_object; + using converting_key = smf_counted_object; + using converting_value = smf_counted_object; + + class counted_key : public smf_counted_object + { + public: + using smf_counted_object::smf_counted_object; + counted_key() = default; + counted_key(const converting_key& k) : counted_key(k.index_) {} + }; + class counted_value : public smf_counted_object + { + public: + using smf_counted_object::smf_counted_object; + counted_value() = default; + counted_value(const converting_value& v) : counted_value(v.index_) {} + }; + void reset_counts() { counted_key::reset_count(); counted_value::reset_count(); + converting_key::reset_count(); + converting_value::reset_count(); } #ifdef BOOST_UNORDERED_FOA_TESTS @@ -170,6 +188,120 @@ namespace emplace_smf_tests { } UNORDERED_TEST(emplace_smf_value_type_set, EMPLACE_SMF_TESTS_SET_ARGS) + + enum emplace_kind + { + copy, + move + }; + + enum emplace_status + { + fail, + success + }; + + struct counted_key_checker_type + { + using key_type = counted_key; + void operator()(emplace_kind kind, emplace_status status) + { + int copies = (kind == copy && status == success) ? 1 : 0; + int moves = (kind == move && status == success) ? 1 : 0; + BOOST_TEST_EQ(counted_key::count, (smf_count{0, copies, moves, 0, 0, 0})); + } + } counted_key_checker; + + struct converting_key_checker_type + { + using key_type = converting_key; + void operator()(emplace_kind, emplace_status status) + { + int moves = (status == success) ? 1 : 0; + BOOST_TEST_EQ(counted_key::count, (smf_count{1, 0, moves, 0, 0, 1})); + } + } converting_key_checker; + + struct counted_value_checker_type + { + using mapped_type = counted_value; + void operator()(emplace_kind kind, emplace_status status) + { + int copies = (kind == copy && status == success) ? 1 : 0; + int moves = (kind == move && status == success) ? 1 : 0; + BOOST_TEST_EQ( + counted_value::count, (smf_count{0, copies, moves, 0, 0, 0})); + } + } counted_value_checker; + + struct converting_value_checker_type + { + using mapped_type = converting_value; + void operator()(emplace_kind, emplace_status status) + { + int ctors = (status == success) ? 1 : 0; + BOOST_TEST_EQ(counted_value::count, (smf_count{ctors, 0, 0, 0, 0, 0})); + } + } converting_value_checker; + + template + static void emplace_smf_key_value_map( + X*, emplace_kind kind, KC key_checker, VC value_checker) + { + using container = X; + using key_type = typename KC::key_type; + using mapped_type = typename VC::mapped_type; + + container x; + key_type key{}; + key_type key2 = key; + mapped_type value{}; + mapped_type value2 = value; + + { + reset_counts(); + auto ret = (kind == copy) ? x.emplace(key, value) + : x.emplace(std::move(key), std::move(value)); + BOOST_TEST_EQ(ret.second, true); + key_checker(kind, success); + value_checker(kind, success); + BOOST_TEST_EQ(converting_key::count, (smf_count{0, 0, 0, 0, 0, 0})); + BOOST_TEST_EQ(converting_value::count, (smf_count{0, 0, 0, 0, 0, 0})); + } + + { + reset_counts(); + auto ret = x.emplace(key2, value2); + BOOST_TEST_EQ(ret.second, false); + key_checker(kind, fail); + value_checker(kind, fail); + BOOST_TEST_EQ(converting_key::count, (smf_count{0, 0, 0, 0, 0, 0})); + BOOST_TEST_EQ(converting_value::count, (smf_count{0, 0, 0, 0, 0, 0})); + } + + { + reset_counts(); + auto ret = x.emplace(std::move(key2), std::move(value2)); + BOOST_TEST_EQ(ret.second, false); + key_checker(kind, fail); + value_checker(kind, fail); + BOOST_TEST_EQ(converting_key::count, (smf_count{0, 0, 0, 0, 0, 0})); + BOOST_TEST_EQ(converting_value::count, (smf_count{0, 0, 0, 0, 0, 0})); + } + } + + // clang-format off + + UNORDERED_TEST( + emplace_smf_key_value_map, + EMPLACE_SMF_TESTS_MAP_ARGS + ((copy)(move)) + ((counted_key_checker)(converting_key_checker)) + ((counted_value_checker)(converting_value_checker)) + ) + + // clang-format on + } // namespace emplace_smf_tests RUN_TESTS() From f411968ffd32a849332c41ed39f4f2fe6a09928d Mon Sep 17 00:00:00 2001 From: Braden Ganetsky Date: Sat, 10 Feb 2024 17:39:23 -0600 Subject: [PATCH 2/9] Add tests for immovable key_type --- test/helpers/unordered.hpp | 6 ++ test/unordered/emplace_smf_tests.cpp | 70 +++++++++++++++++++++++ test/unordered/init_type_insert_tests.cpp | 6 -- 3 files changed, 76 insertions(+), 6 deletions(-) diff --git a/test/helpers/unordered.hpp b/test/helpers/unordered.hpp index d3cbf17f..fae08422 100644 --- a/test/helpers/unordered.hpp +++ b/test/helpers/unordered.hpp @@ -21,4 +21,10 @@ #include "postfix.hpp" // clang-format on +#if defined(BOOST_LIBSTDCXX_VERSION) +#if BOOST_LIBSTDCXX_VERSION < 60000 +#define BOOST_UNORDERED_NO_INIT_TYPE_TESTS +#endif +#endif + #endif diff --git a/test/unordered/emplace_smf_tests.cpp b/test/unordered/emplace_smf_tests.cpp index b67bfe59..ed37e060 100644 --- a/test/unordered/emplace_smf_tests.cpp +++ b/test/unordered/emplace_smf_tests.cpp @@ -30,12 +30,29 @@ namespace emplace_smf_tests { counted_value(const converting_value& v) : counted_value(v.index_) {} }; + class immovable_key : public smf_counted_object + { + public: + using smf_counted_object::smf_counted_object; + immovable_key(immovable_key&&) = delete; + immovable_key& operator=(immovable_key&&) = delete; + }; + class immovable_value : public smf_counted_object + { + public: + using smf_counted_object::smf_counted_object; + immovable_value(immovable_value&&) = delete; + immovable_value& operator=(immovable_value&&) = delete; + }; + void reset_counts() { counted_key::reset_count(); counted_value::reset_count(); converting_key::reset_count(); converting_value::reset_count(); + immovable_key::reset_count(); + immovable_value::reset_count(); } #ifdef BOOST_UNORDERED_FOA_TESTS @@ -302,6 +319,59 @@ namespace emplace_smf_tests { // clang-format on + template static void emplace_smf_key_value_map_immovable_key(X*) + { +#ifndef BOOST_UNORDERED_NO_INIT_TYPE_TESTS + using container = X; + BOOST_STATIC_ASSERT( + std::is_same::value); + using mapped_type = typename X::mapped_type; + + container x; + + { + reset_counts(); + auto ret = x.emplace(0, 0); + BOOST_TEST_EQ(ret.second, true); + BOOST_TEST_EQ(immovable_key::count, (smf_count{1, 0, 0, 0, 0, 0})); + BOOST_TEST_EQ(mapped_type::count, (smf_count{1, 0, 0, 0, 0, 0})); + } + + { + reset_counts(); + auto ret = x.emplace(0, 1); + BOOST_TEST_EQ(ret.second, false); + BOOST_TEST_EQ(immovable_key::count, (smf_count{1, 0, 0, 0, 0, 1})); + BOOST_TEST_EQ(mapped_type::count, (smf_count{1, 0, 0, 0, 0, 1})); + } +#endif + } + +#ifdef BOOST_UNORDERED_FOA_TESTS + static boost::unordered_node_map* + test_smf_node_map_immovable_key_counted_value; + static boost::unordered_node_map* + test_smf_node_map_immovable_key_immovable_value; +#define EMPLACE_SMF_TESTS_MAP_IMMOVABLE_ARGS \ + ((test_smf_node_map_immovable_key_counted_value)(test_smf_node_map_immovable_key_immovable_value)) +#else + static boost::unordered_map* + test_smf_map_immovable_key_counted_value; + static boost::unordered_map* + test_smf_map_immovable_key_immovable_value; +#define EMPLACE_SMF_TESTS_MAP_IMMOVABLE_ARGS \ + ((test_smf_map_immovable_key_counted_value)(test_smf_map_immovable_key_immovable_value)) +#endif + + // clang-format off + + UNORDERED_TEST( + emplace_smf_key_value_map_immovable_key, + EMPLACE_SMF_TESTS_MAP_IMMOVABLE_ARGS + ) + + // clang-format on + } // namespace emplace_smf_tests RUN_TESTS() diff --git a/test/unordered/init_type_insert_tests.cpp b/test/unordered/init_type_insert_tests.cpp index 3358ddfe..dd72efc6 100644 --- a/test/unordered/init_type_insert_tests.cpp +++ b/test/unordered/init_type_insert_tests.cpp @@ -12,12 +12,6 @@ #include -#if defined(BOOST_LIBSTDCXX_VERSION) -#if BOOST_LIBSTDCXX_VERSION < 60000 -#define BOOST_UNORDERED_NO_INIT_TYPE_TESTS -#endif -#endif - struct move_only { int x_ = -1; From bd5c9fc740cf3af0c1efbd61aaa594ea58ac3b37 Mon Sep 17 00:00:00 2001 From: Braden Ganetsky Date: Tue, 6 Feb 2024 23:36:29 -0600 Subject: [PATCH 3/9] Add cfoa tests for emplace(k,v) --- test/cfoa/emplace_tests.cpp | 171 ++++++++++++++++++++++++++++++++++++ test/cfoa/helpers.hpp | 68 +++++++++----- 2 files changed, 216 insertions(+), 23 deletions(-) diff --git a/test/cfoa/emplace_tests.cpp b/test/cfoa/emplace_tests.cpp index f7105e6c..23105cb7 100644 --- a/test/cfoa/emplace_tests.cpp +++ b/test/cfoa/emplace_tests.cpp @@ -4,6 +4,7 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) #include "helpers.hpp" +#include "../helpers/count.hpp" #include #include @@ -293,4 +294,174 @@ UNORDERED_TEST( // clang-format on +namespace { + using converting_key_type = basic_raii; + using converting_value_type = basic_raii; + + class counted_key_type : public basic_raii + { + public: + using basic_raii::basic_raii; + counted_key_type() = default; + counted_key_type(const converting_key_type& k) : counted_key_type(k.x_) {} + }; + class counted_value_type : public basic_raii + { + public: + using basic_raii::basic_raii; + counted_value_type() = default; + counted_value_type(const converting_value_type& v) + : counted_value_type(v.x_) + { + } + }; + + void reset_counts() + { + counted_key_type::reset_counts(); + counted_value_type::reset_counts(); + converting_key_type::reset_counts(); + converting_value_type::reset_counts(); + } + + using test::smf_count; + + template smf_count count_for() + { + return test::smf_count{ + (int)T::default_constructor.load(std::memory_order_relaxed), + (int)T::copy_constructor.load(std::memory_order_relaxed), + (int)T::move_constructor.load(std::memory_order_relaxed), + (int)T::copy_assignment.load(std::memory_order_relaxed), + (int)T::move_assignment.load(std::memory_order_relaxed), + (int)T::destructor.load(std::memory_order_relaxed)}; + } + + enum emplace_kind + { + copy, + move + }; + + enum emplace_status + { + fail, + success + }; + + struct counted_key_checker_type + { + using key_type = counted_key_type; + void operator()(emplace_kind kind, emplace_status status) + { + int copies = (kind == copy && status == success) ? 1 : 0; + int moves = (kind == move && status == success) ? 1 : 0; + BOOST_TEST_EQ( + count_for(), (smf_count{0, copies, moves, 0, 0, 0})); + } + } counted_key_checker; + + struct converting_key_checker_type + { + using key_type = converting_key_type; + void operator()(emplace_kind, emplace_status status) + { + int moves = (status == success) ? 1 : 0; + BOOST_TEST_EQ( + count_for(), (smf_count{1, 0, moves, 0, 0, 1})); + } + } converting_key_checker; + + struct counted_value_checker_type + { + using mapped_type = counted_value_type; + void operator()(emplace_kind kind, emplace_status status) + { + int copies = (kind == copy && status == success) ? 1 : 0; + int moves = (kind == move && status == success) ? 1 : 0; + BOOST_TEST_EQ(count_for(), + (smf_count{0, copies, moves, 0, 0, 0})); + } + } counted_value_checker; + + struct converting_value_checker_type + { + using mapped_type = converting_value_type; + void operator()(emplace_kind, emplace_status status) + { + int ctors = (status == success) ? 1 : 0; + BOOST_TEST_EQ( + count_for(), (smf_count{ctors, 0, 0, 0, 0, 0})); + } + } converting_value_checker; + + template + void emplace_map_key_value( + X*, emplace_kind kind, KC key_checker, VC value_checker) + { + using container = X; + using key_type = typename KC::key_type; + using mapped_type = typename VC::mapped_type; + + container x; + key_type key{}; + key_type key2 = key; + mapped_type value{}; + mapped_type value2 = value; + + { + reset_counts(); + auto ret = (kind == copy) ? x.emplace(key, value) + : x.emplace(std::move(key), std::move(value)); + BOOST_TEST_EQ(ret, true); + key_checker(kind, success); + value_checker(kind, success); + BOOST_TEST_EQ( + count_for(), (smf_count{0, 0, 0, 0, 0, 0})); + BOOST_TEST_EQ( + count_for(), (smf_count{0, 0, 0, 0, 0, 0})); + } + + { + reset_counts(); + bool ret = x.emplace(key2, value2); + BOOST_TEST_EQ(ret, false); + key_checker(kind, fail); + value_checker(kind, fail); + BOOST_TEST_EQ( + count_for(), (smf_count{0, 0, 0, 0, 0, 0})); + BOOST_TEST_EQ( + count_for(), (smf_count{0, 0, 0, 0, 0, 0})); + } + + { + reset_counts(); + bool ret = x.emplace(std::move(key2), std::move(value2)); + BOOST_TEST_EQ(ret, false); + key_checker(kind, fail); + value_checker(kind, fail); + BOOST_TEST_EQ( + count_for(), (smf_count{0, 0, 0, 0, 0, 0})); + BOOST_TEST_EQ( + count_for(), (smf_count{0, 0, 0, 0, 0, 0})); + } + } + + boost::unordered::concurrent_flat_map* + test_counted_flat_map = {}; + +} // namespace + +// clang-format off + +UNORDERED_TEST( + emplace_map_key_value, + ((test_counted_flat_map)) + ((copy)(move)) + ((counted_key_checker)(converting_key_checker)) + ((counted_value_checker)(converting_value_checker)) +) + +// clang-format on + RUN_TESTS() diff --git a/test/cfoa/helpers.hpp b/test/cfoa/helpers.hpp index fcdc64ce..c983c916 100644 --- a/test/cfoa/helpers.hpp +++ b/test/cfoa/helpers.hpp @@ -207,7 +207,8 @@ template struct stateful_allocator2 bool operator!=(stateful_allocator2 const& rhs) const { return x_ != rhs.x_; } }; -struct raii +template +struct basic_raii { static std::atomic default_constructor; static std::atomic copy_constructor; @@ -219,17 +220,17 @@ struct raii int x_ = -1; - raii() { ++default_constructor; } - raii(int const x) : x_{x} { ++default_constructor; } - raii(raii const& rhs) : x_{rhs.x_} { ++copy_constructor; } - raii(raii&& rhs) noexcept : x_{rhs.x_} + basic_raii() { ++default_constructor; } + basic_raii(int const x) : x_{x} { ++default_constructor; } + basic_raii(basic_raii const& rhs) : x_{rhs.x_} { ++copy_constructor; } + basic_raii(basic_raii&& rhs) noexcept : x_{rhs.x_} { rhs.x_ = -1; ++move_constructor; } - ~raii() { ++destructor; } + ~basic_raii() { ++destructor; } - raii& operator=(raii const& rhs) + basic_raii& operator=(basic_raii const& rhs) { ++copy_assignment; if (this != &rhs) { @@ -238,7 +239,7 @@ struct raii return *this; } - raii& operator=(raii&& rhs) noexcept + basic_raii& operator=(basic_raii&& rhs) noexcept { ++move_assignment; if (this != &rhs) { @@ -248,37 +249,37 @@ struct raii return *this; } - friend bool operator==(raii const& lhs, raii const& rhs) + friend bool operator==(basic_raii const& lhs, basic_raii const& rhs) { return lhs.x_ == rhs.x_; } - friend bool operator!=(raii const& lhs, raii const& rhs) + friend bool operator!=(basic_raii const& lhs, basic_raii const& rhs) { return !(lhs == rhs); } - friend bool operator==(raii const& lhs, int const x) { return lhs.x_ == x; } - friend bool operator!=(raii const& lhs, int const x) + friend bool operator==(basic_raii const& lhs, int const x) { return lhs.x_ == x; } + friend bool operator!=(basic_raii const& lhs, int const x) { return !(lhs.x_ == x); } - friend bool operator==(int const x, raii const& rhs) { return rhs.x_ == x; } + friend bool operator==(int const x, basic_raii const& rhs) { return rhs.x_ == x; } - friend bool operator!=(int const x, raii const& rhs) + friend bool operator!=(int const x, basic_raii const& rhs) { return !(rhs.x_ == x); } - friend std::ostream& operator<<(std::ostream& os, raii const& rhs) + friend std::ostream& operator<<(std::ostream& os, basic_raii const& rhs) { os << "{ x_: " << rhs.x_ << " }"; return os; } friend std::ostream& operator<<( - std::ostream& os, std::pair const& rhs) + std::ostream& os, std::pair const& rhs) { os << "pair<" << rhs.first << ", " << rhs.second << ">"; return os; @@ -294,16 +295,30 @@ struct raii move_assignment = 0; } - friend void swap(raii& lhs, raii& rhs) { std::swap(lhs.x_, rhs.x_); } + friend void swap(basic_raii& lhs, basic_raii& rhs) { std::swap(lhs.x_, rhs.x_); } }; -std::atomic raii::default_constructor{0}; -std::atomic raii::copy_constructor{0}; -std::atomic raii::move_constructor{0}; -std::atomic raii::destructor{0}; -std::atomic raii::copy_assignment{0}; -std::atomic raii::move_assignment{0}; +template std::atomic basic_raii::default_constructor(0); +template std::atomic basic_raii::copy_constructor(0); +template std::atomic basic_raii::move_constructor(0); +template std::atomic basic_raii::destructor(0); +template std::atomic basic_raii::copy_assignment(0); +template std::atomic basic_raii::move_assignment(0); +struct raii_tag_ +{ +}; +class raii : public basic_raii +{ + using basic_raii::basic_raii; +}; + +template +std::size_t hash_value(basic_raii const& r) noexcept +{ + boost::hash hasher; + return hasher(r.x_); +} std::size_t hash_value(raii const& r) noexcept { boost::hash hasher; @@ -311,6 +326,13 @@ std::size_t hash_value(raii const& r) noexcept } namespace std { + template struct hash> + { + std::size_t operator()(basic_raii const& r) const noexcept + { + return hash_value(r); + } + }; template <> struct hash { std::size_t operator()(raii const& r) const noexcept From 4fbe450a653065677f03a6d65dd9929d9b031931 Mon Sep 17 00:00:00 2001 From: Braden Ganetsky Date: Sat, 10 Feb 2024 11:46:06 -0600 Subject: [PATCH 4/9] Generalize alloc_cted_insert_type into allocator_constructed and remove the factory function alloc_make_insert_type() --- .../detail/allocator_constructed.hpp | 59 +++++++++++++++++++ .../unordered/detail/foa/concurrent_table.hpp | 2 +- include/boost/unordered/detail/foa/core.hpp | 23 ++------ include/boost/unordered/detail/foa/table.hpp | 2 +- 4 files changed, 66 insertions(+), 20 deletions(-) create mode 100644 include/boost/unordered/detail/allocator_constructed.hpp diff --git a/include/boost/unordered/detail/allocator_constructed.hpp b/include/boost/unordered/detail/allocator_constructed.hpp new file mode 100644 index 00000000..29fb1566 --- /dev/null +++ b/include/boost/unordered/detail/allocator_constructed.hpp @@ -0,0 +1,59 @@ +/* Copyright 2024 Braden Ganetsky. + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * See https://www.boost.org/libs/unordered for library home page. + */ + +#ifndef BOOST_UNORDERED_DETAIL_ALLOCATOR_CONSTRUCTED_HPP +#define BOOST_UNORDERED_DETAIL_ALLOCATOR_CONSTRUCTED_HPP + +#include +#include + +namespace boost { + namespace unordered { + namespace detail { + + struct allocator_policy + { + template + static void construct(Allocator& a, T* p, Args&&... args) + { + boost::allocator_construct(a, p, std::forward(args)...); + } + + template + static void destroy(Allocator& a, T* p) + { + boost::allocator_destroy(a, p); + } + }; + + /* constructs a stack-based object with the given policy and allocator */ + template + class allocator_constructed + { + opt_storage storage; + Allocator alloc; + + public: + template + allocator_constructed(Allocator const& alloc_, Args&&... args) + : alloc(alloc_) + { + Policy::construct( + alloc, storage.address(), std::forward(args)...); + } + + ~allocator_constructed() { Policy::destroy(alloc, storage.address()); } + + T& value() { return *storage.address(); } + }; + + } /* namespace detail */ + } /* namespace unordered */ +} /* namespace boost */ + +#endif diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 5c6de985..9dbef35c 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -1320,7 +1320,7 @@ private: { auto lck=shared_access(); - auto x=alloc_make_insert_type( + alloc_cted_insert_type x( this->al(),std::forward(args)...); int res=unprotected_norehash_emplace_or_visit( access_mode,std::forward(f),type_policy::move(x.value())); diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 0ffbfc03..64894a04 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -1220,31 +1221,17 @@ class alloc_cted_insert_type emplace_type,typename TypePolicy::element_type >::type; - alignas(insert_type) unsigned char storage[sizeof(insert_type)]; - Allocator al; + using alloc_cted = allocator_constructed; + alloc_cted val; public: - alloc_cted_insert_type(const Allocator& al_,Args&&... args):al{al_} + alloc_cted_insert_type(const Allocator& al_,Args&&... args):val{al_,std::forward(args)...} { - TypePolicy::construct(al,data(),std::forward(args)...); } - ~alloc_cted_insert_type() - { - TypePolicy::destroy(al,data()); - } - - insert_type* data(){return reinterpret_cast(&storage);} - insert_type& value(){return *data();} + insert_type& value(){return val.value();} }; -template -alloc_cted_insert_type -alloc_make_insert_type(const Allocator& al,Args&&... args) -{ - return {al,std::forward(args)...}; -} - /* table_core. The TypePolicy template parameter is used to generate * instantiations suitable for either maps or sets, and introduces non-standard * init_type and element_type: diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index 4d248c4a..0cd56de2 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -401,7 +401,7 @@ public: template BOOST_FORCEINLINE std::pair emplace(Args&&... args) { - auto x=alloc_make_insert_type( + alloc_cted_insert_type x( this->al(),std::forward(args)...); return emplace_impl(type_policy::move(x.value())); } From 4e6d4ade6c74e875178620d05c578c19d45b92dc Mon Sep 17 00:00:00 2001 From: Braden Ganetsky Date: Sat, 10 Feb 2024 11:27:43 -0600 Subject: [PATCH 5/9] Optimize emplace(k,v) for foa and cfoa containers --- .../unordered/detail/foa/concurrent_table.hpp | 12 +++++ include/boost/unordered/detail/foa/core.hpp | 54 ++++++++++++++++++- .../unordered/detail/foa/flat_map_types.hpp | 11 ++++ .../unordered/detail/foa/node_map_types.hpp | 11 ++++ include/boost/unordered/detail/foa/table.hpp | 13 +++++ .../boost/unordered/detail/type_traits.hpp | 14 +++++ 6 files changed, 114 insertions(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 9dbef35c..07771ca5 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -691,6 +691,18 @@ public: return emplace_impl(std::forward(x)); } + /* Optimizations for maps for (k,v) to avoid eagerly constructing value */ + template + BOOST_FORCEINLINE auto emplace(K&& k, V&& v) -> + typename std::enable_if::value, + bool>::type + { + alloc_cted_or_fwded_key_type x( + this->al(), std::forward(k)); + return emplace_impl( + try_emplace_args_t{}, x.move_or_fwd(), std::forward(v)); + } + BOOST_FORCEINLINE bool insert(const init_type& x){return emplace_impl(x);} diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 64894a04..5da7acb9 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1232,6 +1232,58 @@ public: insert_type& value(){return val.value();} }; +template +alloc_cted_insert_type +alloc_make_insert_type(const Allocator& al,Args&&... args) +{ + return {al,std::forward(args)...}; +} + +template +class alloc_cted_or_fwded_key_type +{ + using key_type = typename TypePolicy::key_type; + allocator_constructed val; + +public: + alloc_cted_or_fwded_key_type(const Allocator& al_, KFwdRef k) + : val(al_, std::forward(k)) + { + } + + key_type&& move_or_fwd() { return std::move(val.value()); } +}; + +template +class alloc_cted_or_fwded_key_type::value>::type> +{ + // This specialization acts as a forwarding-reference wrapper + BOOST_UNORDERED_STATIC_ASSERT(std::is_reference::value); + KFwdRef ref; + +public: + alloc_cted_or_fwded_key_type(const Allocator&, KFwdRef k) + : ref(std::forward(k)) + { + } + + KFwdRef move_or_fwd() { return std::forward(ref); } +}; + +template +using is_map = + std::integral_constant::value>; + +template +using is_emplace_kv_able = std::integral_constant::value && + (is_similar::value || + is_complete_and_move_constructible::value)>; + /* table_core. The TypePolicy template parameter is used to generate * instantiations suitable for either maps or sets, and introduces non-standard * init_type and element_type: @@ -1249,7 +1301,7 @@ public: * * - TypePolicy::construct and TypePolicy::destroy are used for the * construction and destruction of the internal types: value_type, - * init_type and element_type. + * init_type, element_type, and key_type. * * - TypePolicy::move is used to provide move semantics for the internal * types used by the container during rehashing and emplace. These types diff --git a/include/boost/unordered/detail/foa/flat_map_types.hpp b/include/boost/unordered/detail/foa/flat_map_types.hpp index 712df5fb..a97daba1 100644 --- a/include/boost/unordered/detail/foa/flat_map_types.hpp +++ b/include/boost/unordered/detail/foa/flat_map_types.hpp @@ -56,6 +56,12 @@ namespace boost { boost::allocator_construct(al, p, std::forward(args)...); } + template + static void construct(A& al, key_type* p, Args&&... args) + { + boost::allocator_construct(al, p, std::forward(args)...); + } + template static void destroy(A& al, init_type* p) noexcept { boost::allocator_destroy(al, p); @@ -65,6 +71,11 @@ namespace boost { { boost::allocator_destroy(al, p); } + + template static void destroy(A& al, key_type* p) noexcept + { + boost::allocator_destroy(al, p); + } }; } // namespace foa } // namespace detail diff --git a/include/boost/unordered/detail/foa/node_map_types.hpp b/include/boost/unordered/detail/foa/node_map_types.hpp index ccb378ef..e4308a11 100644 --- a/include/boost/unordered/detail/foa/node_map_types.hpp +++ b/include/boost/unordered/detail/foa/node_map_types.hpp @@ -82,6 +82,12 @@ namespace boost { boost::allocator_construct(al, p, std::forward(args)...); } + template + static void construct(A& al, key_type* p, Args&&... args) + { + boost::allocator_construct(al, p, std::forward(args)...); + } + template static void construct(A& al, element_type* p, Args&&... args) { @@ -109,6 +115,11 @@ namespace boost { boost::allocator_destroy(al, p); } + template static void destroy(A& al, key_type* p) noexcept + { + boost::allocator_destroy(al, p); + } + template static void destroy(A& al, element_type* p) noexcept { diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index 0cd56de2..2ec49595 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -416,6 +416,19 @@ public: return emplace_impl(std::forward(x)); } + /* Optimizations for maps for (k,v) to avoid eagerly constructing value */ + template + BOOST_FORCEINLINE + typename std::enable_if::value, + std::pair >::type + emplace(K&& k, V&& v) + { + alloc_cted_or_fwded_key_type x( + this->al(), std::forward(k)); + return emplace_impl( + try_emplace_args_t{}, x.move_or_fwd(), std::forward(v)); + } + template BOOST_FORCEINLINE std::pair try_emplace( Key&& x,Args&&... args) diff --git a/include/boost/unordered/detail/type_traits.hpp b/include/boost/unordered/detail/type_traits.hpp index 0ae5656f..18f90a3c 100644 --- a/include/boost/unordered/detail/type_traits.hpp +++ b/include/boost/unordered/detail/type_traits.hpp @@ -48,6 +48,20 @@ namespace boost { template using void_t = typename make_void::type; + template struct is_complete : std::false_type + { + }; + + template + struct is_complete > : std::true_type + { + }; + + template + using is_complete_and_move_constructible = + typename std::conditional::value, + std::is_move_constructible, std::false_type>::type; + #if BOOST_WORKAROUND(BOOST_LIBSTDCXX_VERSION, < 50000) /* std::is_trivially_default_constructible not provided */ template From aff242dbaf477c15d2def004b60f9e1a46791334 Mon Sep 17 00:00:00 2001 From: Braden Ganetsky Date: Sat, 10 Feb 2024 11:29:45 -0600 Subject: [PATCH 6/9] Optimize emplace(k,v) for fca containers --- .../boost/unordered/detail/implementation.hpp | 23 +++++++++++++++++-- include/boost/unordered/detail/map.hpp | 1 + 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/include/boost/unordered/detail/implementation.hpp b/include/boost/unordered/detail/implementation.hpp index 97796474..5e0d8001 100644 --- a/include/boost/unordered/detail/implementation.hpp +++ b/include/boost/unordered/detail/implementation.hpp @@ -14,6 +14,7 @@ #pragma once #endif +#include #include #include #include @@ -104,6 +105,10 @@ namespace boost { template no_key(T const&) {} }; + struct converting_key + { + }; + namespace func { template inline void ignore_unused_variable_warning(T const&) { @@ -1913,6 +1918,16 @@ namespace boost { } } + template + emplace_return emplace_unique(converting_key, K&& k, V&& v) + { + using alloc_cted = allocator_constructed; + alloc_cted key(this->node_alloc(), std::forward(k)); + return emplace_unique( + key.value(), std::move(key.value()), std::forward(v)); + } + template emplace_return try_emplace_unique(Key&& k) { std::size_t key_hash = this->hash(k); @@ -2835,9 +2850,13 @@ namespace boost { } template - static no_key extract(Arg1 const&, Arg2 const&) + static typename std::conditional< + (is_similar::value || + is_complete_and_move_constructible::value), + converting_key, no_key>::type + extract(Arg1 const&, Arg2 const&) { - return no_key(); + return {}; } template diff --git a/include/boost/unordered/detail/map.hpp b/include/boost/unordered/detail/map.hpp index ccd2508f..bba19ed1 100644 --- a/include/boost/unordered/detail/map.hpp +++ b/include/boost/unordered/detail/map.hpp @@ -18,6 +18,7 @@ namespace boost { typedef std::pair value_type; typedef H hasher; typedef P key_equal; + typedef K key_type; typedef K const const_key_type; typedef From 260418e4eeaf901b5741207f947422de032604b4 Mon Sep 17 00:00:00 2001 From: Braden Ganetsky Date: Mon, 15 Jan 2024 22:00:52 -0600 Subject: [PATCH 7/9] Fix cfoa tests --- test/cfoa/emplace_tests.cpp | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/test/cfoa/emplace_tests.cpp b/test/cfoa/emplace_tests.cpp index 23105cb7..cdb60103 100644 --- a/test/cfoa/emplace_tests.cpp +++ b/test/cfoa/emplace_tests.cpp @@ -52,7 +52,7 @@ namespace { struct lvalue_emplacer_type { - template void operator()(std::vector& values, X& x) + template void call_impl(std::vector& values, X& x) { static constexpr auto value_type_cardinality = value_cardinality::value; @@ -67,28 +67,33 @@ namespace { } }); BOOST_TEST_EQ(num_inserts, x.size()); - BOOST_TEST_EQ( - raii::default_constructor, value_type_cardinality * values.size()); - BOOST_TEST_EQ(raii::copy_constructor, 0u); - BOOST_TEST_GE(raii::move_constructor, value_type_cardinality * x.size()); + std::uint64_t const default_constructors = value_type_cardinality == 2 + ? values.size() + num_inserts + : values.size(); + BOOST_TEST_EQ(raii::default_constructor, default_constructors); BOOST_TEST_EQ(raii::copy_constructor, 0u); BOOST_TEST_EQ(raii::copy_assignment, 0u); BOOST_TEST_EQ(raii::move_assignment, 0u); } + template void operator()(std::vector& values, X& x) + { + static constexpr auto value_type_cardinality = + value_cardinality::value; + + call_impl(values, x); + BOOST_TEST_GE(raii::move_constructor, value_type_cardinality * x.size()); + } } lvalue_emplacer; struct norehash_lvalue_emplacer_type : public lvalue_emplacer_type { template void operator()(std::vector& values, X& x) { - static constexpr auto value_type_cardinality = - value_cardinality::value; - x.reserve(values.size()); - lvalue_emplacer_type::operator()(values, x); - BOOST_TEST_EQ(raii::move_constructor, value_type_cardinality * x.size()); + lvalue_emplacer_type::call_impl(values, x); + BOOST_TEST_EQ(raii::move_constructor, x.size()); } } norehash_lvalue_emplacer; From 1d25c1c053a9161fa5158da17b6802f50f8c139f Mon Sep 17 00:00:00 2001 From: Braden Ganetsky Date: Mon, 15 Jan 2024 12:26:47 -0600 Subject: [PATCH 8/9] Update copyright notices --- include/boost/unordered/detail/foa/concurrent_table.hpp | 1 + include/boost/unordered/detail/foa/core.hpp | 1 + include/boost/unordered/detail/foa/flat_map_types.hpp | 1 + include/boost/unordered/detail/foa/node_map_types.hpp | 1 + include/boost/unordered/detail/foa/table.hpp | 1 + include/boost/unordered/detail/implementation.hpp | 1 + include/boost/unordered/detail/map.hpp | 1 + include/boost/unordered/detail/type_traits.hpp | 1 + test/cfoa/emplace_tests.cpp | 1 + test/cfoa/helpers.hpp | 1 + test/helpers/count.hpp | 1 + test/unordered/emplace_smf_tests.cpp | 2 +- 12 files changed, 12 insertions(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 07771ca5..305ada28 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -1,6 +1,7 @@ /* Fast open-addressing concurrent hash table. * * Copyright 2023 Joaquin M Lopez Munoz. + * Copyright 2024 Braden Ganetsky. * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at * http://www.boost.org/LICENSE_1_0.txt) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 5da7acb9..28766e2d 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -2,6 +2,7 @@ * * Copyright 2022-2023 Joaquin M Lopez Munoz. * Copyright 2023 Christian Mazakas. + * Copyright 2024 Braden Ganetsky. * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at * http://www.boost.org/LICENSE_1_0.txt) diff --git a/include/boost/unordered/detail/foa/flat_map_types.hpp b/include/boost/unordered/detail/foa/flat_map_types.hpp index a97daba1..05610e46 100644 --- a/include/boost/unordered/detail/foa/flat_map_types.hpp +++ b/include/boost/unordered/detail/foa/flat_map_types.hpp @@ -1,4 +1,5 @@ // Copyright (C) 2023 Christian Mazakas +// Copyright (C) 2024 Braden Ganetsky // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) diff --git a/include/boost/unordered/detail/foa/node_map_types.hpp b/include/boost/unordered/detail/foa/node_map_types.hpp index e4308a11..4de679a9 100644 --- a/include/boost/unordered/detail/foa/node_map_types.hpp +++ b/include/boost/unordered/detail/foa/node_map_types.hpp @@ -1,4 +1,5 @@ // Copyright (C) 2023 Christian Mazakas +// Copyright (C) 2024 Braden Ganetsky // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index 2ec49595..87aa0495 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -2,6 +2,7 @@ * * Copyright 2022-2023 Joaquin M Lopez Munoz. * Copyright 2023 Christian Mazakas. + * Copyright 2024 Braden Ganetsky. * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at * http://www.boost.org/LICENSE_1_0.txt) diff --git a/include/boost/unordered/detail/implementation.hpp b/include/boost/unordered/detail/implementation.hpp index 5e0d8001..a239d448 100644 --- a/include/boost/unordered/detail/implementation.hpp +++ b/include/boost/unordered/detail/implementation.hpp @@ -2,6 +2,7 @@ // Copyright (C) 2005-2016 Daniel James // Copyright (C) 2022-2023 Joaquin M Lopez Munoz. // Copyright (C) 2022-2023 Christian Mazakas +// Copyright (C) 2024 Braden Ganetsky // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) diff --git a/include/boost/unordered/detail/map.hpp b/include/boost/unordered/detail/map.hpp index bba19ed1..a11d35ef 100644 --- a/include/boost/unordered/detail/map.hpp +++ b/include/boost/unordered/detail/map.hpp @@ -1,6 +1,7 @@ // Copyright (C) 2005-2016 Daniel James // Copyright (C) 2022 Christian Mazakas +// Copyright (C) 2024 Braden Ganetsky // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) diff --git a/include/boost/unordered/detail/type_traits.hpp b/include/boost/unordered/detail/type_traits.hpp index 18f90a3c..4369df2e 100644 --- a/include/boost/unordered/detail/type_traits.hpp +++ b/include/boost/unordered/detail/type_traits.hpp @@ -1,4 +1,5 @@ // Copyright (C) 2022-2023 Christian Mazakas +// Copyright (C) 2024 Braden Ganetsky // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) diff --git a/test/cfoa/emplace_tests.cpp b/test/cfoa/emplace_tests.cpp index cdb60103..f3a2b926 100644 --- a/test/cfoa/emplace_tests.cpp +++ b/test/cfoa/emplace_tests.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2023 Christian Mazakas // Copyright (C) 2023 Joaquin M Lopez Munoz +// Copyright (C) 2024 Braden Ganetsky // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) diff --git a/test/cfoa/helpers.hpp b/test/cfoa/helpers.hpp index c983c916..4514b0a7 100644 --- a/test/cfoa/helpers.hpp +++ b/test/cfoa/helpers.hpp @@ -1,5 +1,6 @@ // Copyright (C) 2023 Christian Mazakas // Copyright (C) 2023 Joaquin M Lopez Munoz +// Copyright (C) 2024 Braden Ganetsky // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) diff --git a/test/helpers/count.hpp b/test/helpers/count.hpp index b53c0f33..5455ca45 100644 --- a/test/helpers/count.hpp +++ b/test/helpers/count.hpp @@ -1,5 +1,6 @@ // Copyright 2008-2009 Daniel James. +// Copyright 2024 Braden Ganetsky. // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or move at http://www.boost.org/LICENSE_1_0.txt) diff --git a/test/unordered/emplace_smf_tests.cpp b/test/unordered/emplace_smf_tests.cpp index ed37e060..3f12df8f 100644 --- a/test/unordered/emplace_smf_tests.cpp +++ b/test/unordered/emplace_smf_tests.cpp @@ -1,5 +1,5 @@ // -// Copyright 2023 Braden Ganetsky. +// Copyright 2023-2024 Braden Ganetsky. // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) From bbad4735aa639e73c5df0a257611fc0c6abaa6fd Mon Sep 17 00:00:00 2001 From: Braden Ganetsky Date: Fri, 19 Jan 2024 13:45:57 -0600 Subject: [PATCH 9/9] Update reference --- doc/unordered/changes.adoc | 5 +++++ doc/unordered/concurrent_flat_map.adoc | 4 +++- doc/unordered/copyright.adoc | 2 ++ doc/unordered/unordered_flat_map.adoc | 4 ++++ doc/unordered/unordered_map.adoc | 8 ++++++-- doc/unordered/unordered_node_map.adoc | 4 ++++ 6 files changed, 24 insertions(+), 3 deletions(-) diff --git a/doc/unordered/changes.adoc b/doc/unordered/changes.adoc index 54f2d7a8..9a7d49e8 100644 --- a/doc/unordered/changes.adoc +++ b/doc/unordered/changes.adoc @@ -6,6 +6,11 @@ :github-pr-url: https://github.com/boostorg/unordered/pull :cpp: C++ +== Release 1.85.0 + +* Optimized `emplace()` for a `value_type` or `init_type` (if applicable) argument to bypass creating an intermediate object. The argument is already the same type as the would-be intermediate object. +* Optimized `emplace()` for `k,v` arguments on map containers to delay constructing the object until it is certain that an element should be inserted. This optimization happens when the map's `key_type` is move constructible or when the `k` argument is a `key_type`. + == Release 1.84.0 - Major update * Added `boost::concurrent_flat_set`. diff --git a/doc/unordered/concurrent_flat_map.adoc b/doc/unordered/concurrent_flat_map.adoc index c58adf2a..b921d8df 100644 --- a/doc/unordered/concurrent_flat_map.adoc +++ b/doc/unordered/concurrent_flat_map.adoc @@ -908,7 +908,9 @@ Inserts an object, constructed with the arguments `args`, in the table if and on Requires:;; `value_type` is constructible from `args`. Returns:;; `true` if an insert took place. Concurrency:;; Blocking on rehashing of `*this`. -Notes:;; Invalidates pointers and references to elements if a rehashing is issued. +Notes:;; Invalidates pointers and references to elements if a rehashing is issued. + ++ +If `args...` is of the form `k,v`, it delays constructing the whole object until it is certain that an element should be inserted, using only the `k` argument to check. --- diff --git a/doc/unordered/copyright.adoc b/doc/unordered/copyright.adoc index 180bb2fa..3a14ba72 100644 --- a/doc/unordered/copyright.adoc +++ b/doc/unordered/copyright.adoc @@ -15,4 +15,6 @@ Copyright (C) 2022-2023 Joaquín M López Muñoz Copyright (C) 2022-2023 Peter Dimov +Copyright (C) 2024 Braden Ganetsky + Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) diff --git a/doc/unordered/unordered_flat_map.adoc b/doc/unordered/unordered_flat_map.adoc index f637279d..fd9e3888 100644 --- a/doc/unordered/unordered_flat_map.adoc +++ b/doc/unordered/unordered_flat_map.adoc @@ -750,6 +750,8 @@ Returns:;; The `bool` component of the return type is `true` if an insert took p If an insert took place, then the iterator points to the newly inserted element. Otherwise, it points to the element with equivalent key. Throws:;; If an exception is thrown by an operation other than a call to `hasher` the function has no effect. Notes:;; Can invalidate iterators, pointers and references, but only if the insert causes the load to be greater than the maximum load. + ++ +If `args...` is of the form `k,v`, it delays constructing the whole object until it is certain that an element should be inserted, using only the `k` argument to check. --- @@ -769,6 +771,8 @@ Returns:;; The `bool` component of the return type is `true` if an insert took p If an insert took place, then the iterator points to the newly inserted element. Otherwise, it points to the element with equivalent key. Throws:;; If an exception is thrown by an operation other than a call to `hasher` the function has no effect. Notes:;; Can invalidate iterators, pointers and references, but only if the insert causes the load to be greater than the maximum load. + ++ +If `args...` is of the form `k,v`, it delays constructing the whole object until it is certain that an element should be inserted, using only the `k` argument to check. --- diff --git a/doc/unordered/unordered_map.adoc b/doc/unordered/unordered_map.adoc index 5b70df80..015cb90b 100644 --- a/doc/unordered/unordered_map.adoc +++ b/doc/unordered/unordered_map.adoc @@ -797,7 +797,9 @@ If an insert took place, then the iterator points to the newly inserted element. Throws:;; If an exception is thrown by an operation other than a call to `hasher` the function has no effect. Notes:;; Can invalidate iterators, but only if the insert causes the load factor to be greater to or equal to the maximum load factor. + + -Pointers and references to elements are never invalidated. +Pointers and references to elements are never invalidated. + ++ +If `args...` is of the form `k,v`, it delays constructing the whole object until it is certain that an element should be inserted, using only the `k` argument to check. This optimization happens when the map's `key_type` is move constructible or when the `k` argument is a `key_type`. --- @@ -818,7 +820,9 @@ Notes:;; The standard is fairly vague on the meaning of the hint. But the only p + Can invalidate iterators, but only if the insert causes the load factor to be greater to or equal to the maximum load factor. + + -Pointers and references to elements are never invalidated. +Pointers and references to elements are never invalidated. + ++ +If `args...` is of the form `k,v`, it delays constructing the whole object until it is certain that an element should be inserted, using only the `k` argument to check. This optimization happens when the map's `key_type` is move constructible or when the `k` argument is a `key_type`. --- diff --git a/doc/unordered/unordered_node_map.adoc b/doc/unordered/unordered_node_map.adoc index 14164705..4179d17a 100644 --- a/doc/unordered/unordered_node_map.adoc +++ b/doc/unordered/unordered_node_map.adoc @@ -767,6 +767,8 @@ Returns:;; The `bool` component of the return type is `true` if an insert took p If an insert took place, then the iterator points to the newly inserted element. Otherwise, it points to the element with equivalent key. Throws:;; If an exception is thrown by an operation other than a call to `hasher` the function has no effect. Notes:;; Can invalidate iterators, but only if the insert causes the load to be greater than the maximum load. + ++ +If `args...` is of the form `k,v`, it delays constructing the whole object until it is certain that an element should be inserted, using only the `k` argument to check. This optimization happens when `key_type` is move constructible or when the `k` argument is a `key_type`. --- @@ -786,6 +788,8 @@ Returns:;; The `bool` component of the return type is `true` if an insert took p If an insert took place, then the iterator points to the newly inserted element. Otherwise, it points to the element with equivalent key. Throws:;; If an exception is thrown by an operation other than a call to `hasher` the function has no effect. Notes:;; Can invalidate iterators, but only if the insert causes the load to be greater than the maximum load. + ++ +If `args...` is of the form `k,v`, it delays constructing the whole object until it is certain that an element should be inserted, using only the `k` argument to check. This optimization happens when `key_type` is move constructible or when the `k` argument is a `key_type`. ---