Merge branch 'feature/foa_fast_iteration' of https://github.com/boostorg/unordered into feature/foa_fast_iteration

This commit is contained in:
joaquintides 2023-03-06 21:44:55 +01:00
commit 7faec20f26
15 changed files with 436 additions and 154 deletions

View File

@ -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' };

View File

@ -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}

View File

@ -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

View File

@ -1186,6 +1186,14 @@ _STL_RESTORE_DEPRECATED_WARNING
*/
constexpr static float const mlf = 0.875f;
template <class T>
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<Key&&,T&&>, which is generally cheaper to move
* than std::pair<const Key,T>&& 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<typename... Args>
BOOST_FORCEINLINE std::pair<iterator,bool> 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<init_type,Args...>::value,
init_type,
value_type
>::type;
using insert_type=typename std::conditional<
std::is_constructible<
value_type,
emplace_type<Args...>&&>{},
std::forward<Args>(args)...);
value_type,emplace_type>::value,
emplace_type,element_type
>::type;
uninitialized_storage<insert_type> s;
auto *p=std::addressof(s.t_);
type_policy::construct(al(),p,std::forward<Args>(args)...);
destroy_on_exit<insert_type> guard{al(),p};
return emplace_impl(type_policy::move(*p));
}
template<typename Key,typename... Args>
@ -1671,15 +1692,6 @@ private:
template<typename,typename,typename,typename> friend class table;
using arrays_type=table_arrays<element_type,group_type,size_policy>;
template<typename... Args>
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 <class T>
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<typename... Args>
BOOST_FORCEINLINE std::pair<iterator,bool> emplace_value(
std::true_type /* movable value_type */,Args&&... args
) {
using emplace_type_t = emplace_type<Args...>;
return emplace_impl(emplace_type_t(std::forward<Args>(args)...));
}
template<typename... Args>
BOOST_FORCEINLINE std::pair<iterator,bool> emplace_value(
std::false_type /* immovable value_type */,Args&&... args
) {
alignas(element_type)
unsigned char buf[sizeof(element_type)];
element_type* p = reinterpret_cast<element_type*>(buf);
type_policy::construct(al(),p,std::forward<Args>(args)...);
destroy_element_on_exit d{this,p};
(void)d;
return emplace_impl(type_policy::move(*p));
}
template<typename... Args>
BOOST_FORCEINLINE std::pair<iterator,bool> emplace_impl(Args&&... args)
{

View File

@ -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<class T>
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

View File

@ -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<Allocator> 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<Allocator>::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());
}
}

View File

@ -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 <boost/config.hpp>
#include <boost/config/pragma_message.hpp>
#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

View File

@ -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 <class A, class... Args>
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>(args)...);
}
template <class A> static void destroy(A& al, element_type* p) noexcept
template <class A, class... Args>
static void construct(A& al, value_type* p, Args&&... args)
{
boost::allocator_construct(al, p, std::forward<Args>(args)...);
}
template <class A> static void destroy(A& al, init_type* p) noexcept
{
boost::allocator_destroy(al, p);
}
template <class A> static void destroy(A& al, value_type* p) noexcept
{
boost::allocator_destroy(al, p);
}

View File

@ -46,12 +46,12 @@ namespace boost {
static element_type&& move(element_type& x) { return std::move(x); }
template <class A, class... Args>
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>(args)...);
}
template <class A> static void destroy(A& al, element_type* p) noexcept
template <class A> static void destroy(A& al, value_type* p) noexcept
{
boost::allocator_destroy(al, p);
}

View File

@ -15,6 +15,7 @@
#pragma once
#endif
#include <boost/unordered/detail/requires_cxx11.hpp>
#include <boost/core/explicit_operator_bool.hpp>
#include <boost/functional/hash.hpp>
#include <boost/move/move.hpp>

View File

@ -11,6 +11,7 @@
#endif
#include <boost/unordered/detail/foa.hpp>
#include <boost/unordered/detail/foa/element_type.hpp>
#include <boost/unordered/detail/foa/node_handle.hpp>
#include <boost/unordered/detail/type_traits.hpp>
#include <boost/unordered/unordered_node_map_fwd.hpp>
@ -45,23 +46,7 @@ namespace boost {
using value_type = std::pair<Key const, T>;
using moved_type = std::pair<raw_key_type&&, raw_mapped_type&&>;
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<value_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<raw_key_type&>(x.first)),
@ -96,6 +86,18 @@ namespace boost {
construct(al, p, *copy.p);
}
template <class A, class... Args>
static void construct(A& al, init_type* p, Args&&... args)
{
boost::allocator_construct(al, p, std::forward<Args>(args)...);
}
template <class A, class... Args>
static void construct(A& al, value_type* p, Args&&... args)
{
boost::allocator_construct(al, p, std::forward<Args>(args)...);
}
template <class A, class... Args>
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<A>::type>::pointer_to(*p->p),
1);
using pointer_type = typename boost::allocator_pointer<A>::type;
using pointer_traits = boost::pointer_traits<pointer_type>;
boost::allocator_deallocate(
al, pointer_traits::pointer_to(*(p->p)), 1);
BOOST_RETHROW
}
BOOST_CATCH_END
@ -118,16 +121,22 @@ namespace boost {
template <class A> 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<A>::type>::pointer_to(*p),
1);
}
template <class A> static void destroy(A& al, init_type* p) noexcept
{
boost::allocator_destroy(al, p);
}
template <class A> static void destroy(A& al, element_type* p) noexcept
{
if (p->p) {
destroy(al,p->p);
using pointer_type = typename boost::allocator_pointer<A>::type;
using pointer_traits = boost::pointer_traits<pointer_type>;
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<TypePolicy, Allocator>
{
private:
using base_type =
detail::foa::node_handle_base<TypePolicy, Allocator>;
using base_type = detail::foa::node_handle_base<TypePolicy, Allocator>;
using typename base_type::type_policy;
@ -157,13 +165,13 @@ namespace boost {
key_type& key() const
{
BOOST_ASSERT(!this->empty());
return const_cast<key_type&>(this->element().first);
return const_cast<key_type&>(this->data().first);
}
mapped_type& mapped() const
{
BOOST_ASSERT(!this->empty());
return const_cast<mapped_type&>(this->element().second);
return const_cast<mapped_type&>(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;

View File

@ -11,6 +11,7 @@
#endif
#include <boost/unordered/detail/foa.hpp>
#include <boost/unordered/detail/foa/element_type.hpp>
#include <boost/unordered/detail/foa/node_handle.hpp>
#include <boost/unordered/detail/type_traits.hpp>
#include <boost/unordered/unordered_node_set_fwd.hpp>
@ -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<value_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 <class A, class... Args>
static void construct(A& al, value_type* p, Args&&... args)
{
boost::allocator_construct(al, p, std::forward<Args>(args)...);
}
template <class A, class... Args>
static void construct(A& al, element_type* p, Args&&... args)
{
@ -100,16 +91,16 @@ namespace boost {
template <class A> 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<A>::type>::pointer_to(*p),
1);
}
template <class A> static void destroy(A& al, element_type* p) noexcept
{
if (p->p) {
destroy(al, p->p);
boost::allocator_deallocate(al,
boost::pointer_traits<typename boost::allocator_pointer<
A>::type>::pointer_to(*(p->p)),
1);
}
}
};
@ -119,8 +110,7 @@ namespace boost {
: public detail::foa::node_handle_base<TypePolicy, Allocator>
{
private:
using base_type =
detail::foa::node_handle_base<TypePolicy, Allocator>;
using base_type = detail::foa::node_handle_base<TypePolicy, Allocator>;
using typename base_type::type_policy;
@ -137,7 +127,7 @@ namespace boost {
value_type& value() const
{
BOOST_ASSERT(!this->empty());
return const_cast<value_type&>(this->element());
return const_cast<value_type&>(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 <class K>
@ -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 <class H2, class P2>

View File

@ -15,6 +15,7 @@
#pragma once
#endif
#include <boost/unordered/detail/requires_cxx11.hpp>
#include <boost/core/explicit_operator_bool.hpp>
#include <boost/functional/hash.hpp>
#include <boost/move/move.hpp>

View File

@ -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 ;

View File

@ -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 <boost/unordered/detail/implementation.hpp>
#include <boost/unordered/unordered_flat_map.hpp>
#include <boost/unordered/unordered_flat_set.hpp>
#include <boost/unordered/unordered_node_map.hpp>
#include <boost/unordered/unordered_node_set.hpp>
#include <boost/config.hpp>
#include <boost/config/pragma_message.hpp>
#include <boost/config/workaround.hpp>
#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 <memory>
#include <scoped_allocator>
#include <unordered_map>
#include <vector>
template <class T> struct allocator
{
typedef T value_type;
int tag_ = -1;
allocator() = default;
allocator(int tag) : tag_{tag} {}
allocator(allocator const&) = default;
allocator(allocator&&) = default;
template <class U> allocator(allocator<U> const& rhs) : tag_{rhs.tag_} {}
BOOST_ATTRIBUTE_NODISCARD T* allocate(std::size_t n)
{
return static_cast<T*>(::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<int>;
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<std::pair<raii_tracker const, raii_tracker> >, allocator<int> >;
using set_allocator_type =
std::scoped_allocator_adaptor<allocator<raii_tracker>, allocator<int> >;
using map_type = boost::unordered_flat_map<raii_tracker, raii_tracker,
boost::hash<raii_tracker>, std::equal_to<raii_tracker>, map_allocator_type>;
using node_map_type = boost::unordered_node_map<raii_tracker, raii_tracker,
boost::hash<raii_tracker>, std::equal_to<raii_tracker>, map_allocator_type>;
using set_type = boost::unordered_flat_set<raii_tracker,
boost::hash<raii_tracker>, std::equal_to<raii_tracker>, set_allocator_type>;
using node_set_type = boost::unordered_node_set<raii_tracker,
boost::hash<raii_tracker>, std::equal_to<raii_tracker>, set_allocator_type>;
map_type* flat_map;
node_map_type* node_map;
set_type* flat_set;
node_set_type* node_set;
template <class X> static void map_uses_allocator_construction(X*)
{
reset_counts();
map_allocator_type alloc(
allocator<std::pair<raii_tracker const, raii_tracker> >{12},
allocator<int>{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 <class X> static void set_uses_allocator_construction(X*)
{
reset_counts();
set_allocator_type alloc(allocator<raii_tracker>{12}, allocator<int>{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