mirror of
https://github.com/boostorg/container.git
synced 2026-01-19 04:02:17 +00:00
Implement C++20's uninitialized_construct_using_allocator
This commit is contained in:
@@ -1438,6 +1438,7 @@ use [*Boost.Container]? There are several reasons for that:
|
||||
|
||||
* Implemented heterogeneous overloads from C++23 ([@http://wg21.link/p2077r3 P2077]) and C++26 ([@http://wg21.link/P2363 P2363]).
|
||||
* In C++20 compilers, `static_vector<T>`'s destructor is now trivial if `T` is trivial.
|
||||
* Implemented C++20's [classref boost::container::uninitialized_construct_using_allocator uninitialized_construct_using_allocator].
|
||||
* Fixed bugs/issues:
|
||||
* [@https://github.com/boostorg/container/issues/323 GitHub #323: ['"flat_tree::try_emplace UB"]].
|
||||
|
||||
|
||||
@@ -21,17 +21,17 @@ namespace container {
|
||||
|
||||
namespace dtl {
|
||||
|
||||
template<typename T, typename Allocator>
|
||||
template<typename T, typename AllocArg>
|
||||
struct uses_allocator_imp
|
||||
{
|
||||
// Use SFINAE (Substitution Failure Is Not An Error) to detect the
|
||||
// presence of an 'allocator_type' nested type convertilble from Allocator.
|
||||
// presence of an 'allocator_type' nested type convertilble from AllocArg.
|
||||
private:
|
||||
typedef char yes_type;
|
||||
struct no_type{ char dummy[2]; };
|
||||
|
||||
// Match this function if T::allocator_type exists and is
|
||||
// implicitly convertible from Allocator
|
||||
// implicitly convertible from `AllocArg`
|
||||
template <class U>
|
||||
static yes_type test(typename U::allocator_type);
|
||||
|
||||
@@ -43,10 +43,10 @@ struct uses_allocator_imp
|
||||
>::type test(const V&);
|
||||
|
||||
// Match this function if TypeT::allocator_type does not exist or is
|
||||
// not convertible from Allocator.
|
||||
// not convertible from `AllocArg`.
|
||||
template <typename U>
|
||||
static no_type test(...);
|
||||
static Allocator alloc; // Declared but not defined
|
||||
static AllocArg alloc; // Declared but not defined
|
||||
|
||||
public:
|
||||
BOOST_STATIC_CONSTEXPR bool value = sizeof(test<T>(alloc)) == sizeof(yes_type);
|
||||
@@ -64,23 +64,49 @@ struct constructible_with_allocator_suffix
|
||||
|
||||
#endif //#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED
|
||||
|
||||
//! <b>Remark</b>: Automatically detects whether T has a nested allocator_type that is convertible from
|
||||
//! Allocator. Meets the BinaryTypeTrait requirements ([meta.rqmts] 20.4.1). A program may
|
||||
//! specialize this type to define `uses_allocator<X>::value` as true for a T of user-defined type if T does not
|
||||
//! have a nested allocator_type but is nonetheless constructible using the specified Allocator where either:
|
||||
//! the first argument of a constructor has type `allocator_arg_t` and the second argument has type `Allocator` or
|
||||
//! the last argument of a constructor has type `Allocator`.
|
||||
//! <b>Remark</b>: Automatically detects whether `T` has a nested `allocator_type` that is convertible from
|
||||
//! `AllocArg`. Meets the BinaryTypeTrait requirements ([meta.rqmts] 20.4.1). This trait is used to signal if
|
||||
//! type `T` supports <b>uses-allocator construction</b>.
|
||||
//!
|
||||
//! <b>uses-allocator construction</b> protocol specifies three conventions of passing an allocator argument
|
||||
//! `alloc_arg` of type `AllocArg` to a constructor of some type T in addition to an arbitrary number of arguments
|
||||
//! (specified as `args...` in this explanation):
|
||||
//!
|
||||
//! * If `T` does not use a compatible allocator (`uses_allocator<T, AllocArg>::value` is false), then `alloc_arg` is ignored
|
||||
//! and `T` is constructed as `T(args...)`
|
||||
//!
|
||||
//! * Otherwise, if `uses_allocator<T, AllocArg>::value` is true and `T` is constructible as `T(std::allocator_arg, alloc_arg, args...)`
|
||||
//! then uses-allocator construction uses this form.
|
||||
//!
|
||||
//! * Otherwise, if `T` is constructible as `T(args..., alloc_arg)`, then uses-allocator construction uses this form.
|
||||
//!
|
||||
//! * Otherwise, as an non-standard extension provided by Boost.Container, `alloc_arg` is ignored and `T(args...)` is called.
|
||||
//! (Note that this extension is provided to enhance backwards compatibility for types created without uses-allocator
|
||||
//! construction in mind that declare an `allocator_type` type but are not prepared to be built with an additional allocator argument)
|
||||
//!
|
||||
//! <b>Result</b>: `uses_allocator<T, Allocator>::value == true` if a type `T::allocator_type`
|
||||
//! exists and either `is_convertible<Allocator, T::allocator_type>::value != false` or `T::allocator_type`
|
||||
//! <b>Result</b>: `uses_allocator<T, AllocArg>::value == true` if a type `T::allocator_type`
|
||||
//! exists and either `is_convertible<AllocArg, T::allocator_type>::value == true` or `T::allocator_type`
|
||||
//! is an alias of `erased_type`. False otherwise.
|
||||
template <typename T, typename Allocator>
|
||||
//!
|
||||
//! <b>Note</b>: A program may specialize this type to define `uses_allocator<X>::value` as true for a `T` of user-defined type
|
||||
//! if `T` does not have a nested `allocator_type` but is nonetheless constructible using the specified `AllocArg`
|
||||
//! where either: the first argument of a constructor has type `allocator_arg_t` and the second argument has type `AllocArg`
|
||||
//! or the last argument of a constructor has type `AllocArg`.
|
||||
|
||||
template <typename T, typename AllocArg>
|
||||
struct uses_allocator
|
||||
#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED
|
||||
: dtl::uses_allocator_imp<T, Allocator>
|
||||
: dtl::uses_allocator_imp<T, AllocArg>
|
||||
#endif //BOOST_CONTAINER_DOXYGEN_INVOKED
|
||||
{};
|
||||
|
||||
#if !defined(BOOST_NO_CXX14_VARIABLE_TEMPLATES)
|
||||
|
||||
template< class T, class AllocArg >
|
||||
BOOST_CONSTEXPR bool uses_allocator_v = uses_allocator<T, AllocArg>::value;
|
||||
|
||||
#endif //BOOST_NO_CXX14_VARIABLE_TEMPLATES
|
||||
|
||||
}} //namespace boost::container
|
||||
|
||||
#endif //BOOST_CONTAINER_USES_ALLOCATOR_HPP
|
||||
|
||||
74
include/boost/container/uses_allocator_construction.hpp
Normal file
74
include/boost/container/uses_allocator_construction.hpp
Normal file
@@ -0,0 +1,74 @@
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// (C) Copyright Ion Gaztanaga 2025-2025. 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 http://www.boost.org/libs/container for documentation.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef BOOST_CONTAINER_USES_ALLOCATOR_CONSTRUCTION_HPP
|
||||
#define BOOST_CONTAINER_USES_ALLOCATOR_CONSTRUCTION_HPP
|
||||
|
||||
#ifndef BOOST_CONFIG_HPP
|
||||
# include <boost/config.hpp>
|
||||
#endif
|
||||
|
||||
#if defined(BOOST_HAS_PRAGMA_ONCE)
|
||||
# pragma once
|
||||
#endif
|
||||
|
||||
#include <boost/container/detail/config_begin.hpp>
|
||||
#include <boost/container/detail/workaround.hpp>
|
||||
#include <boost/container/detail/dispatch_uses_allocator.hpp>
|
||||
#include <boost/move/utility_core.hpp>
|
||||
#if defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES)
|
||||
#include <boost/move/detail/fwd_macros.hpp>
|
||||
#endif
|
||||
|
||||
namespace boost {
|
||||
namespace container {
|
||||
|
||||
#if defined(BOOST_CONTAINER_DOXYGEN_INVOKED) || !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES)
|
||||
|
||||
//! <b>Effects</b>: Creates an object of the given type T by means of uses-allocator
|
||||
//! construction (see `uses_allocator`) at the uninitialized memory, where:
|
||||
//!
|
||||
//! * `p` is the memory location where the object will be placed
|
||||
//! * `alloc_arg` is the allocator argument whose type AllocArg will be used to evaluate uses_allocator<T, AllocArg>::value
|
||||
//! * `args` are the arguments to pass to T's constructor.
|
||||
//!
|
||||
//! <b>Returns</b>: Pointer to the newly-created object of type T
|
||||
//!
|
||||
//! <b>Throws</b>: Any exception thrown by the constructor of T.
|
||||
template< class T, class AllocArg, class... Args >
|
||||
T* uninitialized_construct_using_allocator(T* p, BOOST_FWD_REF(AllocArg) alloc_arg, BOOST_FWD_REF(Args)... args)
|
||||
{
|
||||
boost::container::dtl::allocator_traits_dummy<T> atd;
|
||||
boost::container::dtl::dispatch_uses_allocator
|
||||
(atd, boost::forward<AllocArg>(alloc_arg), p, boost::forward<Args>(args)...);
|
||||
return p;
|
||||
}
|
||||
|
||||
#else //BOOST_NO_CXX11_VARIADIC_TEMPLATES
|
||||
|
||||
#define BOOST_CONTAINER_USES_ALLOCATOR_CONSTRUCTION_CODE(N) \
|
||||
template < typename T, typename AllocArg BOOST_MOVE_I##N BOOST_MOVE_CLASS##N >\
|
||||
inline T* uninitialized_construct_using_allocator\
|
||||
(T* p, BOOST_FWD_REF(AllocArg) alloc_arg BOOST_MOVE_I##N BOOST_MOVE_UREF##N)\
|
||||
{\
|
||||
boost::container::dtl::allocator_traits_dummy<T> atd;\
|
||||
boost::container::dtl::dispatch_uses_allocator\
|
||||
(atd, boost::forward<AllocArg>(alloc_arg), p BOOST_MOVE_I##N BOOST_MOVE_FWD##N);\
|
||||
return p;\
|
||||
}\
|
||||
//
|
||||
BOOST_MOVE_ITERATE_0TO9(BOOST_CONTAINER_USES_ALLOCATOR_CONSTRUCTION_CODE)
|
||||
#undef BOOST_CONTAINER_USES_ALLOCATOR_CONSTRUCTION_CODE
|
||||
|
||||
#endif //BOOST_NO_CXX11_VARIADIC_TEMPLATES
|
||||
|
||||
}} //namespace boost::container
|
||||
|
||||
#endif //BOOST_CONTAINER_USES_ALLOCATOR_CONSTRUCTION_HPP
|
||||
202
test/uses_allocator_construction_test.cpp
Normal file
202
test/uses_allocator_construction_test.cpp
Normal file
@@ -0,0 +1,202 @@
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// (C) Copyright Ion Gaztanaga 2025-2025. 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 http://www.boost.org/libs/container for documentation.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
#include <boost/container/uses_allocator_construction.hpp>
|
||||
#include <boost/container/detail/type_traits.hpp>
|
||||
#include <boost/core/lightweight_test.hpp>
|
||||
|
||||
typedef int arg1_t;
|
||||
typedef void * arg2_t;
|
||||
typedef void (*arg3_t) (int);
|
||||
|
||||
typedef void *(*non_alloc_arg_t) (double*);
|
||||
typedef int alloc_arg_t;
|
||||
|
||||
using namespace boost::container;
|
||||
|
||||
struct not_uses_allocator
|
||||
{};
|
||||
|
||||
struct uses_allocator_and_not_convertible_arg
|
||||
{
|
||||
bool allocator_called;
|
||||
unsigned args_called;
|
||||
typedef void (*allocator_type) (uses_allocator_and_not_convertible_arg);
|
||||
|
||||
uses_allocator_and_not_convertible_arg()
|
||||
: allocator_called(false), args_called(0)
|
||||
{}
|
||||
|
||||
explicit uses_allocator_and_not_convertible_arg(allocator_type)
|
||||
: allocator_called(true), args_called(0)
|
||||
{}
|
||||
|
||||
explicit uses_allocator_and_not_convertible_arg(arg1_t, arg2_t, arg3_t, allocator_type)
|
||||
: allocator_called(true), args_called(3)
|
||||
{}
|
||||
|
||||
explicit uses_allocator_and_not_convertible_arg(arg1_t, arg2_t, arg3_t)
|
||||
: allocator_called(false), args_called(3)
|
||||
{}
|
||||
|
||||
explicit uses_allocator_and_not_convertible_arg(allocator_arg_t, allocator_type, arg1_t, arg2_t)
|
||||
: allocator_called(true), args_called(2)
|
||||
{}
|
||||
|
||||
explicit uses_allocator_and_not_convertible_arg(arg1_t, arg2_t)
|
||||
: allocator_called(false), args_called(2)
|
||||
{}
|
||||
};
|
||||
|
||||
struct uses_allocator_and_convertible_arg
|
||||
{
|
||||
bool allocator_called;
|
||||
unsigned args_called;
|
||||
typedef long allocator_type;
|
||||
|
||||
uses_allocator_and_convertible_arg()
|
||||
: allocator_called(false), args_called(0)
|
||||
{}
|
||||
|
||||
explicit uses_allocator_and_convertible_arg(allocator_type)
|
||||
: allocator_called(true), args_called(0)
|
||||
{}
|
||||
|
||||
explicit uses_allocator_and_convertible_arg(arg1_t, arg2_t, arg3_t, allocator_type)
|
||||
: allocator_called(true), args_called(3)
|
||||
{}
|
||||
|
||||
explicit uses_allocator_and_convertible_arg(arg1_t, arg2_t, arg3_t)
|
||||
: allocator_called(false), args_called(3)
|
||||
{}
|
||||
|
||||
explicit uses_allocator_and_convertible_arg(allocator_arg_t, allocator_type, arg1_t, arg2_t)
|
||||
: allocator_called(true), args_called(2)
|
||||
{}
|
||||
|
||||
explicit uses_allocator_and_convertible_arg(arg1_t, arg2_t)
|
||||
: allocator_called(false), args_called(2)
|
||||
{}
|
||||
};
|
||||
|
||||
struct uses_erased_type_allocator
|
||||
{
|
||||
bool allocator_called;
|
||||
unsigned args_called;
|
||||
|
||||
typedef boost::container::erased_type allocator_type;
|
||||
typedef long constructible_from_int_t;
|
||||
|
||||
uses_erased_type_allocator()
|
||||
: allocator_called(false), args_called(0)
|
||||
{}
|
||||
|
||||
explicit uses_erased_type_allocator(int)
|
||||
: allocator_called(true), args_called(0)
|
||||
{}
|
||||
|
||||
explicit uses_erased_type_allocator(arg1_t, arg2_t, arg3_t, constructible_from_int_t)
|
||||
: allocator_called(true), args_called(3)
|
||||
{}
|
||||
|
||||
explicit uses_erased_type_allocator(arg1_t, arg2_t, arg3_t)
|
||||
: allocator_called(false), args_called(3)
|
||||
{}
|
||||
|
||||
explicit uses_erased_type_allocator(allocator_arg_t, constructible_from_int_t, arg1_t, arg2_t)
|
||||
: allocator_called(true), args_called(2)
|
||||
{}
|
||||
|
||||
explicit uses_erased_type_allocator(arg1_t, arg2_t)
|
||||
: allocator_called(false), args_called(2)
|
||||
{}
|
||||
};
|
||||
|
||||
typedef boost::container::dtl::aligned_storage<sizeof(uses_allocator_and_not_convertible_arg)>::type storage_t;
|
||||
|
||||
//Make sure aligned_storage will be big enough
|
||||
BOOST_CONTAINER_STATIC_ASSERT( sizeof(storage_t) >= sizeof(uses_allocator_and_not_convertible_arg) );
|
||||
BOOST_CONTAINER_STATIC_ASSERT( sizeof(storage_t) >= sizeof(not_uses_allocator) );
|
||||
BOOST_CONTAINER_STATIC_ASSERT( sizeof(storage_t) >= sizeof(uses_allocator_and_convertible_arg) );
|
||||
BOOST_CONTAINER_STATIC_ASSERT( sizeof(storage_t) >= sizeof(uses_erased_type_allocator) );
|
||||
|
||||
int main()
|
||||
{
|
||||
storage_t storage;
|
||||
void *const st_ptr = static_cast<void*>(storage.data);
|
||||
|
||||
not_uses_allocator *nua_ptr = reinterpret_cast<not_uses_allocator*>(st_ptr);
|
||||
uses_allocator_and_not_convertible_arg *uanci_ptr = reinterpret_cast<uses_allocator_and_not_convertible_arg*>(st_ptr);
|
||||
uses_allocator_and_convertible_arg *uaci_ptr = reinterpret_cast<uses_allocator_and_convertible_arg*>(st_ptr);
|
||||
uses_erased_type_allocator *ueta_ptr = reinterpret_cast<uses_erased_type_allocator*>(st_ptr);
|
||||
|
||||
//not_uses_allocator
|
||||
nua_ptr = uninitialized_construct_using_allocator(nua_ptr, alloc_arg_t());
|
||||
|
||||
//uses_allocator_and_convertible_arg
|
||||
uanci_ptr = uninitialized_construct_using_allocator(uanci_ptr, alloc_arg_t());
|
||||
BOOST_TEST(uanci_ptr->allocator_called == false);
|
||||
BOOST_TEST(uanci_ptr->args_called == 0u);
|
||||
|
||||
uanci_ptr = uninitialized_construct_using_allocator(uanci_ptr, alloc_arg_t());
|
||||
BOOST_TEST(uanci_ptr->allocator_called == false);
|
||||
BOOST_TEST(uanci_ptr->args_called == 0u);
|
||||
|
||||
uanci_ptr = uninitialized_construct_using_allocator(uanci_ptr, alloc_arg_t(), arg1_t(), arg2_t(), arg3_t());
|
||||
BOOST_TEST(uanci_ptr->allocator_called == false);
|
||||
BOOST_TEST(uanci_ptr->args_called == 3u);
|
||||
|
||||
uanci_ptr = uninitialized_construct_using_allocator(uanci_ptr, alloc_arg_t(), arg1_t(), arg2_t());
|
||||
BOOST_TEST(uanci_ptr->allocator_called == false);
|
||||
BOOST_TEST(uanci_ptr->args_called == 2u);
|
||||
|
||||
uanci_ptr = uninitialized_construct_using_allocator(uanci_ptr, non_alloc_arg_t(), arg1_t(), arg2_t(), arg3_t());
|
||||
BOOST_TEST(uanci_ptr->allocator_called == false);
|
||||
BOOST_TEST(uanci_ptr->args_called == 3u);
|
||||
|
||||
uanci_ptr = uninitialized_construct_using_allocator(uanci_ptr, non_alloc_arg_t(), arg1_t(), arg2_t());
|
||||
BOOST_TEST(uanci_ptr->allocator_called == false);
|
||||
BOOST_TEST(uanci_ptr->args_called == 2u);
|
||||
|
||||
//uses_allocator_and_not_convertible_arg
|
||||
uaci_ptr = uninitialized_construct_using_allocator(uaci_ptr, alloc_arg_t());
|
||||
BOOST_TEST(uaci_ptr->allocator_called == true);
|
||||
BOOST_TEST(uaci_ptr->args_called == 0u);
|
||||
|
||||
uaci_ptr = uninitialized_construct_using_allocator(uaci_ptr, alloc_arg_t(), arg1_t(), arg2_t(), arg3_t());
|
||||
BOOST_TEST(uaci_ptr->allocator_called == true);
|
||||
BOOST_TEST(uaci_ptr->args_called == 3u);
|
||||
|
||||
uaci_ptr = uninitialized_construct_using_allocator(uaci_ptr, alloc_arg_t(), arg1_t(), arg2_t());
|
||||
BOOST_TEST(uaci_ptr->allocator_called == true);
|
||||
BOOST_TEST(uaci_ptr->args_called == 2u);
|
||||
|
||||
//uses_erased_type_allocator
|
||||
ueta_ptr = uninitialized_construct_using_allocator(ueta_ptr, alloc_arg_t());
|
||||
BOOST_TEST(ueta_ptr->allocator_called == true);
|
||||
BOOST_TEST(ueta_ptr->args_called == 0u);
|
||||
|
||||
ueta_ptr = uninitialized_construct_using_allocator(ueta_ptr, alloc_arg_t(), arg1_t(), arg2_t(), arg3_t());
|
||||
BOOST_TEST(ueta_ptr->allocator_called == true);
|
||||
BOOST_TEST(ueta_ptr->args_called == 3u);
|
||||
|
||||
ueta_ptr = uninitialized_construct_using_allocator(ueta_ptr, alloc_arg_t(), arg1_t(), arg2_t());
|
||||
BOOST_TEST(ueta_ptr->allocator_called == true);
|
||||
BOOST_TEST(ueta_ptr->args_called == 2u);
|
||||
|
||||
ueta_ptr = uninitialized_construct_using_allocator(ueta_ptr, non_alloc_arg_t(), arg1_t(), arg2_t());
|
||||
BOOST_TEST(ueta_ptr->allocator_called == false);
|
||||
BOOST_TEST(ueta_ptr->args_called == 2u);
|
||||
|
||||
ueta_ptr = uninitialized_construct_using_allocator(ueta_ptr, non_alloc_arg_t(), arg1_t(), arg2_t(), arg3_t());
|
||||
BOOST_TEST(ueta_ptr->allocator_called == false);
|
||||
BOOST_TEST(ueta_ptr->args_called == 3u);
|
||||
|
||||
return boost::report_errors();
|
||||
}
|
||||
Reference in New Issue
Block a user