Implement C++20's uninitialized_construct_using_allocator

This commit is contained in:
Ion Gaztañaga
2025-12-17 14:39:45 +01:00
parent 3183fc28cc
commit f26b0ff58c
4 changed files with 318 additions and 15 deletions

View File

@@ -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"]].

View File

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

View 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

View 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();
}