| //===----------------------------------------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef USES_ALLOC_TYPES_HPP |
| #define USES_ALLOC_TYPES_HPP |
| |
| # include <memory> |
| # include <cassert> |
| #include <cstdlib> |
| |
| #include "test_macros.h" |
| #include "test_workarounds.h" |
| #include "type_id.h" |
| |
| // There are two forms of uses-allocator construction: |
| // (1) UA_AllocArg: 'T(allocator_arg_t, Alloc const&, Args&&...)' |
| // (2) UA_AllocLast: 'T(Args&&..., Alloc const&)' |
| // 'UA_None' represents non-uses allocator construction. |
| enum class UsesAllocatorType { |
| UA_None = 0, |
| UA_AllocArg = 2, |
| UA_AllocLast = 4 |
| }; |
| constexpr UsesAllocatorType UA_None = UsesAllocatorType::UA_None; |
| constexpr UsesAllocatorType UA_AllocArg = UsesAllocatorType::UA_AllocArg; |
| constexpr UsesAllocatorType UA_AllocLast = UsesAllocatorType::UA_AllocLast; |
| |
| inline const char* toString(UsesAllocatorType UA) { |
| switch (UA) { |
| case UA_None: |
| return "UA_None"; |
| case UA_AllocArg: |
| return "UA_AllocArg"; |
| case UA_AllocLast: |
| return "UA_AllocLast"; |
| default: |
| std::abort(); |
| } |
| } |
| |
| #define COMPARE_ALLOC_TYPE(LHS, RHS) CompareVerbose(#LHS, LHS, #RHS, RHS) |
| |
| inline bool CompareVerbose(const char* LHSString, UsesAllocatorType LHS, |
| const char* RHSString, UsesAllocatorType RHS) { |
| if (LHS == RHS) |
| return true; |
| std::printf("UsesAllocatorType's don't match:\n%s %s\n----------\n%s %s\n", |
| LHSString, toString(LHS), RHSString, toString(RHS)); |
| return false; |
| } |
| |
| template <class Alloc, std::size_t N> |
| class UsesAllocatorV1; |
| // Implements form (1) of uses-allocator construction from the specified |
| // 'Alloc' type and exactly 'N' additional arguments. It also provides |
| // non-uses allocator construction from 'N' arguments. This test type |
| // blows up when form (2) of uses-allocator is even considered. |
| |
| template <class Alloc, std::size_t N> |
| class UsesAllocatorV2; |
| // Implements form (2) of uses-allocator construction from the specified |
| // 'Alloc' type and exactly 'N' additional arguments. It also provides |
| // non-uses allocator construction from 'N' arguments. |
| |
| template <class Alloc, std::size_t N> |
| class UsesAllocatorV3; |
| // Implements both form (1) and (2) of uses-allocator construction from |
| // the specified 'Alloc' type and exactly 'N' additional arguments. It also |
| // provides non-uses allocator construction from 'N' arguments. |
| |
| template <class Alloc, std::size_t> |
| class NotUsesAllocator; |
| // Implements both form (1) and (2) of uses-allocator construction from |
| // the specified 'Alloc' type and exactly 'N' additional arguments. It also |
| // provides non-uses allocator construction from 'N' arguments. However |
| // 'NotUsesAllocator' never provides a 'allocator_type' typedef so it is |
| // never automatically uses-allocator constructed. |
| |
| |
| template <class ...ArgTypes, class TestType> |
| bool checkConstruct(TestType& value, UsesAllocatorType form, |
| typename TestType::CtorAlloc const& alloc) |
| // Check that 'value' was constructed using the specified 'form' of |
| // construction and with the specified 'ArgTypes...'. Additionally |
| // check that 'value' was constructed using the specified 'alloc'. |
| { |
| if (form == UA_None) { |
| return value.template checkConstruct<ArgTypes&&...>(form); |
| } else { |
| return value.template checkConstruct<ArgTypes&&...>(form, alloc); |
| } |
| } |
| |
| |
| template <class ...ArgTypes, class TestType> |
| bool checkConstruct(TestType& value, UsesAllocatorType form) { |
| return value.template checkConstruct<ArgTypes&&...>(form); |
| } |
| |
| template <class TestType> |
| bool checkConstructionEquiv(TestType& T, TestType& U) |
| // check that 'T' and 'U' where initialized in the exact same manner. |
| { |
| return T.checkConstructEquiv(U); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| namespace detail { |
| |
| template <bool IsZero, size_t N, class ArgList, class ...Args> |
| struct TakeNImp; |
| |
| template <class ArgList, class ...Args> |
| struct TakeNImp<true, 0, ArgList, Args...> { |
| typedef ArgList type; |
| }; |
| |
| template <size_t N, class ...A1, class F, class ...R> |
| struct TakeNImp<false, N, ArgumentListID<A1...>, F, R...> |
| : TakeNImp<N-1 == 0, N - 1, ArgumentListID<A1..., F>, R...> {}; |
| |
| template <size_t N, class ...Args> |
| struct TakeNArgs : TakeNImp<N == 0, N, ArgumentListID<>, Args...> {}; |
| |
| template <class T> |
| struct Identity { typedef T type; }; |
| |
| template <class T> |
| using IdentityT = typename Identity<T>::type; |
| |
| template <bool Value> |
| using EnableIfB = typename std::enable_if<Value, bool>::type; |
| |
| } // end namespace detail |
| |
| // FIXME: UsesAllocatorTestBase needs some special logic to deal with |
| // polymorphic allocators. However we don't want to include |
| // <experimental/memory_resource> in this header. Therefore in order |
| // to inject this behavior later we use a trait. |
| // See test_memory_resource.hpp for more info. |
| template <class Alloc> |
| struct TransformErasedTypeAlloc { |
| using type = Alloc; |
| }; |
| |
| using detail::EnableIfB; |
| |
| struct AllocLastTag {}; |
| |
| template <class Alloc, bool = std::is_default_constructible<Alloc>::value> |
| struct UsesAllocatorTestBaseStorage { |
| Alloc allocator; |
| UsesAllocatorTestBaseStorage() = default; |
| UsesAllocatorTestBaseStorage(Alloc const& a) : allocator(a) {} |
| const Alloc* get_allocator() const { return &allocator; } |
| }; |
| |
| template <class Alloc> |
| struct UsesAllocatorTestBaseStorage<Alloc, false> { |
| union { |
| char dummy; |
| Alloc alloc; |
| }; |
| bool has_alloc = false; |
| |
| UsesAllocatorTestBaseStorage() : dummy(), has_alloc(false) {} |
| UsesAllocatorTestBaseStorage(Alloc const& a) : alloc(a), has_alloc(true) {} |
| ~UsesAllocatorTestBaseStorage() { |
| if (has_alloc) |
| alloc.~Alloc(); |
| } |
| |
| Alloc const* get_allocator() const { |
| if (!has_alloc) |
| return nullptr; |
| return &alloc; |
| } |
| }; |
| |
| template <class Self, class Alloc> |
| struct UsesAllocatorTestBase { |
| public: |
| using CtorAlloc = typename TransformErasedTypeAlloc<Alloc>::type; |
| |
| template <class ...ArgTypes> |
| bool checkConstruct(UsesAllocatorType expectType) const { |
| auto expectArgs = &makeArgumentID<ArgTypes...>(); |
| return COMPARE_ALLOC_TYPE(expectType, constructor_called) && |
| COMPARE_TYPEID(args_id, expectArgs); |
| } |
| |
| template <class ...ArgTypes> |
| bool checkConstruct(UsesAllocatorType expectType, |
| CtorAlloc const& expectAlloc) const { |
| auto ExpectID = &makeArgumentID<ArgTypes...>() ; |
| return COMPARE_ALLOC_TYPE(expectType, constructor_called) && |
| COMPARE_TYPEID(args_id, ExpectID) && |
| has_alloc() && expectAlloc == *get_alloc(); |
| |
| } |
| |
| bool checkConstructEquiv(UsesAllocatorTestBase& O) const { |
| if (has_alloc() != O.has_alloc()) |
| return false; |
| return COMPARE_ALLOC_TYPE(constructor_called, O.constructor_called) |
| && COMPARE_TYPEID(args_id, O.args_id) |
| && (!has_alloc() || *get_alloc() == *O.get_alloc()); |
| } |
| |
| protected: |
| explicit UsesAllocatorTestBase(const TypeID* aid) |
| : args_id(aid), constructor_called(UA_None), alloc_store() |
| {} |
| |
| UsesAllocatorTestBase(UsesAllocatorTestBase const&) |
| : args_id(&makeArgumentID<Self const&>()), constructor_called(UA_None), |
| alloc_store() |
| {} |
| |
| UsesAllocatorTestBase(UsesAllocatorTestBase&&) |
| : args_id(&makeArgumentID<Self&&>()), constructor_called(UA_None), |
| alloc_store() |
| {} |
| |
| template <class ...Args> |
| UsesAllocatorTestBase(std::allocator_arg_t, CtorAlloc const& a, Args&&...) |
| : args_id(&makeArgumentID<Args&&...>()), |
| constructor_called(UA_AllocArg), |
| alloc_store(a) |
| {} |
| |
| template <class ...Args, class ArgsIDL = detail::TakeNArgs<sizeof...(Args) - 1, Args&&...>> |
| UsesAllocatorTestBase(AllocLastTag, Args&&... args) |
| : args_id(&makeTypeIDImp<typename ArgsIDL::type>()), |
| constructor_called(UA_AllocLast), |
| alloc_store(UsesAllocatorTestBase::getAllocatorFromPack( |
| typename ArgsIDL::type{}, |
| std::forward<Args>(args)...)) |
| { |
| } |
| |
| private: |
| template <class ...LArgs, class ...Args> |
| static CtorAlloc getAllocatorFromPack(ArgumentListID<LArgs...>, Args&&... args) { |
| return UsesAllocatorTestBase::getAllocatorFromPackImp<LArgs const&...>(args...); |
| } |
| |
| template <class ...LArgs> |
| static CtorAlloc getAllocatorFromPackImp( |
| typename detail::Identity<LArgs>::type..., CtorAlloc const& alloc) { |
| return alloc; |
| } |
| |
| bool has_alloc() const { return alloc_store.get_allocator() != nullptr; } |
| const CtorAlloc *get_alloc() const { return alloc_store.get_allocator(); } |
| public: |
| const TypeID* args_id; |
| UsesAllocatorType constructor_called = UA_None; |
| UsesAllocatorTestBaseStorage<CtorAlloc> alloc_store; |
| }; |
| |
| template <class Alloc, size_t Arity> |
| class UsesAllocatorV1 : public UsesAllocatorTestBase<UsesAllocatorV1<Alloc, Arity>, Alloc> |
| { |
| public: |
| typedef Alloc allocator_type; |
| |
| using Base = UsesAllocatorTestBase<UsesAllocatorV1, Alloc>; |
| using CtorAlloc = typename Base::CtorAlloc; |
| |
| UsesAllocatorV1() : Base(&makeArgumentID<>()) {} |
| |
| UsesAllocatorV1(UsesAllocatorV1 const&) |
| : Base(&makeArgumentID<UsesAllocatorV1 const&>()) {} |
| UsesAllocatorV1(UsesAllocatorV1 &&) |
| : Base(&makeArgumentID<UsesAllocatorV1 &&>()) {} |
| // Non-Uses Allocator Ctor |
| template <class ...Args, EnableIfB<sizeof...(Args) == Arity> = false> |
| UsesAllocatorV1(Args&&...) : Base(&makeArgumentID<Args&&...>()) {} |
| |
| // Uses Allocator Arg Ctor |
| template <class ...Args> |
| UsesAllocatorV1(std::allocator_arg_t tag, CtorAlloc const & a, Args&&... args) |
| : Base(tag, a, std::forward<Args>(args)...) |
| { } |
| |
| // BLOWS UP: Uses Allocator Last Ctor |
| template <class First, class ...Args, EnableIfB<sizeof...(Args) == Arity> Dummy = false> |
| constexpr UsesAllocatorV1(First&&, Args&&...) |
| { |
| static_assert(!std::is_same<First, First>::value, ""); |
| } |
| }; |
| |
| |
| template <class Alloc, size_t Arity> |
| class UsesAllocatorV2 : public UsesAllocatorTestBase<UsesAllocatorV2<Alloc, Arity>, Alloc> |
| { |
| public: |
| typedef Alloc allocator_type; |
| |
| using Base = UsesAllocatorTestBase<UsesAllocatorV2, Alloc>; |
| using CtorAlloc = typename Base::CtorAlloc; |
| |
| UsesAllocatorV2() : Base(&makeArgumentID<>()) {} |
| UsesAllocatorV2(UsesAllocatorV2 const&) |
| : Base(&makeArgumentID<UsesAllocatorV2 const&>()) {} |
| UsesAllocatorV2(UsesAllocatorV2 &&) |
| : Base(&makeArgumentID<UsesAllocatorV2 &&>()) {} |
| |
| // Non-Uses Allocator Ctor |
| template <class ...Args, EnableIfB<sizeof...(Args) == Arity> = false> |
| UsesAllocatorV2(Args&&...) : Base(&makeArgumentID<Args&&...>()) {} |
| |
| // Uses Allocator Last Ctor |
| template <class ...Args, EnableIfB<sizeof...(Args) == Arity + 1> = false> |
| UsesAllocatorV2(Args&&... args) |
| : Base(AllocLastTag{}, std::forward<Args>(args)...) |
| {} |
| }; |
| |
| template <class Alloc, size_t Arity> |
| class UsesAllocatorV3 : public UsesAllocatorTestBase<UsesAllocatorV3<Alloc, Arity>, Alloc> |
| { |
| public: |
| typedef Alloc allocator_type; |
| |
| using Base = UsesAllocatorTestBase<UsesAllocatorV3, Alloc>; |
| using CtorAlloc = typename Base::CtorAlloc; |
| |
| UsesAllocatorV3() : Base(&makeArgumentID<>()) {} |
| UsesAllocatorV3(UsesAllocatorV3 const&) |
| : Base(&makeArgumentID<UsesAllocatorV3 const&>()) {} |
| UsesAllocatorV3(UsesAllocatorV3 &&) |
| : Base(&makeArgumentID<UsesAllocatorV3 &&>()) {} |
| |
| // Non-Uses Allocator Ctor |
| template <class ...Args, EnableIfB<sizeof...(Args) == Arity> = false> |
| UsesAllocatorV3(Args&&...) : Base(&makeArgumentID<Args&&...>()) {} |
| |
| // Uses Allocator Arg Ctor |
| template <class ...Args> |
| UsesAllocatorV3(std::allocator_arg_t tag, CtorAlloc const& alloc, Args&&... args) |
| : Base(tag, alloc, std::forward<Args>(args)...) |
| {} |
| |
| // Uses Allocator Last Ctor |
| template <class ...Args, EnableIfB<sizeof...(Args) == Arity + 1> = false> |
| UsesAllocatorV3(Args&&... args) |
| : Base(AllocLastTag{}, std::forward<Args>(args)...) |
| {} |
| }; |
| |
| template <class Alloc, size_t Arity> |
| class NotUsesAllocator : public UsesAllocatorTestBase<NotUsesAllocator<Alloc, Arity>, Alloc> |
| { |
| public: |
| // no allocator_type typedef provided |
| |
| using Base = UsesAllocatorTestBase<NotUsesAllocator, Alloc>; |
| using CtorAlloc = typename Base::CtorAlloc; |
| |
| NotUsesAllocator() : Base(&makeArgumentID<>()) {} |
| NotUsesAllocator(NotUsesAllocator const&) |
| : Base(&makeArgumentID<NotUsesAllocator const&>()) {} |
| NotUsesAllocator(NotUsesAllocator &&) |
| : Base(&makeArgumentID<NotUsesAllocator &&>()) {} |
| // Non-Uses Allocator Ctor |
| template <class ...Args, EnableIfB<sizeof...(Args) == Arity> = false> |
| NotUsesAllocator(Args&&...) : Base(&makeArgumentID<Args&&...>()) {} |
| |
| // Uses Allocator Arg Ctor |
| template <class ...Args> |
| NotUsesAllocator(std::allocator_arg_t tag, CtorAlloc const& alloc, Args&&... args) |
| : Base(tag, alloc, std::forward<Args>(args)...) |
| {} |
| |
| // Uses Allocator Last Ctor |
| template <class ...Args, EnableIfB<sizeof...(Args) == Arity + 1> = false> |
| NotUsesAllocator(Args&&... args) |
| : Base(AllocLastTag{}, std::forward<Args>(args)...) |
| {} |
| }; |
| |
| #endif /* USES_ALLOC_TYPES_HPP */ |