diff --git a/doc/container.qbk b/doc/container.qbk index ff4b86b..cf0773a 100644 --- a/doc/container.qbk +++ b/doc/container.qbk @@ -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`'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"]]. diff --git a/include/boost/container/uses_allocator.hpp b/include/boost/container/uses_allocator.hpp index 26ae514..89fb7c4 100644 --- a/include/boost/container/uses_allocator.hpp +++ b/include/boost/container/uses_allocator.hpp @@ -21,17 +21,17 @@ namespace container { namespace dtl { -template +template 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 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 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(alloc)) == sizeof(yes_type); @@ -64,23 +64,49 @@ struct constructible_with_allocator_suffix #endif //#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED -//! Remark: 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::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`. +//! Remark: 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 uses-allocator construction. +//! +//! uses-allocator construction 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::value` is false), then `alloc_arg` is ignored +//! and `T` is constructed as `T(args...)` +//! +//! * Otherwise, if `uses_allocator::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) //! -//! Result: `uses_allocator::value == true` if a type `T::allocator_type` -//! exists and either `is_convertible::value != false` or `T::allocator_type` +//! Result: `uses_allocator::value == true` if a type `T::allocator_type` +//! exists and either `is_convertible::value == true` or `T::allocator_type` //! is an alias of `erased_type`. False otherwise. -template +//! +//! Note: A program may specialize this type to define `uses_allocator::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 struct uses_allocator #ifndef BOOST_CONTAINER_DOXYGEN_INVOKED - : dtl::uses_allocator_imp + : dtl::uses_allocator_imp #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::value; + +#endif //BOOST_NO_CXX14_VARIABLE_TEMPLATES + }} //namespace boost::container #endif //BOOST_CONTAINER_USES_ALLOCATOR_HPP diff --git a/include/boost/container/uses_allocator_construction.hpp b/include/boost/container/uses_allocator_construction.hpp new file mode 100644 index 0000000..54e7bb6 --- /dev/null +++ b/include/boost/container/uses_allocator_construction.hpp @@ -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 +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +#include +#include +#include +#include +#if defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) +#include +#endif + +namespace boost { +namespace container { + +#if defined(BOOST_CONTAINER_DOXYGEN_INVOKED) || !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + +//! Effects: 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::value +//! * `args` are the arguments to pass to T's constructor. +//! +//! Returns: Pointer to the newly-created object of type T +//! +//! Throws: 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 atd; + boost::container::dtl::dispatch_uses_allocator + (atd, boost::forward(alloc_arg), p, boost::forward(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 atd;\ + boost::container::dtl::dispatch_uses_allocator\ + (atd, boost::forward(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 diff --git a/test/uses_allocator_construction_test.cpp b/test/uses_allocator_construction_test.cpp new file mode 100644 index 0000000..bbb5475 --- /dev/null +++ b/test/uses_allocator_construction_test.cpp @@ -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 +#include +#include + +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::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(storage.data); + + not_uses_allocator *nua_ptr = reinterpret_cast(st_ptr); + uses_allocator_and_not_convertible_arg *uanci_ptr = reinterpret_cast(st_ptr); + uses_allocator_and_convertible_arg *uaci_ptr = reinterpret_cast(st_ptr); + uses_erased_type_allocator *ueta_ptr = reinterpret_cast(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(); +}