diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt index c2a597f49e317f2..3431ea7dab386b9 100644 --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -747,6 +747,7 @@ set(files __type_traits/common_type.h __type_traits/conditional.h __type_traits/conjunction.h + __type_traits/container_traits.h __type_traits/copy_cv.h __type_traits/copy_cvref.h __type_traits/datasizeof.h diff --git a/libcxx/include/__type_traits/container_traits.h b/libcxx/include/__type_traits/container_traits.h new file mode 100644 index 000000000000000..5262cef94c61efd --- /dev/null +++ b/libcxx/include/__type_traits/container_traits.h @@ -0,0 +1,43 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// 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 _LIBCPP___TYPE_TRAITS_CONTAINER_TRAITS_H +#define _LIBCPP___TYPE_TRAITS_CONTAINER_TRAITS_H + +#include <__config> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_BEGIN_NAMESPACE_STD + +// // __container_traits is a general purpose utility containing traits describing various containers operations. +// It currently only has one trait: `__emplacement_has_strong_exception_safety_guarantee`, but it's +// intended to be extended in the future. +// +// These traits should only be used for optimization or QoI purposes. In particular, since this is a libc++ internal +// mechanism, no user-defined containers should be expected to specialize these traits (in fact it would be illegal for +// them to do so). Hence, when using these traits to implement something, make sure that a container that fails to +// specialize these traits does not result in non-conforming code. +// +// When a trait is nonsensical for a type, this class still provides a fallback value for that trait. +// For example, `std::array` does not support `insert` or `emplace`, so +// `__emplacement_has_strong_exception_safety_guarantee` is false for such types. +template +struct __container_traits { + // A trait that tells whether a single element insertion/emplacement via member function + // `insert(...)` or `emplace(...)` has strong exception guarantee, that is, if the function + // exits via an exception, the original container is unaffected + static _LIBCPP_CONSTEXPR const bool __emplacement_has_strong_exception_safety_guarantee = false; +}; + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___TYPE_TRAITS_CONTAINER_TRAITS_H diff --git a/libcxx/include/deque b/libcxx/include/deque index fa67916ba11bcae..11219d1a99244ee 100644 --- a/libcxx/include/deque +++ b/libcxx/include/deque @@ -220,6 +220,8 @@ template #include <__ranges/size.h> #include <__split_buffer> #include <__type_traits/conditional.h> +#include <__type_traits/container_traits.h> +#include <__type_traits/disjunction.h> #include <__type_traits/enable_if.h> #include <__type_traits/is_allocator.h> #include <__type_traits/is_convertible.h> @@ -2609,6 +2611,17 @@ inline constexpr bool __format::__enable_insertable> = true; #endif // _LIBCPP_STD_VER >= 20 +template +struct __container_traits > { + // http://eel.is/c++draft/deque.modifiers#3 + // If an exception is thrown other than by the copy constructor, move constructor, assignment operator, or move + // assignment operator of T, there are no effects. If an exception is thrown while inserting a single element at + // either end, there are no effects. Otherwise, if an exception is thrown by the move constructor of a + // non-Cpp17CopyInsertable T, the effects are unspecified. + static _LIBCPP_CONSTEXPR const bool __emplacement_has_strong_exception_safety_guarantee = + _Or, __is_cpp17_copy_insertable<_Allocator> >::value; +}; + _LIBCPP_END_NAMESPACE_STD #if _LIBCPP_STD_VER >= 17 diff --git a/libcxx/include/forward_list b/libcxx/include/forward_list index dd86888bf7a6e7f..d3262fb8eaed1f5 100644 --- a/libcxx/include/forward_list +++ b/libcxx/include/forward_list @@ -218,6 +218,7 @@ template #include <__ranges/container_compatible_range.h> #include <__ranges/from_range.h> #include <__type_traits/conditional.h> +#include <__type_traits/container_traits.h> #include <__type_traits/enable_if.h> #include <__type_traits/is_allocator.h> #include <__type_traits/is_const.h> @@ -1544,6 +1545,17 @@ erase(forward_list<_Tp, _Allocator>& __c, const _Up& __v) { } #endif +template +struct __container_traits > { + // http://eel.is/c++draft/container.reqmts + // Unless otherwise specified (see [associative.reqmts.except], [unord.req.except], [deque.modifiers], + // [inplace.vector.modifiers], and [vector.modifiers]) all container types defined in this Clause meet the following + // additional requirements: + // - If an exception is thrown by an insert() or emplace() function while inserting a single element, that + // function has no effects. + static _LIBCPP_CONSTEXPR const bool __emplacement_has_strong_exception_safety_guarantee = true; +}; + _LIBCPP_END_NAMESPACE_STD #if _LIBCPP_STD_VER >= 17 diff --git a/libcxx/include/list b/libcxx/include/list index ba21dbe3f74c083..4a169b08d8cddbb 100644 --- a/libcxx/include/list +++ b/libcxx/include/list @@ -225,6 +225,7 @@ template #include <__ranges/container_compatible_range.h> #include <__ranges/from_range.h> #include <__type_traits/conditional.h> +#include <__type_traits/container_traits.h> #include <__type_traits/enable_if.h> #include <__type_traits/is_allocator.h> #include <__type_traits/is_nothrow_assignable.h> @@ -1715,6 +1716,17 @@ inline constexpr bool __format::__enable_insertable> = true; #endif // _LIBCPP_STD_VER >= 20 +template +struct __container_traits > { + // http://eel.is/c++draft/container.reqmts + // Unless otherwise specified (see [associative.reqmts.except], [unord.req.except], [deque.modifiers], + // [inplace.vector.modifiers], and [vector.modifiers]) all container types defined in this Clause meet the following + // additional requirements: + // - If an exception is thrown by an insert() or emplace() function while inserting a single element, that + // function has no effects. + static _LIBCPP_CONSTEXPR const bool __emplacement_has_strong_exception_safety_guarantee = true; +}; + _LIBCPP_END_NAMESPACE_STD #if _LIBCPP_STD_VER >= 17 diff --git a/libcxx/include/map b/libcxx/include/map index 7fca4c8a0872fd5..fabf39512ab75fa 100644 --- a/libcxx/include/map +++ b/libcxx/include/map @@ -594,6 +594,7 @@ erase_if(multimap& c, Predicate pred); // C++20 #include <__ranges/container_compatible_range.h> #include <__ranges/from_range.h> #include <__tree> +#include <__type_traits/container_traits.h> #include <__type_traits/is_allocator.h> #include <__type_traits/remove_const.h> #include <__type_traits/type_identity.h> @@ -1644,6 +1645,14 @@ erase_if(map<_Key, _Tp, _Compare, _Allocator>& __c, _Predicate __pred) { } #endif +template +struct __container_traits > { + // http://eel.is/c++draft/associative.reqmts.except#2 + // For associative containers, if an exception is thrown by any operation from within + // an insert or emplace function inserting a single element, the insertion has no effect. + static _LIBCPP_CONSTEXPR const bool __emplacement_has_strong_exception_safety_guarantee = true; +}; + template , class _Allocator = allocator > > class _LIBCPP_TEMPLATE_VIS multimap { public: @@ -2158,6 +2167,14 @@ erase_if(multimap<_Key, _Tp, _Compare, _Allocator>& __c, _Predicate __pred) { } #endif +template +struct __container_traits > { + // http://eel.is/c++draft/associative.reqmts.except#2 + // For associative containers, if an exception is thrown by any operation from within + // an insert or emplace function inserting a single element, the insertion has no effect. + static _LIBCPP_CONSTEXPR const bool __emplacement_has_strong_exception_safety_guarantee = true; +}; + _LIBCPP_END_NAMESPACE_STD #if _LIBCPP_STD_VER >= 17 diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap index 3ea91274a9cc9a1..5a0e199394d0185 100644 --- a/libcxx/include/module.modulemap +++ b/libcxx/include/module.modulemap @@ -79,6 +79,7 @@ module std_core [system] { } module conditional { header "__type_traits/conditional.h" } module conjunction { header "__type_traits/conjunction.h" } + module container_traits { header "__type_traits/container_traits.h" } module copy_cv { header "__type_traits/copy_cv.h" } module copy_cvref { header "__type_traits/copy_cvref.h" } module datasizeof { header "__type_traits/datasizeof.h" } diff --git a/libcxx/include/set b/libcxx/include/set index 0c2ca64139e0d39..5db0db8086dec6a 100644 --- a/libcxx/include/set +++ b/libcxx/include/set @@ -531,6 +531,7 @@ erase_if(multiset& c, Predicate pred); // C++20 #include <__ranges/container_compatible_range.h> #include <__ranges/from_range.h> #include <__tree> +#include <__type_traits/container_traits.h> #include <__type_traits/enable_if.h> #include <__type_traits/is_allocator.h> #include <__type_traits/is_nothrow_assignable.h> @@ -1022,6 +1023,14 @@ erase_if(set<_Key, _Compare, _Allocator>& __c, _Predicate __pred) { } #endif +template +struct __container_traits > { + // http://eel.is/c++draft/associative.reqmts.except#2 + // For associative containers, if an exception is thrown by any operation from within + // an insert or emplace function inserting a single element, the insertion has no effect. + static _LIBCPP_CONSTEXPR const bool __emplacement_has_strong_exception_safety_guarantee = true; +}; + template , class _Allocator = allocator<_Key> > class _LIBCPP_TEMPLATE_VIS multiset { public: @@ -1481,6 +1490,14 @@ erase_if(multiset<_Key, _Compare, _Allocator>& __c, _Predicate __pred) { } #endif +template +struct __container_traits > { + // http://eel.is/c++draft/associative.reqmts.except#2 + // For associative containers, if an exception is thrown by any operation from within + // an insert or emplace function inserting a single element, the insertion has no effect. + static _LIBCPP_CONSTEXPR const bool __emplacement_has_strong_exception_safety_guarantee = true; +}; + _LIBCPP_END_NAMESPACE_STD #if _LIBCPP_STD_VER >= 17 diff --git a/libcxx/include/unordered_map b/libcxx/include/unordered_map index 0d71a51ee9e7299..05aa01a3b7c3083 100644 --- a/libcxx/include/unordered_map +++ b/libcxx/include/unordered_map @@ -604,7 +604,9 @@ template #include <__ranges/concepts.h> #include <__ranges/container_compatible_range.h> #include <__ranges/from_range.h> +#include <__type_traits/container_traits.h> #include <__type_traits/enable_if.h> +#include <__type_traits/invoke.h> #include <__type_traits/is_allocator.h> #include <__type_traits/is_integral.h> #include <__type_traits/remove_const.h> @@ -1830,6 +1832,16 @@ inline _LIBCPP_HIDE_FROM_ABI bool operator!=(const unordered_map<_Key, _Tp, _Has #endif +template +struct __container_traits > { + // http://eel.is/c++draft/unord.req.except#2 + // For unordered associative containers, if an exception is thrown by any operation + // other than the container's hash function from within an insert or emplace function + // inserting a single element, the insertion has no effect. + static _LIBCPP_CONSTEXPR const bool __emplacement_has_strong_exception_safety_guarantee = + __nothrow_invokable<_Hash, const _Key&>::value; +}; + template , @@ -2520,6 +2532,16 @@ inline _LIBCPP_HIDE_FROM_ABI bool operator!=(const unordered_multimap<_Key, _Tp, #endif +template +struct __container_traits > { + // http://eel.is/c++draft/unord.req.except#2 + // For unordered associative containers, if an exception is thrown by any operation + // other than the container's hash function from within an insert or emplace function + // inserting a single element, the insertion has no effect. + static _LIBCPP_CONSTEXPR const bool __emplacement_has_strong_exception_safety_guarantee = + __nothrow_invokable<_Hash, const _Key&>::value; +}; + _LIBCPP_END_NAMESPACE_STD #if _LIBCPP_STD_VER >= 17 diff --git a/libcxx/include/unordered_set b/libcxx/include/unordered_set index 2b09c72b866b03c..7ab1c651b8c9565 100644 --- a/libcxx/include/unordered_set +++ b/libcxx/include/unordered_set @@ -550,7 +550,9 @@ template #include <__ranges/concepts.h> #include <__ranges/container_compatible_range.h> #include <__ranges/from_range.h> +#include <__type_traits/container_traits.h> #include <__type_traits/enable_if.h> +#include <__type_traits/invoke.h> #include <__type_traits/is_allocator.h> #include <__type_traits/is_integral.h> #include <__type_traits/is_nothrow_assignable.h> @@ -1183,6 +1185,16 @@ inline _LIBCPP_HIDE_FROM_ABI bool operator!=(const unordered_set<_Value, _Hash, #endif +template +struct __container_traits > { + // http://eel.is/c++draft/unord.req.except#2 + // For unordered associative containers, if an exception is thrown by any operation + // other than the container's hash function from within an insert or emplace function + // inserting a single element, the insertion has no effect. + static _LIBCPP_CONSTEXPR const bool __emplacement_has_strong_exception_safety_guarantee = + __nothrow_invokable<_Hash, const _Value&>::value; +}; + template , class _Pred = equal_to<_Value>, class _Alloc = allocator<_Value> > class _LIBCPP_TEMPLATE_VIS unordered_multiset { public: @@ -1793,6 +1805,16 @@ inline _LIBCPP_HIDE_FROM_ABI bool operator!=(const unordered_multiset<_Value, _H #endif +template +struct __container_traits > { + // http://eel.is/c++draft/unord.req.except#2 + // For unordered associative containers, if an exception is thrown by any operation + // other than the container's hash function from within an insert or emplace function + // inserting a single element, the insertion has no effect. + static _LIBCPP_CONSTEXPR const bool __emplacement_has_strong_exception_safety_guarantee = + __nothrow_invokable<_Hash, const _Value&>::value; +}; + _LIBCPP_END_NAMESPACE_STD #if _LIBCPP_STD_VER >= 17 diff --git a/libcxx/include/vector b/libcxx/include/vector index 76de0ab0a81a04b..dc31f31838264ce 100644 --- a/libcxx/include/vector +++ b/libcxx/include/vector @@ -356,6 +356,8 @@ template requires is-vector-bool-reference // Since C++ #include <__ranges/size.h> #include <__split_buffer> #include <__type_traits/conditional.h> +#include <__type_traits/container_traits.h> +#include <__type_traits/disjunction.h> #include <__type_traits/enable_if.h> #include <__type_traits/is_allocator.h> #include <__type_traits/is_constructible.h> @@ -3005,6 +3007,18 @@ public: }; #endif // _LIBCPP_STD_VER >= 23 +template +struct __container_traits > { + // http://eel.is/c++draft/vector.modifiers#2 + // If an exception is thrown other than by the copy constructor, move constructor, assignment operator, or move + // assignment operator of T or by any InputIterator operation, there are no effects. If an exception is thrown while + // inserting a single element at the end and T is Cpp17CopyInsertable or is_nothrow_move_constructible_v is true, + // there are no effects. Otherwise, if an exception is thrown by the move constructor of a non-Cpp17CopyInsertable T, + // the effects are unspecified. + static _LIBCPP_CONSTEXPR const bool __emplacement_has_strong_exception_safety_guarantee = + _Or, __is_cpp17_copy_insertable<_Allocator> >::value; +}; + _LIBCPP_END_NAMESPACE_STD #if _LIBCPP_STD_VER >= 17 diff --git a/libcxx/test/libcxx/containers/container_traits.compile.pass.cpp b/libcxx/test/libcxx/containers/container_traits.compile.pass.cpp new file mode 100644 index 000000000000000..1452bfbaf3960ea --- /dev/null +++ b/libcxx/test/libcxx/containers/container_traits.compile.pass.cpp @@ -0,0 +1,163 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// <__type_traits/container_traits.h> +// + +#include <__type_traits/container_traits.h> + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test_allocator.h" +#include "test_macros.h" +#include "MoveOnly.h" + +struct ThrowOnMove { + ThrowOnMove(); + ThrowOnMove(const ThrowOnMove&) TEST_NOEXCEPT_COND(false); + ThrowOnMove(ThrowOnMove&&) TEST_NOEXCEPT_COND(false); + ThrowOnMove& operator=(ThrowOnMove&&) TEST_NOEXCEPT_COND(false); + ThrowOnMove& operator=(const ThrowOnMove&) TEST_NOEXCEPT_COND(false); + + bool operator<(ThrowOnMove const&) const; + bool operator==(ThrowOnMove const&) const; +}; + +struct NonCopyThrowOnMove { + NonCopyThrowOnMove(); + NonCopyThrowOnMove(NonCopyThrowOnMove&&) TEST_NOEXCEPT_COND(false); + NonCopyThrowOnMove(const NonCopyThrowOnMove&) = delete; + NonCopyThrowOnMove& operator=(NonCopyThrowOnMove&&) TEST_NOEXCEPT_COND(false); + NonCopyThrowOnMove& operator=(const NonCopyThrowOnMove&) = delete; + + bool operator<(NonCopyThrowOnMove const&) const; + bool operator==(NonCopyThrowOnMove const&) const; +}; + +struct ThrowingHash { + template + std::size_t operator()(const T&) const TEST_NOEXCEPT_COND(false); +}; + +struct NoThrowHash { + template + std::size_t operator()(const T&) const TEST_NOEXCEPT; +}; + +template +void check() { + static_assert( + std::__container_traits::__emplacement_has_strong_exception_safety_guarantee == Expected, ""); +} + +void test() { + check >(); + check > >(); + check >(); + check >(); + check >(); + + check >(); + check > >(); + check >(); + check >(); + check >(); + + check >(); + check > >(); + check >(); + check >(); + check >(); + + check >(); + check > >(); + check >(); + check >(); + check >(); + + check >(); + check, test_allocator > >(); + check >(); + check >(); + check >(); + + check >(); + check, test_allocator > >(); + check >(); + check >(); + check >(); + + check >(); + check, test_allocator > >(); + check >(); + check >(); + check >(); + + check >(); + check, test_allocator > >(); + check >(); + check >(); + check >(); + +#if TEST_STD_VER < 11 + check >(); + check, std::less, test_allocator > >(); + check >(); + check >(); + check >(); + + check >(); + check, std::less, test_allocator > >(); + check >(); + check >(); + check >(); + + check >(); + check, std::less, test_allocator > >(); + check >(); + check >(); + check >(); + + check >(); + check, std::less, test_allocator > >(); + check >(); + check >(); + check >(); +#else + check >(); + check, std::less, test_allocator > >(); + check >(); + check >(); + check >(); + + check >(); + check, std::less, test_allocator > >(); + check >(); + check >(); + check >(); + + check >(); + check, std::less, test_allocator > >(); + check >(); + check >(); + check >(); + + check >(); + check, std::less, test_allocator > >(); + check >(); + check >(); + check >(); +#endif +}