diff --git a/.drone.jsonnet b/.drone.jsonnet index ec0258b7..51340c4c 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -6,7 +6,7 @@ local library = "unordered"; local triggers = { - branch: [ "master", "develop", "feature/*", "bugfix/*" ] + branch: [ "master", "develop", "feature/*", "bugfix/*", "fix/*", "pr/*" ] }; local ubsan = { UBSAN: '1', UBSAN_OPTIONS: 'print_stacktrace=1' }; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 03a8f51c..da3f5b2c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,7 +75,7 @@ jobs: - { compiler: clang, cxxstd: '03,11,14,17,2a', os: macos-11, } - { compiler: clang, cxxstd: '03,11,14,17,2a', os: macos-12, sanitize: yes } - timeout-minutes: 120 + timeout-minutes: 180 runs-on: ${{matrix.os}} container: ${{matrix.container}} env: {B2_USE_CCACHE: 1} diff --git a/doc/unordered/changes.adoc b/doc/unordered/changes.adoc index c99d69a5..8f1c37b0 100644 --- a/doc/unordered/changes.adoc +++ b/doc/unordered/changes.adoc @@ -16,6 +16,9 @@ https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2363r5.html[P2363]. * Replaced the previous post-mixing process for open-addressing containers with a new algorithm based on extended multiplication by a constant. +* Fixed bug in internal emplace() impl where stack-local types were not properly + constructed using the Allocator of the container which breaks uses-allocator + construction. == Release 1.81.0 - Major update diff --git a/include/boost/unordered/detail/foa.hpp b/include/boost/unordered/detail/foa.hpp index c4d8b56f..c5c7abe6 100644 --- a/include/boost/unordered/detail/foa.hpp +++ b/include/boost/unordered/detail/foa.hpp @@ -1186,6 +1186,14 @@ _STL_RESTORE_DEPRECATED_WARNING */ constexpr static float const mlf = 0.875f; +template +union uninitialized_storage +{ + T t_; + uninitialized_storage(){} + ~uninitialized_storage(){} +}; + /* foa::table interface departs in a number of ways from that of C++ unordered * associative containers because it's not for end-user consumption * (boost::unordered_[flat|node]_[map|set]) wrappers complete it as @@ -1220,11 +1228,17 @@ constexpr static float const mlf = 0.875f; * a copyable const std::string&&. foa::table::insert is extended to accept * both init_type and value_type references. * - * - TypePolicy::move(element_type&) returns a temporary object for value - * transfer on rehashing, move copy/assignment, and merge. For flat map, this - * object is a std::pair, which is generally cheaper to move - * than std::pair&& because of the constness in Key. For - * node-based tables, this is used to transfer ownership of pointer. + * - TypePolicy::construct and TypePolicy::destroy are used for the + * construction and destruction of the internal types: value_type, init_type + * and element_type. + * + * - TypePolicy::move is used to provide move semantics for the internal + * types used by the container during rehashing and emplace. These types + * are init_type, value_type and emplace_type. During insertion, a + * stack-local type will be created based on the constructibility of the + * value_type and the supplied arguments. TypePolicy::move is used here + * for transfer of ownership. Similarly, TypePolicy::move is also used + * during rehashing when elements are moved to the new table. * * - TypePolicy::extract returns a const reference to the key part of * a value of type value_type, init_type, element_type or @@ -1475,18 +1489,25 @@ public: template BOOST_FORCEINLINE std::pair emplace(Args&&... args) { - /* We dispatch based on whether or not the value_type is constructible from - * an rvalue reference of the deduced emplace_type. We do this specifically - * for the case of the node-based containers. To this end, we're able to - * avoid allocating a node when a duplicate element is attempted to be - * inserted. For immovable types, we instead dispatch to the routine that - * unconditionally allocates via `type_policy::construct()`. - */ - return emplace_value( + using emplace_type=typename std::conditional< + std::is_constructible::value, + init_type, + value_type + >::type; + + using insert_type=typename std::conditional< std::is_constructible< - value_type, - emplace_type&&>{}, - std::forward(args)...); + value_type,emplace_type>::value, + emplace_type,element_type + >::type; + + uninitialized_storage s; + auto *p=std::addressof(s.t_); + + type_policy::construct(al(),p,std::forward(args)...); + + destroy_on_exit guard{al(),p}; + return emplace_impl(type_policy::move(*p)); } template @@ -1671,15 +1692,6 @@ private: template friend class table; using arrays_type=table_arrays; - template - using emplace_type = typename std::conditional< - std::is_constructible< - init_type,Args... - >::value, - init_type, - value_type - >::type; - struct clear_on_exit { ~clear_on_exit(){x.clear();} @@ -1698,6 +1710,14 @@ private: bool rollback_=false; }; + template + struct destroy_on_exit + { + Allocator &a; + T *p; + ~destroy_on_exit(){type_policy::destroy(a,p);}; + }; + Hash& h(){return hash_base::get();} const Hash& h()const{return hash_base::get();} Pred& pred(){return pred_base::get();} @@ -1961,29 +1981,6 @@ private: #pragma warning(pop) /* C4800 */ #endif - template - BOOST_FORCEINLINE std::pair emplace_value( - std::true_type /* movable value_type */,Args&&... args - ) { - using emplace_type_t = emplace_type; - return emplace_impl(emplace_type_t(std::forward(args)...)); - } - - template - BOOST_FORCEINLINE std::pair emplace_value( - std::false_type /* immovable value_type */,Args&&... args - ) { - alignas(element_type) - unsigned char buf[sizeof(element_type)]; - element_type* p = reinterpret_cast(buf); - - type_policy::construct(al(),p,std::forward(args)...); - destroy_element_on_exit d{this,p}; - (void)d; - - return emplace_impl(type_policy::move(*p)); - } - template BOOST_FORCEINLINE std::pair emplace_impl(Args&&... args) { diff --git a/include/boost/unordered/detail/foa/element_type.hpp b/include/boost/unordered/detail/foa/element_type.hpp new file mode 100644 index 00000000..650e3b4f --- /dev/null +++ b/include/boost/unordered/detail/foa/element_type.hpp @@ -0,0 +1,60 @@ +/* Copyright 2023 Christian Mazakas. + * 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_FOA_ELEMENT_TYPE_HPP +#define BOOST_UNORDERED_DETAIL_FOA_ELEMENT_TYPE_HPP + +namespace boost{ +namespace unordered{ +namespace detail{ +namespace foa{ + +template +struct element_type +{ + using value_type=T; + value_type* p; + + /* + * we use a deleted copy constructor here so the type is no longer + * trivially copy-constructible which inhibits our memcpy + * optimizations when copying the tables + */ + element_type() = default; + element_type(value_type* p_):p(p_){} + element_type(element_type const&) = delete; + element_type(element_type&& rhs) noexcept + { + p = rhs.p; + rhs.p = nullptr; + } + + element_type& operator=(element_type const&)=delete; + element_type& operator=(element_type&& rhs)noexcept + { + if (this!=&rhs){ + p=rhs.p; + rhs.p=nullptr; + } + return *this; + } + + void swap(element_type& rhs)noexcept + { + auto tmp=p; + p=rhs.p; + rhs.p=tmp; + } +}; + +} +} +} +} + +#endif // BOOST_UNORDERED_DETAIL_FOA_ELEMENT_TYPE_HPP diff --git a/include/boost/unordered/detail/foa/node_handle.hpp b/include/boost/unordered/detail/foa/node_handle.hpp index 1f6319db..ddf34414 100644 --- a/include/boost/unordered/detail/foa/node_handle.hpp +++ b/include/boost/unordered/detail/foa/node_handle.hpp @@ -45,20 +45,30 @@ struct node_handle_base private: using node_value_type=typename type_policy::value_type; - node_value_type* p_=nullptr; + element_type p_; BOOST_ATTRIBUTE_NO_UNIQUE_ADDRESS opt_storage a_; protected: - node_value_type& element()noexcept + node_value_type& data()noexcept { - BOOST_ASSERT(!empty()); - return *p_; + return *(p_.p); } - node_value_type const& element()const noexcept + node_value_type const& data()const noexcept + { + return *(p_.p); + } + + element_type& element()noexcept { BOOST_ASSERT(!empty()); - return *p_; + return p_; + } + + element_type const& element()const noexcept + { + BOOST_ASSERT(!empty()); + return p_; } Allocator& al()noexcept @@ -73,32 +83,29 @@ struct node_handle_base return a_.t_; } - void emplace(node_value_type* p,Allocator a) - { - BOOST_ASSERT(empty()); - p_=p; - new(&a_.t_)Allocator(a); - } - void emplace(element_type&& x,Allocator a) { - emplace(x.p,a); + BOOST_ASSERT(empty()); + auto* p=x.p; + p_.p=p; + new(&a_.t_)Allocator(a); x.p=nullptr; } void reset() { - al().~Allocator(); - p_=nullptr; + a_.t_.~Allocator(); + p_.p=nullptr; } public: - constexpr node_handle_base()noexcept{} + constexpr node_handle_base()noexcept:p_{nullptr}{} node_handle_base(node_handle_base&& nh) noexcept { + p_.p = nullptr; if (!nh.empty()){ - emplace(nh.p_,nh.al()); + emplace(std::move(nh.p_),nh.al()); nh.reset(); } } @@ -110,12 +117,12 @@ struct node_handle_base if(nh.empty()){ /* empty(), nh.empty() */ /* nothing to do */ }else{ /* empty(), !nh.empty() */ - emplace(nh.p_,std::move(nh.al())); + emplace(std::move(nh.p_),std::move(nh.al())); nh.reset(); } }else{ if(nh.empty()){ /* !empty(), nh.empty() */ - type_policy::destroy(al(),p_); + type_policy::destroy(al(),&p_); reset(); }else{ /* !empty(), !nh.empty() */ bool const pocma= @@ -124,12 +131,12 @@ struct node_handle_base BOOST_ASSERT(pocma||al()==nh.al()); - type_policy::destroy(al(),p_); + type_policy::destroy(al(),&p_); if(pocma){ al()=std::move(nh.al()); } - p_=nh.p_; + p_=std::move(nh.p_); nh.reset(); } } @@ -137,7 +144,7 @@ struct node_handle_base if(empty()){ /* empty(), nh.empty() */ /* nothing to do */ }else{ /* !empty(), !nh.empty() */ - type_policy::destroy(al(),p_); + type_policy::destroy(al(),&p_); reset(); } } @@ -147,14 +154,14 @@ struct node_handle_base ~node_handle_base() { if(!empty()){ - type_policy::destroy(al(),p_); + type_policy::destroy(al(),&p_); reset(); } } allocator_type get_allocator()const noexcept{return al();} explicit operator bool()const noexcept{ return !empty();} - BOOST_ATTRIBUTE_NODISCARD bool empty()const noexcept{return p_==nullptr;} + BOOST_ATTRIBUTE_NODISCARD bool empty()const noexcept{return p_.p==nullptr;} void swap(node_handle_base& nh) noexcept( boost::allocator_is_always_equal::type::value|| @@ -165,12 +172,12 @@ struct node_handle_base if(nh.empty()) { /* nothing to do here */ } else { - emplace(nh.p_, nh.al()); + emplace(std::move(nh.p_), nh.al()); nh.reset(); } }else{ if(nh.empty()){ - nh.emplace(p_,al()); + nh.emplace(std::move(p_),al()); reset(); }else{ bool const pocs= @@ -180,7 +187,7 @@ struct node_handle_base BOOST_ASSERT(pocs || al()==nh.al()); using std::swap; - swap(p_,nh.p_); + p_.swap(nh.p_); if(pocs)swap(al(),nh.al()); } } diff --git a/include/boost/unordered/detail/requires_cxx11.hpp b/include/boost/unordered/detail/requires_cxx11.hpp new file mode 100644 index 00000000..b7c7b813 --- /dev/null +++ b/include/boost/unordered/detail/requires_cxx11.hpp @@ -0,0 +1,21 @@ +#ifndef BOOST_UNORDERED_DETAIL_REQUIRES_CXX11_HPP_INCLUDED +#define BOOST_UNORDERED_DETAIL_REQUIRES_CXX11_HPP_INCLUDED + +// Copyright 2023 Peter Dimov +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include + +#if defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) || \ + defined(BOOST_NO_CXX11_RVALUE_REFERENCES) || \ + defined(BOOST_NO_CXX11_DECLTYPE) || \ + defined(BOOST_NO_CXX11_CONSTEXPR) || \ + defined(BOOST_NO_CXX11_NOEXCEPT) + +BOOST_PRAGMA_MESSAGE("C++03 support is deprecated in Boost.Unordered 1.82 and will be removed in Boost.Unordered 1.84.") + +#endif + +#endif // #ifndef BOOST_UNORDERED_DETAIL_REQUIRES_CXX11_HPP_INCLUDED diff --git a/include/boost/unordered/unordered_flat_map.hpp b/include/boost/unordered/unordered_flat_map.hpp index d419739b..7fb736f6 100644 --- a/include/boost/unordered/unordered_flat_map.hpp +++ b/include/boost/unordered/unordered_flat_map.hpp @@ -53,6 +53,11 @@ namespace boost { return kv.first; } + static moved_type move(init_type& x) + { + return {std::move(x.first), std::move(x.second)}; + } + static moved_type move(element_type& x) { // TODO: we probably need to launder here @@ -61,12 +66,23 @@ namespace boost { } template - static void construct(A& al, element_type* p, Args&&... args) + static void construct(A& al, init_type* p, Args&&... args) { boost::allocator_construct(al, p, std::forward(args)...); } - template static void destroy(A& al, element_type* p) noexcept + template + static void construct(A& al, value_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); + } + + template static void destroy(A& al, value_type* p) noexcept { boost::allocator_destroy(al, p); } diff --git a/include/boost/unordered/unordered_flat_set.hpp b/include/boost/unordered/unordered_flat_set.hpp index 8829742e..2138a9f3 100644 --- a/include/boost/unordered/unordered_flat_set.hpp +++ b/include/boost/unordered/unordered_flat_set.hpp @@ -46,12 +46,12 @@ namespace boost { static element_type&& move(element_type& x) { return std::move(x); } template - static void construct(A& al, element_type* p, Args&&... args) + static void construct(A& al, value_type* p, Args&&... args) { boost::allocator_construct(al, p, std::forward(args)...); } - template static void destroy(A& al, element_type* p) noexcept + template static void destroy(A& al, value_type* p) noexcept { boost::allocator_destroy(al, p); } diff --git a/include/boost/unordered/unordered_map.hpp b/include/boost/unordered/unordered_map.hpp index 6d05471c..8a8bc062 100644 --- a/include/boost/unordered/unordered_map.hpp +++ b/include/boost/unordered/unordered_map.hpp @@ -15,6 +15,7 @@ #pragma once #endif +#include #include #include #include diff --git a/include/boost/unordered/unordered_node_map.hpp b/include/boost/unordered/unordered_node_map.hpp index 3802d494..450e7809 100644 --- a/include/boost/unordered/unordered_node_map.hpp +++ b/include/boost/unordered/unordered_node_map.hpp @@ -11,6 +11,7 @@ #endif #include +#include #include #include #include @@ -45,23 +46,7 @@ namespace boost { using value_type = std::pair; using moved_type = std::pair; - struct element_type - { - value_type* p; - - /* - * we use a deleted copy constructor here so the type is no longer - * trivially copy-constructible which inhibits our memcpy - * optimizations when copying the tables - */ - element_type() = default; - element_type(element_type const&) = delete; - element_type(element_type&& rhs) noexcept - { - p = rhs.p; - rhs.p = nullptr; - } - }; + using element_type=foa::element_type; static value_type& value_from(element_type const& x) { return *(x.p); } @@ -77,6 +62,11 @@ namespace boost { } static element_type&& move(element_type& x) { return std::move(x); } + static moved_type move(init_type& x) + { + return {std::move(x.first), std::move(x.second)}; + } + static moved_type move(value_type& x) { return {std::move(const_cast(x.first)), @@ -96,6 +86,18 @@ namespace boost { construct(al, p, *copy.p); } + template + static void construct(A& al, init_type* p, Args&&... args) + { + boost::allocator_construct(al, p, std::forward(args)...); + } + + template + static void construct(A& al, value_type* p, Args&&... args) + { + boost::allocator_construct(al, p, std::forward(args)...); + } + template static void construct(A& al, element_type* p, Args&&... args) { @@ -106,10 +108,11 @@ namespace boost { } BOOST_CATCH(...) { - boost::allocator_deallocate(al, - boost::pointer_traits< - typename boost::allocator_pointer::type>::pointer_to(*p->p), - 1); + using pointer_type = typename boost::allocator_pointer::type; + using pointer_traits = boost::pointer_traits; + + boost::allocator_deallocate( + al, pointer_traits::pointer_to(*(p->p)), 1); BOOST_RETHROW } BOOST_CATCH_END @@ -118,16 +121,22 @@ namespace boost { template static void destroy(A& al, value_type* p) noexcept { boost::allocator_destroy(al, p); - boost::allocator_deallocate(al, - boost::pointer_traits< - typename boost::allocator_pointer::type>::pointer_to(*p), - 1); + } + + template static void destroy(A& al, init_type* p) noexcept + { + boost::allocator_destroy(al, p); } template static void destroy(A& al, element_type* p) noexcept { if (p->p) { - destroy(al,p->p); + using pointer_type = typename boost::allocator_pointer::type; + using pointer_traits = boost::pointer_traits; + + destroy(al, p->p); + boost::allocator_deallocate( + al, pointer_traits::pointer_to(*(p->p)), 1); } } }; @@ -137,8 +146,7 @@ namespace boost { : public detail::foa::node_handle_base { private: - using base_type = - detail::foa::node_handle_base; + using base_type = detail::foa::node_handle_base; using typename base_type::type_policy; @@ -157,13 +165,13 @@ namespace boost { key_type& key() const { BOOST_ASSERT(!this->empty()); - return const_cast(this->element().first); + return const_cast(this->data().first); } mapped_type& mapped() const { BOOST_ASSERT(!this->empty()); - return const_cast(this->element().second); + return const_cast(this->data().second); } }; } // namespace detail @@ -402,10 +410,7 @@ namespace boost { BOOST_ASSERT(get_allocator() == nh.get_allocator()); - typename map_types::element_type x; - x.p = std::addressof(nh.element()); - - auto itp = table_.insert(std::move(x)); + auto itp = table_.insert(std::move(nh.element())); if (itp.second) { nh.reset(); return {itp.first, true, node_type{}}; @@ -422,10 +427,7 @@ namespace boost { BOOST_ASSERT(get_allocator() == nh.get_allocator()); - typename map_types::element_type x; - x.p = std::addressof(nh.element()); - - auto itp = table_.insert(std::move(x)); + auto itp = table_.insert(std::move(nh.element())); if (itp.second) { nh.reset(); return itp.first; diff --git a/include/boost/unordered/unordered_node_set.hpp b/include/boost/unordered/unordered_node_set.hpp index c6fc8617..30a63502 100644 --- a/include/boost/unordered/unordered_node_set.hpp +++ b/include/boost/unordered/unordered_node_set.hpp @@ -11,6 +11,7 @@ #endif #include +#include #include #include #include @@ -41,23 +42,7 @@ namespace boost { static Key const& extract(value_type const& key) { return key; } - struct element_type - { - value_type* p; - - /* - * we use a deleted copy constructor here so the type is no longer - * trivially copy-constructible which inhibits our memcpy - * optimizations when copying the tables - */ - element_type() = default; - element_type(element_type const&) = delete; - element_type(element_type&& rhs) noexcept - { - p = rhs.p; - rhs.p = nullptr; - } - }; + using element_type=foa::element_type; static value_type& value_from(element_type const& x) { return *x.p; } static Key const& extract(element_type const& k) { return *k.p; } @@ -78,6 +63,12 @@ namespace boost { x.p = nullptr; } + template + static void construct(A& al, value_type* p, Args&&... args) + { + boost::allocator_construct(al, p, std::forward(args)...); + } + template static void construct(A& al, element_type* p, Args&&... args) { @@ -100,16 +91,16 @@ namespace boost { template static void destroy(A& al, value_type* p) noexcept { boost::allocator_destroy(al, p); - boost::allocator_deallocate(al, - boost::pointer_traits< - typename boost::allocator_pointer::type>::pointer_to(*p), - 1); } template static void destroy(A& al, element_type* p) noexcept { if (p->p) { destroy(al, p->p); + boost::allocator_deallocate(al, + boost::pointer_traits::type>::pointer_to(*(p->p)), + 1); } } }; @@ -119,8 +110,7 @@ namespace boost { : public detail::foa::node_handle_base { private: - using base_type = - detail::foa::node_handle_base; + using base_type = detail::foa::node_handle_base; using typename base_type::type_policy; @@ -137,7 +127,7 @@ namespace boost { value_type& value() const { BOOST_ASSERT(!this->empty()); - return const_cast(this->element()); + return const_cast(this->data()); } }; } // namespace detail @@ -390,10 +380,7 @@ namespace boost { BOOST_ASSERT(get_allocator() == nh.get_allocator()); - typename set_types::element_type x; - x.p=std::addressof(nh.element()); - - auto itp = table_.insert(std::move(x)); + auto itp = table_.insert(std::move(nh.element())); if (itp.second) { nh.reset(); return {itp.first, true, node_type{}}; @@ -410,10 +397,7 @@ namespace boost { BOOST_ASSERT(get_allocator() == nh.get_allocator()); - typename set_types::element_type x; - x.p=std::addressof(nh.element()); - - auto itp = table_.insert(std::move(x)); + auto itp = table_.insert(std::move(nh.element())); if (itp.second) { nh.reset(); return itp.first; @@ -478,7 +462,7 @@ namespace boost { node_type extract(key_type const& key) { auto pos = find(key); - return pos!=end()?extract(pos):node_type(); + return pos != end() ? extract(pos) : node_type(); } template @@ -489,7 +473,7 @@ namespace boost { extract(K const& key) { auto pos = find(key); - return pos!=end()?extract(pos):node_type(); + return pos != end() ? extract(pos) : node_type(); } template diff --git a/include/boost/unordered/unordered_set.hpp b/include/boost/unordered/unordered_set.hpp index 1bd4ba3d..23837b2d 100644 --- a/include/boost/unordered/unordered_set.hpp +++ b/include/boost/unordered/unordered_set.hpp @@ -15,6 +15,7 @@ #pragma once #endif +#include #include #include #include diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 4f7a537f..31b17efd 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -144,6 +144,7 @@ build_foa init_type_insert_tests ; build_foa max_load_tests ; build_foa extract_tests ; build_foa node_handle_tests ; +build_foa uses_allocator ; run unordered/hash_is_avalanching_test.cpp ; diff --git a/test/unordered/uses_allocator.cpp b/test/unordered/uses_allocator.cpp new file mode 100644 index 00000000..066a3585 --- /dev/null +++ b/test/unordered/uses_allocator.cpp @@ -0,0 +1,189 @@ + +// Copyright 2023 Christian Mazakas. +// 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) + +#include "../helpers/test.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#if BOOST_CXX_VERSION <= 199711L || \ + BOOST_WORKAROUND(BOOST_GCC_VERSION, < 40800) || \ + (defined(BOOST_LIBSTDCXX_VERSION) && BOOST_CXX_VERSION > 201703L) || \ + (defined(BOOST_MSVC_FULL_VER) && BOOST_MSVC_FULL_VER >= 192000000 && \ + BOOST_MSVC_FULL_VER < 193000000) + +// automatically disable this test for C++03 builds so we can use the STL's +// scoped_allocator_adaptor +// we remove C++20 support for libstdc++ builds because of: +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108952 +// +// msvc-14.2 w/ C++20 is similarly affected +// + + +BOOST_PRAGMA_MESSAGE("uses_allocator tests require C++11, scoped_allocator") + +int main() {} + +#else + +#include +#include +#include +#include + +template struct allocator +{ + typedef T value_type; + + int tag_ = -1; + + allocator() = default; + allocator(int tag) : tag_{tag} {} + allocator(allocator const&) = default; + allocator(allocator&&) = default; + + template allocator(allocator const& rhs) : tag_{rhs.tag_} {} + + BOOST_ATTRIBUTE_NODISCARD T* allocate(std::size_t n) + { + return static_cast(::operator new(n * sizeof(T))); + } + + void deallocate(T* p, std::size_t) noexcept { ::operator delete(p); } + + allocator& operator=(allocator const& rhs) + { + tag_ = rhs.tag_; + return *this; + } + + allocator& operator=(allocator&& rhs) noexcept + { + tag_ = rhs.tag_; + return *this; + } + + bool operator==(allocator const&) const { return true; } + bool operator!=(allocator const&) const { return false; } +}; + +struct raii_tracker +{ + static int count; + static int copy_count; + static int move_count; + static int alloc_move_count; + + using allocator_type = allocator; + + allocator_type a_; + + raii_tracker(allocator_type a) : a_(a) { ++count; } + raii_tracker(int, allocator_type const& a) : a_(a) { ++count; } + + raii_tracker(raii_tracker const&) { ++copy_count; } + raii_tracker(raii_tracker&&) noexcept { ++move_count; } + raii_tracker(raii_tracker&&, allocator_type const& a) noexcept : a_(a) + { + ++alloc_move_count; + } + + allocator_type get_allocator() const noexcept { return a_; } + + friend bool operator==(raii_tracker const&, raii_tracker const&) + { + return true; + } +}; + +int raii_tracker::count = 0; +int raii_tracker::copy_count = 0; +int raii_tracker::move_count = 0; +int raii_tracker::alloc_move_count = 0; + +static void reset_counts() +{ + raii_tracker::count = 0; + raii_tracker::copy_count = 0; + raii_tracker::move_count = 0; + raii_tracker::alloc_move_count = 0; +} + +std::size_t hash_value(raii_tracker const&) { return 0; } + +using map_allocator_type = std::scoped_allocator_adaptor< + allocator >, allocator >; + +using set_allocator_type = + std::scoped_allocator_adaptor, allocator >; + +using map_type = boost::unordered_flat_map, std::equal_to, map_allocator_type>; + +using node_map_type = boost::unordered_node_map, std::equal_to, map_allocator_type>; + +using set_type = boost::unordered_flat_set, std::equal_to, set_allocator_type>; + +using node_set_type = boost::unordered_node_set, std::equal_to, set_allocator_type>; + +map_type* flat_map; +node_map_type* node_map; + +set_type* flat_set; +node_set_type* node_set; + +template static void map_uses_allocator_construction(X*) +{ + reset_counts(); + + map_allocator_type alloc( + allocator >{12}, + allocator{34}); + + X map(1, alloc); + map.emplace( + std::piecewise_construct, std::make_tuple(1337), std::make_tuple(7331)); + + BOOST_TEST_EQ(raii_tracker::count, 2); + BOOST_TEST_EQ(raii_tracker::move_count, 0); + BOOST_TEST_EQ(raii_tracker::alloc_move_count, 2); + + BOOST_TEST_EQ(map.begin()->first.get_allocator().tag_, 34); + BOOST_TEST_EQ(map.begin()->second.get_allocator().tag_, 34); +} + +template static void set_uses_allocator_construction(X*) +{ + reset_counts(); + + set_allocator_type alloc(allocator{12}, allocator{34}); + + X set(1, alloc); + set.emplace(); + + BOOST_TEST_EQ(raii_tracker::count, 1); + BOOST_TEST_EQ(raii_tracker::move_count, 0); + BOOST_TEST_EQ(raii_tracker::alloc_move_count, 1); + + BOOST_TEST_EQ(set.begin()->get_allocator().tag_, 34); +} + +UNORDERED_TEST(map_uses_allocator_construction, ((flat_map)(node_map))) +UNORDERED_TEST(set_uses_allocator_construction, ((flat_set)(node_set))) + +RUN_TESTS() + +#endif