Skip to content

Commit f26b0ff

Browse files
committed
Implement C++20's uninitialized_construct_using_allocator
1 parent 3183fc2 commit f26b0ff

File tree

4 files changed

+318
-15
lines changed

4 files changed

+318
-15
lines changed

doc/container.qbk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1438,6 +1438,7 @@ use [*Boost.Container]? There are several reasons for that:
14381438

14391439
* Implemented heterogeneous overloads from C++23 ([@http://wg21.link/p2077r3 P2077]) and C++26 ([@http://wg21.link/P2363 P2363]).
14401440
* In C++20 compilers, `static_vector<T>`'s destructor is now trivial if `T` is trivial.
1441+
* Implemented C++20's [classref boost::container::uninitialized_construct_using_allocator uninitialized_construct_using_allocator].
14411442
* Fixed bugs/issues:
14421443
* [@https://github.com/boostorg/container/issues/323 GitHub #323: ['"flat_tree::try_emplace UB"]].
14431444

include/boost/container/uses_allocator.hpp

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,17 @@ namespace container {
2121

2222
namespace dtl {
2323

24-
template<typename T, typename Allocator>
24+
template<typename T, typename AllocArg>
2525
struct uses_allocator_imp
2626
{
2727
// Use SFINAE (Substitution Failure Is Not An Error) to detect the
28-
// presence of an 'allocator_type' nested type convertilble from Allocator.
28+
// presence of an 'allocator_type' nested type convertilble from AllocArg.
2929
private:
3030
typedef char yes_type;
3131
struct no_type{ char dummy[2]; };
3232

3333
// Match this function if T::allocator_type exists and is
34-
// implicitly convertible from Allocator
34+
// implicitly convertible from `AllocArg`
3535
template <class U>
3636
static yes_type test(typename U::allocator_type);
3737

@@ -43,10 +43,10 @@ struct uses_allocator_imp
4343
>::type test(const V&);
4444

4545
// Match this function if TypeT::allocator_type does not exist or is
46-
// not convertible from Allocator.
46+
// not convertible from `AllocArg`.
4747
template <typename U>
4848
static no_type test(...);
49-
static Allocator alloc; // Declared but not defined
49+
static AllocArg alloc; // Declared but not defined
5050

5151
public:
5252
BOOST_STATIC_CONSTEXPR bool value = sizeof(test<T>(alloc)) == sizeof(yes_type);
@@ -64,23 +64,49 @@ struct constructible_with_allocator_suffix
6464

6565
#endif //#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED
6666

67-
//! <b>Remark</b>: Automatically detects whether T has a nested allocator_type that is convertible from
68-
//! Allocator. Meets the BinaryTypeTrait requirements ([meta.rqmts] 20.4.1). A program may
69-
//! specialize this type to define `uses_allocator<X>::value` as true for a T of user-defined type if T does not
70-
//! have a nested allocator_type but is nonetheless constructible using the specified Allocator where either:
71-
//! the first argument of a constructor has type `allocator_arg_t` and the second argument has type `Allocator` or
72-
//! the last argument of a constructor has type `Allocator`.
67+
//! <b>Remark</b>: Automatically detects whether `T` has a nested `allocator_type` that is convertible from
68+
//! `AllocArg`. Meets the BinaryTypeTrait requirements ([meta.rqmts] 20.4.1). This trait is used to signal if
69+
//! type `T` supports <b>uses-allocator construction</b>.
70+
//!
71+
//! <b>uses-allocator construction</b> protocol specifies three conventions of passing an allocator argument
72+
//! `alloc_arg` of type `AllocArg` to a constructor of some type T in addition to an arbitrary number of arguments
73+
//! (specified as `args...` in this explanation):
74+
//!
75+
//! * If `T` does not use a compatible allocator (`uses_allocator<T, AllocArg>::value` is false), then `alloc_arg` is ignored
76+
//! and `T` is constructed as `T(args...)`
77+
//!
78+
//! * Otherwise, if `uses_allocator<T, AllocArg>::value` is true and `T` is constructible as `T(std::allocator_arg, alloc_arg, args...)`
79+
//! then uses-allocator construction uses this form.
80+
//!
81+
//! * Otherwise, if `T` is constructible as `T(args..., alloc_arg)`, then uses-allocator construction uses this form.
82+
//!
83+
//! * Otherwise, as an non-standard extension provided by Boost.Container, `alloc_arg` is ignored and `T(args...)` is called.
84+
//! (Note that this extension is provided to enhance backwards compatibility for types created without uses-allocator
85+
//! construction in mind that declare an `allocator_type` type but are not prepared to be built with an additional allocator argument)
7386
//!
74-
//! <b>Result</b>: `uses_allocator<T, Allocator>::value == true` if a type `T::allocator_type`
75-
//! exists and either `is_convertible<Allocator, T::allocator_type>::value != false` or `T::allocator_type`
87+
//! <b>Result</b>: `uses_allocator<T, AllocArg>::value == true` if a type `T::allocator_type`
88+
//! exists and either `is_convertible<AllocArg, T::allocator_type>::value == true` or `T::allocator_type`
7689
//! is an alias of `erased_type`. False otherwise.
77-
template <typename T, typename Allocator>
90+
//!
91+
//! <b>Note</b>: A program may specialize this type to define `uses_allocator<X>::value` as true for a `T` of user-defined type
92+
//! if `T` does not have a nested `allocator_type` but is nonetheless constructible using the specified `AllocArg`
93+
//! where either: the first argument of a constructor has type `allocator_arg_t` and the second argument has type `AllocArg`
94+
//! or the last argument of a constructor has type `AllocArg`.
95+
96+
template <typename T, typename AllocArg>
7897
struct uses_allocator
7998
#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED
80-
: dtl::uses_allocator_imp<T, Allocator>
99+
: dtl::uses_allocator_imp<T, AllocArg>
81100
#endif //BOOST_CONTAINER_DOXYGEN_INVOKED
82101
{};
83102

103+
#if !defined(BOOST_NO_CXX14_VARIABLE_TEMPLATES)
104+
105+
template< class T, class AllocArg >
106+
BOOST_CONSTEXPR bool uses_allocator_v = uses_allocator<T, AllocArg>::value;
107+
108+
#endif //BOOST_NO_CXX14_VARIABLE_TEMPLATES
109+
84110
}} //namespace boost::container
85111

86112
#endif //BOOST_CONTAINER_USES_ALLOCATOR_HPP
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//////////////////////////////////////////////////////////////////////////////
2+
//
3+
// (C) Copyright Ion Gaztanaga 2025-2025. Distributed under the Boost
4+
// Software License, Version 1.0. (See accompanying file
5+
// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6+
//
7+
// See http://www.boost.org/libs/container for documentation.
8+
//
9+
//////////////////////////////////////////////////////////////////////////////
10+
11+
#ifndef BOOST_CONTAINER_USES_ALLOCATOR_CONSTRUCTION_HPP
12+
#define BOOST_CONTAINER_USES_ALLOCATOR_CONSTRUCTION_HPP
13+
14+
#ifndef BOOST_CONFIG_HPP
15+
# include <boost/config.hpp>
16+
#endif
17+
18+
#if defined(BOOST_HAS_PRAGMA_ONCE)
19+
# pragma once
20+
#endif
21+
22+
#include <boost/container/detail/config_begin.hpp>
23+
#include <boost/container/detail/workaround.hpp>
24+
#include <boost/container/detail/dispatch_uses_allocator.hpp>
25+
#include <boost/move/utility_core.hpp>
26+
#if defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES)
27+
#include <boost/move/detail/fwd_macros.hpp>
28+
#endif
29+
30+
namespace boost {
31+
namespace container {
32+
33+
#if defined(BOOST_CONTAINER_DOXYGEN_INVOKED) || !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES)
34+
35+
//! <b>Effects</b>: Creates an object of the given type T by means of uses-allocator
36+
//! construction (see `uses_allocator`) at the uninitialized memory, where:
37+
//!
38+
//! * `p` is the memory location where the object will be placed
39+
//! * `alloc_arg` is the allocator argument whose type AllocArg will be used to evaluate uses_allocator<T, AllocArg>::value
40+
//! * `args` are the arguments to pass to T's constructor.
41+
//!
42+
//! <b>Returns</b>: Pointer to the newly-created object of type T
43+
//!
44+
//! <b>Throws</b>: Any exception thrown by the constructor of T.
45+
template< class T, class AllocArg, class... Args >
46+
T* uninitialized_construct_using_allocator(T* p, BOOST_FWD_REF(AllocArg) alloc_arg, BOOST_FWD_REF(Args)... args)
47+
{
48+
boost::container::dtl::allocator_traits_dummy<T> atd;
49+
boost::container::dtl::dispatch_uses_allocator
50+
(atd, boost::forward<AllocArg>(alloc_arg), p, boost::forward<Args>(args)...);
51+
return p;
52+
}
53+
54+
#else //BOOST_NO_CXX11_VARIADIC_TEMPLATES
55+
56+
#define BOOST_CONTAINER_USES_ALLOCATOR_CONSTRUCTION_CODE(N) \
57+
template < typename T, typename AllocArg BOOST_MOVE_I##N BOOST_MOVE_CLASS##N >\
58+
inline T* uninitialized_construct_using_allocator\
59+
(T* p, BOOST_FWD_REF(AllocArg) alloc_arg BOOST_MOVE_I##N BOOST_MOVE_UREF##N)\
60+
{\
61+
boost::container::dtl::allocator_traits_dummy<T> atd;\
62+
boost::container::dtl::dispatch_uses_allocator\
63+
(atd, boost::forward<AllocArg>(alloc_arg), p BOOST_MOVE_I##N BOOST_MOVE_FWD##N);\
64+
return p;\
65+
}\
66+
//
67+
BOOST_MOVE_ITERATE_0TO9(BOOST_CONTAINER_USES_ALLOCATOR_CONSTRUCTION_CODE)
68+
#undef BOOST_CONTAINER_USES_ALLOCATOR_CONSTRUCTION_CODE
69+
70+
#endif //BOOST_NO_CXX11_VARIADIC_TEMPLATES
71+
72+
}} //namespace boost::container
73+
74+
#endif //BOOST_CONTAINER_USES_ALLOCATOR_CONSTRUCTION_HPP
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
//////////////////////////////////////////////////////////////////////////////
2+
//
3+
// (C) Copyright Ion Gaztanaga 2025-2025. Distributed under the Boost
4+
// Software License, Version 1.0. (See accompanying file
5+
// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6+
//
7+
// See http://www.boost.org/libs/container for documentation.
8+
//
9+
//////////////////////////////////////////////////////////////////////////////
10+
#include <boost/container/uses_allocator_construction.hpp>
11+
#include <boost/container/detail/type_traits.hpp>
12+
#include <boost/core/lightweight_test.hpp>
13+
14+
typedef int arg1_t;
15+
typedef void * arg2_t;
16+
typedef void (*arg3_t) (int);
17+
18+
typedef void *(*non_alloc_arg_t) (double*);
19+
typedef int alloc_arg_t;
20+
21+
using namespace boost::container;
22+
23+
struct not_uses_allocator
24+
{};
25+
26+
struct uses_allocator_and_not_convertible_arg
27+
{
28+
bool allocator_called;
29+
unsigned args_called;
30+
typedef void (*allocator_type) (uses_allocator_and_not_convertible_arg);
31+
32+
uses_allocator_and_not_convertible_arg()
33+
: allocator_called(false), args_called(0)
34+
{}
35+
36+
explicit uses_allocator_and_not_convertible_arg(allocator_type)
37+
: allocator_called(true), args_called(0)
38+
{}
39+
40+
explicit uses_allocator_and_not_convertible_arg(arg1_t, arg2_t, arg3_t, allocator_type)
41+
: allocator_called(true), args_called(3)
42+
{}
43+
44+
explicit uses_allocator_and_not_convertible_arg(arg1_t, arg2_t, arg3_t)
45+
: allocator_called(false), args_called(3)
46+
{}
47+
48+
explicit uses_allocator_and_not_convertible_arg(allocator_arg_t, allocator_type, arg1_t, arg2_t)
49+
: allocator_called(true), args_called(2)
50+
{}
51+
52+
explicit uses_allocator_and_not_convertible_arg(arg1_t, arg2_t)
53+
: allocator_called(false), args_called(2)
54+
{}
55+
};
56+
57+
struct uses_allocator_and_convertible_arg
58+
{
59+
bool allocator_called;
60+
unsigned args_called;
61+
typedef long allocator_type;
62+
63+
uses_allocator_and_convertible_arg()
64+
: allocator_called(false), args_called(0)
65+
{}
66+
67+
explicit uses_allocator_and_convertible_arg(allocator_type)
68+
: allocator_called(true), args_called(0)
69+
{}
70+
71+
explicit uses_allocator_and_convertible_arg(arg1_t, arg2_t, arg3_t, allocator_type)
72+
: allocator_called(true), args_called(3)
73+
{}
74+
75+
explicit uses_allocator_and_convertible_arg(arg1_t, arg2_t, arg3_t)
76+
: allocator_called(false), args_called(3)
77+
{}
78+
79+
explicit uses_allocator_and_convertible_arg(allocator_arg_t, allocator_type, arg1_t, arg2_t)
80+
: allocator_called(true), args_called(2)
81+
{}
82+
83+
explicit uses_allocator_and_convertible_arg(arg1_t, arg2_t)
84+
: allocator_called(false), args_called(2)
85+
{}
86+
};
87+
88+
struct uses_erased_type_allocator
89+
{
90+
bool allocator_called;
91+
unsigned args_called;
92+
93+
typedef boost::container::erased_type allocator_type;
94+
typedef long constructible_from_int_t;
95+
96+
uses_erased_type_allocator()
97+
: allocator_called(false), args_called(0)
98+
{}
99+
100+
explicit uses_erased_type_allocator(int)
101+
: allocator_called(true), args_called(0)
102+
{}
103+
104+
explicit uses_erased_type_allocator(arg1_t, arg2_t, arg3_t, constructible_from_int_t)
105+
: allocator_called(true), args_called(3)
106+
{}
107+
108+
explicit uses_erased_type_allocator(arg1_t, arg2_t, arg3_t)
109+
: allocator_called(false), args_called(3)
110+
{}
111+
112+
explicit uses_erased_type_allocator(allocator_arg_t, constructible_from_int_t, arg1_t, arg2_t)
113+
: allocator_called(true), args_called(2)
114+
{}
115+
116+
explicit uses_erased_type_allocator(arg1_t, arg2_t)
117+
: allocator_called(false), args_called(2)
118+
{}
119+
};
120+
121+
typedef boost::container::dtl::aligned_storage<sizeof(uses_allocator_and_not_convertible_arg)>::type storage_t;
122+
123+
//Make sure aligned_storage will be big enough
124+
BOOST_CONTAINER_STATIC_ASSERT( sizeof(storage_t) >= sizeof(uses_allocator_and_not_convertible_arg) );
125+
BOOST_CONTAINER_STATIC_ASSERT( sizeof(storage_t) >= sizeof(not_uses_allocator) );
126+
BOOST_CONTAINER_STATIC_ASSERT( sizeof(storage_t) >= sizeof(uses_allocator_and_convertible_arg) );
127+
BOOST_CONTAINER_STATIC_ASSERT( sizeof(storage_t) >= sizeof(uses_erased_type_allocator) );
128+
129+
int main()
130+
{
131+
storage_t storage;
132+
void *const st_ptr = static_cast<void*>(storage.data);
133+
134+
not_uses_allocator *nua_ptr = reinterpret_cast<not_uses_allocator*>(st_ptr);
135+
uses_allocator_and_not_convertible_arg *uanci_ptr = reinterpret_cast<uses_allocator_and_not_convertible_arg*>(st_ptr);
136+
uses_allocator_and_convertible_arg *uaci_ptr = reinterpret_cast<uses_allocator_and_convertible_arg*>(st_ptr);
137+
uses_erased_type_allocator *ueta_ptr = reinterpret_cast<uses_erased_type_allocator*>(st_ptr);
138+
139+
//not_uses_allocator
140+
nua_ptr = uninitialized_construct_using_allocator(nua_ptr, alloc_arg_t());
141+
142+
//uses_allocator_and_convertible_arg
143+
uanci_ptr = uninitialized_construct_using_allocator(uanci_ptr, alloc_arg_t());
144+
BOOST_TEST(uanci_ptr->allocator_called == false);
145+
BOOST_TEST(uanci_ptr->args_called == 0u);
146+
147+
uanci_ptr = uninitialized_construct_using_allocator(uanci_ptr, alloc_arg_t());
148+
BOOST_TEST(uanci_ptr->allocator_called == false);
149+
BOOST_TEST(uanci_ptr->args_called == 0u);
150+
151+
uanci_ptr = uninitialized_construct_using_allocator(uanci_ptr, alloc_arg_t(), arg1_t(), arg2_t(), arg3_t());
152+
BOOST_TEST(uanci_ptr->allocator_called == false);
153+
BOOST_TEST(uanci_ptr->args_called == 3u);
154+
155+
uanci_ptr = uninitialized_construct_using_allocator(uanci_ptr, alloc_arg_t(), arg1_t(), arg2_t());
156+
BOOST_TEST(uanci_ptr->allocator_called == false);
157+
BOOST_TEST(uanci_ptr->args_called == 2u);
158+
159+
uanci_ptr = uninitialized_construct_using_allocator(uanci_ptr, non_alloc_arg_t(), arg1_t(), arg2_t(), arg3_t());
160+
BOOST_TEST(uanci_ptr->allocator_called == false);
161+
BOOST_TEST(uanci_ptr->args_called == 3u);
162+
163+
uanci_ptr = uninitialized_construct_using_allocator(uanci_ptr, non_alloc_arg_t(), arg1_t(), arg2_t());
164+
BOOST_TEST(uanci_ptr->allocator_called == false);
165+
BOOST_TEST(uanci_ptr->args_called == 2u);
166+
167+
//uses_allocator_and_not_convertible_arg
168+
uaci_ptr = uninitialized_construct_using_allocator(uaci_ptr, alloc_arg_t());
169+
BOOST_TEST(uaci_ptr->allocator_called == true);
170+
BOOST_TEST(uaci_ptr->args_called == 0u);
171+
172+
uaci_ptr = uninitialized_construct_using_allocator(uaci_ptr, alloc_arg_t(), arg1_t(), arg2_t(), arg3_t());
173+
BOOST_TEST(uaci_ptr->allocator_called == true);
174+
BOOST_TEST(uaci_ptr->args_called == 3u);
175+
176+
uaci_ptr = uninitialized_construct_using_allocator(uaci_ptr, alloc_arg_t(), arg1_t(), arg2_t());
177+
BOOST_TEST(uaci_ptr->allocator_called == true);
178+
BOOST_TEST(uaci_ptr->args_called == 2u);
179+
180+
//uses_erased_type_allocator
181+
ueta_ptr = uninitialized_construct_using_allocator(ueta_ptr, alloc_arg_t());
182+
BOOST_TEST(ueta_ptr->allocator_called == true);
183+
BOOST_TEST(ueta_ptr->args_called == 0u);
184+
185+
ueta_ptr = uninitialized_construct_using_allocator(ueta_ptr, alloc_arg_t(), arg1_t(), arg2_t(), arg3_t());
186+
BOOST_TEST(ueta_ptr->allocator_called == true);
187+
BOOST_TEST(ueta_ptr->args_called == 3u);
188+
189+
ueta_ptr = uninitialized_construct_using_allocator(ueta_ptr, alloc_arg_t(), arg1_t(), arg2_t());
190+
BOOST_TEST(ueta_ptr->allocator_called == true);
191+
BOOST_TEST(ueta_ptr->args_called == 2u);
192+
193+
ueta_ptr = uninitialized_construct_using_allocator(ueta_ptr, non_alloc_arg_t(), arg1_t(), arg2_t());
194+
BOOST_TEST(ueta_ptr->allocator_called == false);
195+
BOOST_TEST(ueta_ptr->args_called == 2u);
196+
197+
ueta_ptr = uninitialized_construct_using_allocator(ueta_ptr, non_alloc_arg_t(), arg1_t(), arg2_t(), arg3_t());
198+
BOOST_TEST(ueta_ptr->allocator_called == false);
199+
BOOST_TEST(ueta_ptr->args_called == 3u);
200+
201+
return boost::report_errors();
202+
}

0 commit comments

Comments
 (0)