diff --git a/libcxx/include/__memory/allocator_traits.h b/libcxx/include/__memory/allocator_traits.h --- a/libcxx/include/__memory/allocator_traits.h +++ b/libcxx/include/__memory/allocator_traits.h @@ -13,6 +13,7 @@ #include <__config> #include <__memory/construct_at.h> #include <__memory/pointer_traits.h> +#include <__type_traits/is_primary_template.h> #include <__utility/forward.h> #include #include @@ -240,6 +241,8 @@ using propagate_on_container_swap = typename __propagate_on_container_swap::type; using is_always_equal = typename __is_always_equal::type; + using __primary_template = allocator_traits; + #ifndef _LIBCPP_CXX03_LANG template using rebind_alloc = __allocator_traits_rebind_t; @@ -365,34 +368,52 @@ template struct __is_default_allocator > : true_type { }; -// __is_cpp17_move_insertable -template -struct __is_cpp17_move_insertable - : is_move_constructible -{ }; +// Implement named requirements from https://wg21.link/container.alloc.reqmts + +template +decltype(allocator_traits<_Alloc>::construct(std::declval<_Alloc&>(), + std::declval::value_type*>(), + std::declval<_Args>()...), + true_type()) +__is_cpp17_emplace_constructible_impl(int); + +template +false_type __is_cpp17_emplace_constructible_impl(...); + +// If it's the primary template and the allocator doesn't have a custom construt(), +// we check instead that the type is constructible, since the default construct() isn't constrained. +// This is also the case for std::allocator. +template +struct __is_cpp17_emplace_constructible + : _If<__is_primary_template>::value && + (!__has_construct<_Alloc, _Args...>::value || __is_default_allocator<_Alloc>::value), + is_constructible::value_type, _Args...>, + decltype(std::__is_cpp17_emplace_constructible_impl<_Alloc, _Args...>(0))> {}; template -struct __is_cpp17_move_insertable<_Alloc, __enable_if_t< - !__is_default_allocator<_Alloc>::value && - __has_construct<_Alloc, typename _Alloc::value_type*, typename _Alloc::value_type&&>::value -> > : true_type { }; +struct __is_cpp17_default_insertable : __is_cpp17_emplace_constructible<_Alloc> {}; -// __is_cpp17_copy_insertable -template +template +struct __is_cpp17_move_insertable + : __is_cpp17_emplace_constructible<_Alloc, typename allocator_traits<_Alloc>::value_type&&> {}; + +template struct __is_cpp17_copy_insertable - : integral_constant::value && - __is_cpp17_move_insertable<_Alloc>::value - > -{ }; + : _And<__is_cpp17_move_insertable<_Alloc>, + __is_cpp17_emplace_constructible<_Alloc, typename allocator_traits<_Alloc>::value_type&>, + __is_cpp17_emplace_constructible<_Alloc, const typename allocator_traits<_Alloc>::value_type&> > {}; + +template +decltype(allocator_traits<_Alloc>::destroy(std::declval<_Alloc&>(), + std::declval::value_type*>()), + true_type()) +__is_cpp17_erasable_impl(int); + +template +false_type __is_cpp17_erasable_impl(...); template -struct __is_cpp17_copy_insertable<_Alloc, __enable_if_t< - !__is_default_allocator<_Alloc>::value && - __has_construct<_Alloc, typename _Alloc::value_type*, const typename _Alloc::value_type&>::value -> > - : __is_cpp17_move_insertable<_Alloc> -{ }; +struct __is_cpp17_erasable : decltype(std::__is_cpp17_erasable_impl<_Alloc>(0)) {}; #undef _LIBCPP_ALLOCATOR_TRAITS_HAS_XXX diff --git a/libcxx/include/vector b/libcxx/include/vector --- a/libcxx/include/vector +++ b/libcxx/include/vector @@ -388,6 +388,7 @@ vector(size_type __n, const value_type& __x, const allocator_type& __a) : __end_cap_(nullptr, __a) { + static_assert(__is_cpp17_copy_insertable<_Allocator>::value, "value_type has to be Cpp17CopyInsertable"); std::__debug_db_insert_c(this); if (__n > 0) { @@ -1053,6 +1054,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 vector<_Tp, _Allocator>::vector(size_type __n) { + static_assert(__is_cpp17_default_insertable<_Allocator>::value, "value_type has to be Cpp17DefaultInsertable"); std::__debug_db_insert_c(this); if (__n > 0) { @@ -1067,6 +1069,7 @@ vector<_Tp, _Allocator>::vector(size_type __n, const allocator_type& __a) : __end_cap_(nullptr, __a) { + static_assert(__is_cpp17_default_insertable<_Allocator>::value, "value_type has to be Cpp17DefaultInsertable"); std::__debug_db_insert_c(this); if (__n > 0) { @@ -1080,6 +1083,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 vector<_Tp, _Allocator>::vector(size_type __n, const value_type& __x) { + static_assert(__is_cpp17_copy_insertable<_Allocator>::value, "value_type has to be Cpp17CopyInsertable"); std::__debug_db_insert_c(this); if (__n > 0) { @@ -1451,6 +1455,7 @@ void vector<_Tp, _Allocator>::reserve(size_type __n) { + static_assert(__is_cpp17_move_insertable<_Allocator>::value, "value_type has to be Cpp17MoveInserable"); if (__n > capacity()) { if (__n > max_size()) @@ -1466,6 +1471,7 @@ void vector<_Tp, _Allocator>::shrink_to_fit() _NOEXCEPT { + static_assert(__is_cpp17_move_insertable<_Allocator>::value, "value_type has to be Cpp17MoveInserable"); if (capacity() > size()) { #ifndef _LIBCPP_NO_EXCEPTIONS @@ -1863,6 +1869,8 @@ void vector<_Tp, _Allocator>::resize(size_type __sz) { + static_assert(__is_cpp17_move_insertable<_Allocator>::value, "value_type has to be Cpp17MoveInserable"); + static_assert(__is_cpp17_default_insertable<_Allocator>::value, "value_type has to be Cpp17DefaultInsertable"); size_type __cs = size(); if (__cs < __sz) this->__append(__sz - __cs); @@ -1875,6 +1883,7 @@ void vector<_Tp, _Allocator>::resize(size_type __sz, const_reference __x) { + static_assert(__is_cpp17_copy_insertable<_Allocator>::value, "value_type has to be Cpp17CopyInsertable"); size_type __cs = size(); if (__cs < __sz) this->__append(__sz - __cs, __x); diff --git a/libcxx/test/libcxx/containers/containers.requirements/container.gen.reqmts/allocator_requirements.compile.pass.cpp b/libcxx/test/libcxx/containers/containers.requirements/container.gen.reqmts/allocator_requirements.compile.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/containers/containers.requirements/container.gen.reqmts/allocator_requirements.compile.pass.cpp @@ -0,0 +1,104 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// Check that allocator requirements are implemented correctly + +#include + +class S {}; + +struct Allocator { + using value_type = S; +}; + +template +struct SpecializedAllocator { + using value_type = S; +}; + +template <> +struct std::allocator_traits> { + using value_type = S; + + static void construct(SpecializedAllocator<0>&, S*); +}; + +template <> +struct std::allocator_traits> { + using value_type = S; + + static void construct(SpecializedAllocator<1>&, S*, S&&); +}; + +template <> +struct std::allocator_traits> { + using value_type = S; + + static void construct(SpecializedAllocator<2>&, S*, int, long); +}; + +template <> +struct std::allocator_traits> { + using value_type = S; + + static void construct(SpecializedAllocator<3>&, S*, S&&); + static void construct(SpecializedAllocator<3>&, S*, const S&); +}; + +template <> +struct std::allocator_traits> { + using value_type = S; + + static void construct(SpecializedAllocator<4>&, S*, S&&) = delete; + static void construct(SpecializedAllocator<4>&, S*, const S&); +}; + +template <> +struct std::allocator_traits> { + using value_type = S; + + static void destroy(SpecializedAllocator<5>&, S*); +}; + +template <> +struct std::allocator_traits> { + using value_type = S; + + static void destroy(SpecializedAllocator<6>&, S*, int); +}; + +template <> +struct std::allocator_traits> { + using value_type = S; + + static void destroy(SpecializedAllocator<7>&, S*, int); +}; + +static_assert(!std::__is_cpp17_emplace_constructible, int, long>::value, ""); + +static_assert(std::__is_cpp17_emplace_constructible::value, ""); +static_assert(!std::__is_cpp17_emplace_constructible, int, long>::value, ""); +static_assert(std::__is_cpp17_emplace_constructible, int, long>::value, ""); + +static_assert(std::__is_cpp17_default_insertable::value, ""); +static_assert(!std::__is_cpp17_default_insertable>::value, ""); +static_assert(std::__is_cpp17_default_insertable>::value, ""); + +static_assert(std::__is_cpp17_move_insertable::value, ""); +static_assert(!std::__is_cpp17_move_insertable>::value, ""); +static_assert(std::__is_cpp17_move_insertable>::value, ""); + +static_assert(std::__is_cpp17_copy_insertable::value, ""); +static_assert(!std::__is_cpp17_copy_insertable>::value, ""); +static_assert(std::__is_cpp17_copy_insertable>::value, ""); +static_assert(!std::__is_cpp17_copy_insertable>::value, ""); + +static_assert(std::__is_cpp17_erasable::value, ""); +static_assert(!std::__is_cpp17_erasable>::value, ""); +static_assert(std::__is_cpp17_erasable>::value, ""); +static_assert(!std::__is_cpp17_erasable>::value, ""); diff --git a/libcxx/test/libcxx/containers/sequences/vector/preconditions.verify.cpp b/libcxx/test/libcxx/containers/sequences/vector/preconditions.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/containers/sequences/vector/preconditions.verify.cpp @@ -0,0 +1,59 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// Ensure that libc++ statically checks preconditions when using std::vector, as a matter of QOI. + +#include + +struct NotDefaultInsertable { + NotDefaultInsertable(int); +}; + +struct NotMoveInsertable { + NotMoveInsertable(NotMoveInsertable&&) = delete; +}; + +struct NotCopyInsertable { + NotCopyInsertable(int); + NotCopyInsertable(NotCopyInsertable&&); +}; + +void func() { + // expected-error@* {{failed due to requirement}} + // expected-error@* {{no matching function for call to}} + std::vector v1(10); + + // expected-error@* {{failed due to requirement}} + std::vector v2(10, std::allocator()); + + // expected-error@* {{failed due to requirement}} + // expected-error@* {{no matching function for call to}} + std::vector v3( + 10, NotCopyInsertable(1), std::allocator()); + + std::vector v4; + + // expected-error@* {{failed due to requirement}} + // expected-error@* {{failed due to requirement}} + // expected-error@* {{no matching function for call to}} + v4.reserve(1); + // expected-error@* {{failed due to requirement}} + v4.shrink_to_fit(); + // expected-error@* {{failed due to requirement}} + // expected-error@* {{failed due to requirement}} + // expected-error@* {{no matching function for call to}} + v4.resize(1); + + std::vector v5; + // expected-error@* {{failed due to requirement}} + v5.resize(1); + + std::vector v6; + // expected-error@* {{failed due to requirement}} + v6.resize(1, NotCopyInsertable(1)); +}