https://gcc.gnu.org/g:f46eccc9409136831225e30017a1d8ba6654a9b9
commit r17-908-gf46eccc9409136831225e30017a1d8ba6654a9b9 Author: Patrick Palka <[email protected]> Date: Thu May 28 10:39:29 2026 -0400 libstdc++: Implement P3567R2 flat_meow fixes This implements the changes in sections 5, 6 and 8 of P3567R2; the other changes (in section 4 and 7) are effectively already implemented. libstdc++-v3/ChangeLog: * include/bits/version.def (flat_map): Bump to 202511. (flat_set): Likewise. * include/bits/version.h: Regenerate. * include/std/flat_map (_Flat_map_impl): Remove is_nothrow_swappable_v assertions. (_Flat_map_impl::_Flat_map_impl): Explicitly default copy ctor. Define move ctor with corrected exception handling as per P3567R2. (_Flat_map_impl::operator=): Likewise. (_Flat_map_impl::insert_range): Define new __sorted_t overload as per P3567R2. (_Flat_map_impl::swap): Make conditionally noexcept as per P3567R2. * include/std/flat_set (_Flat_set_impl): Remove is_nothrow_swappable_v assertion. (_Flat_set_impl::_Flat_set_impl): Explicitly default copy ctor. Define move ctor with correct invariant preserving behavior as per P3567R2. (_Flat_set_impl::operator=): Likewise. (_Flat_set_impl::_M_insert_range): Factored out from insert_range. Add bool parameter __is_sorted defaulted to false. (_Flat_set_impl::insert_range): Define new __sorted_t overload as per P3567R2. (_Flat_set_impl::swap): Make conditionally noexcept as per P3567R2. Correct to use ranges::swap instead of ADL swap. * testsuite/23_containers/flat_map/1.cc (test11, test12): New tests. * testsuite/23_containers/flat_multimap/1.cc (test10, test11): New tests. * testsuite/23_containers/flat_multiset/1.cc (test10, test11): New tests. * testsuite/23_containers/flat_set/1.cc (test10, test11): New tests. Reviewed-by: Tomasz KamiĆski <[email protected]> Reviewed-by: Jonathan Wakely <[email protected]> Diff: --- libstdc++-v3/include/bits/version.def | 9 +- libstdc++-v3/include/bits/version.h | 8 +- libstdc++-v3/include/std/flat_map | 54 +++++++++-- libstdc++-v3/include/std/flat_set | 69 ++++++++++++-- libstdc++-v3/testsuite/23_containers/flat_map/1.cc | 104 ++++++++++++++++++++- .../testsuite/23_containers/flat_multimap/1.cc | 102 ++++++++++++++++++++ .../testsuite/23_containers/flat_multiset/1.cc | 94 +++++++++++++++++++ libstdc++-v3/testsuite/23_containers/flat_set/1.cc | 96 ++++++++++++++++++- 8 files changed, 513 insertions(+), 23 deletions(-) diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def index 2f32a8bda98f..97f7d6865190 100644 --- a/libstdc++-v3/include/bits/version.def +++ b/libstdc++-v3/include/bits/version.def @@ -1898,16 +1898,21 @@ ftms = { ftms = { name = flat_map; + // 202207 P0429R9 A Standard flat_map + // 202511 P3567R2 flat_meow Fixes values = { - v = 202207; + v = 202511; // P3567R2 applied as DR for C++23 cxxmin = 23; }; }; ftms = { name = flat_set; + // 202207 P1222R4 A Standard flat_set + // LWG3751 Missing feature macro for flat_set + // 202511 P3567R2 flat_meow Fixes values = { - v = 202207; + v = 202511; // P3567R2 applied as DR for C++23 cxxmin = 23; }; }; diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h index 517b9020ec65..c31893e34c4e 100644 --- a/libstdc++-v3/include/bits/version.h +++ b/libstdc++-v3/include/bits/version.h @@ -2108,9 +2108,9 @@ #if !defined(__cpp_lib_flat_map) # if (__cplusplus >= 202100L) -# define __glibcxx_flat_map 202207L +# define __glibcxx_flat_map 202511L # if defined(__glibcxx_want_all) || defined(__glibcxx_want_flat_map) -# define __cpp_lib_flat_map 202207L +# define __cpp_lib_flat_map 202511L # endif # endif #endif /* !defined(__cpp_lib_flat_map) */ @@ -2118,9 +2118,9 @@ #if !defined(__cpp_lib_flat_set) # if (__cplusplus >= 202100L) -# define __glibcxx_flat_set 202207L +# define __glibcxx_flat_set 202511L # if defined(__glibcxx_want_all) || defined(__glibcxx_want_flat_set) -# define __cpp_lib_flat_set 202207L +# define __cpp_lib_flat_set 202511L # endif # endif #endif /* !defined(__cpp_lib_flat_set) */ diff --git a/libstdc++-v3/include/std/flat_map b/libstdc++-v3/include/std/flat_map index a51c6625d54a..f06c75a16f65 100644 --- a/libstdc++-v3/include/std/flat_map +++ b/libstdc++-v3/include/std/flat_map @@ -74,9 +74,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION static_assert(is_same_v<_Key, typename _KeyContainer::value_type>); static_assert(is_same_v<_Tp, typename _MappedContainer::value_type>); - static_assert(is_nothrow_swappable_v<_KeyContainer>); - static_assert(is_nothrow_swappable_v<_MappedContainer>); - using _Derived = __conditional_t<_Multi, flat_multimap<_Key, _Tp, _Compare, _KeyContainer, _MappedContainer>, @@ -395,6 +392,38 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION : _Flat_map_impl(__s, __il.begin(), __il.end(), __comp, __a) { } + _Flat_map_impl(const _Flat_map_impl&) = default; + _Flat_map_impl& operator=(const _Flat_map_impl&) = default; + + _GLIBCXX26_CONSTEXPR + _Flat_map_impl(_Flat_map_impl&& __other) + noexcept(is_nothrow_move_constructible_v<containers> + && is_nothrow_move_constructible_v<key_compare>) +#if __cpp_exceptions + try +#endif + : _M_cont(std::move(__other._M_cont)), _M_comp(std::move(__other._M_comp)) + { __other.clear(); } +#if __cpp_exceptions + catch (...) + { __other.clear(); } +#endif + + _GLIBCXX26_CONSTEXPR + _Flat_map_impl& + operator=(_Flat_map_impl&& __other) + noexcept(is_nothrow_move_assignable_v<containers> + && is_nothrow_move_assignable_v<key_compare>) + { + auto __guard = _M_make_clear_guard(); + auto __guard_other = _ClearGuard{__other._M_cont}; + _M_cont = std::move(__other._M_cont); + _M_comp = std::move(__other._M_comp); + __guard._M_disable(); + // __guard_other._M_disable is deliberately not called. + return *this; + } + _GLIBCXX26_CONSTEXPR _Derived& operator=(initializer_list<value_type> __il) @@ -644,6 +673,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION insert_range(_Rg&& __rg) { _M_insert(ranges::begin(__rg), ranges::end(__rg)); } + template<__detail::__container_compatible_range<value_type> _Rg> + _GLIBCXX26_CONSTEXPR + void + insert_range(__sorted_t, _Rg&& __rg) + { _M_insert(ranges::begin(__rg), ranges::end(__rg), true); } + _GLIBCXX26_CONSTEXPR void insert(initializer_list<value_type> __il) @@ -728,11 +763,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _GLIBCXX26_CONSTEXPR void - swap(_Derived& __y) noexcept + swap(_Derived& __y) + noexcept(is_nothrow_swappable_v<key_container_type> + && is_nothrow_swappable_v<mapped_container_type> + && is_nothrow_swappable_v<key_compare>) { - ranges::swap(_M_comp, __y._M_comp); + auto __guard = _M_make_clear_guard(); + auto __guard_y = _ClearGuard{__y._M_cont}; ranges::swap(_M_cont.keys, __y._M_cont.keys); ranges::swap(_M_cont.values, __y._M_cont.values); + ranges::swap(_M_comp, __y._M_comp); + __guard._M_disable(); + __guard_y._M_disable(); } _GLIBCXX26_CONSTEXPR @@ -974,7 +1016,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION } friend _GLIBCXX26_CONSTEXPR void - swap(_Derived& __x, _Derived& __y) noexcept + swap(_Derived& __x, _Derived& __y) noexcept(noexcept(__x.swap(__y))) { return __x.swap(__y); } template<typename _Predicate> diff --git a/libstdc++-v3/include/std/flat_set b/libstdc++-v3/include/std/flat_set index 286290896ae0..e92559949ebb 100644 --- a/libstdc++-v3/include/std/flat_set +++ b/libstdc++-v3/include/std/flat_set @@ -71,7 +71,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION class _Flat_set_impl { static_assert(is_same_v<_Key, typename _KeyContainer::value_type>); - static_assert(is_nothrow_swappable_v<_KeyContainer>); using _Derived = __conditional_t<_Multi, flat_multiset<_Key, _Compare, _KeyContainer>, @@ -324,6 +323,38 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION : _Flat_set_impl(__s, __il.begin(), __il.end(), __comp, __a) { } + _Flat_set_impl(const _Flat_set_impl&) = default; + _Flat_set_impl& operator=(const _Flat_set_impl&) = default; + + _GLIBCXX26_CONSTEXPR + _Flat_set_impl(_Flat_set_impl&& __other) + noexcept(is_nothrow_move_constructible_v<container_type> + && is_nothrow_move_constructible_v<key_compare>) +#if __cpp_exceptions + try +#endif + : _M_cont(std::move(__other._M_cont)), _M_comp(std::move(__other._M_comp)) + { __other.clear(); } +#if __cpp_exceptions + catch (...) + { __other.clear(); } +#endif + + _GLIBCXX26_CONSTEXPR + _Flat_set_impl& + operator=(_Flat_set_impl&& __other) + noexcept(is_nothrow_move_assignable_v<container_type> + && is_nothrow_move_assignable_v<key_compare>) + { + auto __guard = _M_make_clear_guard(); + auto __guard_other = _ClearGuard{__other._M_cont}; + _M_cont = std::move(__other._M_cont); + _M_comp = std::move(__other._M_comp); + __guard._M_disable(); + // __guard_other._M_disable is deliberately not called. + return *this; + } + _GLIBCXX26_CONSTEXPR _Derived& operator=(initializer_list<value_type> __il) @@ -540,10 +571,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION __guard._M_disable(); } - template<__detail::__container_compatible_range<value_type> _Rg> + template<typename _Rg> _GLIBCXX26_CONSTEXPR void - insert_range(_Rg&& __rg) + _M_insert_range(_Rg&& __rg, bool __is_sorted = false) { auto __guard = _M_make_clear_guard(); typename container_type::iterator __it; @@ -561,13 +592,28 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _M_cont.emplace_back(*__first); __it = _M_cont.begin() + __n; } - std::sort(__it, _M_cont.end(), _M_comp); + if (__is_sorted) + _GLIBCXX_DEBUG_ASSERT(ranges::is_sorted(__it, _M_cont.end(), _M_comp)); + else + std::sort(__it, _M_cont.end(), _M_comp); std::inplace_merge(_M_cont.begin(), __it, _M_cont.end(), _M_comp); if constexpr (!_Multi) _M_unique(); __guard._M_disable(); } + template<__detail::__container_compatible_range<value_type> _Rg> + _GLIBCXX26_CONSTEXPR + void + insert_range(_Rg&& __rg) + { _M_insert_range(std::forward<_Rg>(__rg)); } + + template<__detail::__container_compatible_range<value_type> _Rg> + _GLIBCXX26_CONSTEXPR + void + insert_range(__sorted_t, _Rg&& __rg) + { _M_insert_range(std::forward<_Rg>(__rg), true); } + _GLIBCXX26_CONSTEXPR void insert(initializer_list<value_type> __il) @@ -628,11 +674,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _GLIBCXX26_CONSTEXPR void - swap(_Derived& __x) noexcept + swap(_Derived& __y) + noexcept(is_nothrow_swappable_v<container_type> + && is_nothrow_swappable_v<key_compare>) { - using std::swap; - swap(_M_cont, __x._M_cont); - swap(_M_comp, __x._M_comp); + auto __guard = _M_make_clear_guard(); + auto __guard_y = _ClearGuard{__y._M_cont}; + ranges::swap(_M_comp, __y._M_comp); + ranges::swap(_M_cont, __y._M_cont); + __guard._M_disable(); + __guard_y._M_disable(); } _GLIBCXX26_CONSTEXPR @@ -830,7 +881,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION } friend _GLIBCXX26_CONSTEXPR void - swap(_Derived& __x, _Derived& __y) noexcept + swap(_Derived& __x, _Derived& __y) noexcept(noexcept(__x.swap(__y))) { return __x.swap(__y); } template<typename _Predicate> diff --git a/libstdc++-v3/testsuite/23_containers/flat_map/1.cc b/libstdc++-v3/testsuite/23_containers/flat_map/1.cc index 1cb088f511eb..7e22e0366ec2 100644 --- a/libstdc++-v3/testsuite/23_containers/flat_map/1.cc +++ b/libstdc++-v3/testsuite/23_containers/flat_map/1.cc @@ -3,7 +3,7 @@ #include <flat_map> -#if __cpp_lib_flat_map != 202207L +#if __cpp_lib_flat_map != 202511L # error "Feature-test macro __cpp_lib_flat_map has wrong value in <flat_map>" #endif @@ -299,6 +299,103 @@ test10() VERIFY (k == "world"); } +template<typename T> +struct throwing_vector : std::vector<T> +{ + static inline bool throw_on_move = false; + + throwing_vector() = default; + throwing_vector(const throwing_vector&) = default; + throwing_vector& operator=(const throwing_vector&) = default; + + throwing_vector(throwing_vector&& other) + : std::vector<T>(std::move(other)) + { + if (throw_on_move) + throw std::runtime_error("move ctor"); + } + + throwing_vector& + operator=(throwing_vector&& other) + { + static_cast<std::vector<T>&>(*this) = std::move(other); + if (throw_on_move) + throw std::runtime_error("move assign"); + return *this; + } +}; + +template<template<typename> class KC, template<typename> class MC> +void +test11() +{ +#if __cpp_exceptions + using flat_map = std::flat_map<int, int, std::less<int>, KC<int>, MC<int>>; + + auto is_really_empty = [](const flat_map& m) { + return m.empty() && m.keys().empty() && m.values().empty(); + }; + throwing_vector<int>::throw_on_move = true; + + // Verify invariant preservation upon throwing move construction. + flat_map source; + source.insert({{1, 100}, {2, 200}}); + try + { + flat_map target(std::move(source)); + VERIFY( false ); + } + catch (const std::runtime_error&) + { + VERIFY( is_really_empty(source) ); + } + + // Verify invariant preservation upon throwing move assignment. + source.clear(); + source.insert({{1, 100}, {2, 200}}); + flat_map target; + target.insert({{3, 300}, {4, 400}}); + try + { + target = std::move(source); + VERIFY( false ); + } + catch (const std::runtime_error&) + { + VERIFY( is_really_empty(source) ); + VERIFY( is_really_empty(target) ); + } + + // Verify invariant preservation upon throwing swap. + source.clear(); + source.insert({{1, 100}, {2, 200}}); + target.clear(); + target.insert({{3, 300}, {4, 400}}); + try + { + source.swap(target); + VERIFY( false ); + } + catch (const std::runtime_error&) + { + VERIFY( is_really_empty(source) ); + VERIFY( is_really_empty(target) ); + } +#endif +} + +constexpr +void +test12() +{ + // Verify usability of flat_map::insert_range(sorted_unique_t, Rg&&). + std::flat_map<int, int> m = {{2, 200}}; + std::pair<int, int> s[] = {{1, 100}, {3, 300}}; + m.insert_range(std::sorted_unique, s); + VERIFY( std::ranges::equal(m.keys(), (int[]){1, 2, 3}) ); + VERIFY( std::ranges::equal(m.values(), (int[]){100, 200, 300}) ); +} + void test() { @@ -315,6 +412,9 @@ test() test08(); test09(); test10(); + test11<std::vector, throwing_vector>(); + test11<throwing_vector, std::vector>(); + test12(); } constexpr @@ -333,6 +433,8 @@ test_constexpr() #if __cpp_lib_constexpr_string >= 201907L test10(); #endif + // test11() is non-constexpr + test12(); return true; } diff --git a/libstdc++-v3/testsuite/23_containers/flat_multimap/1.cc b/libstdc++-v3/testsuite/23_containers/flat_multimap/1.cc index 00644c14061a..48688b7583ed 100644 --- a/libstdc++-v3/testsuite/23_containers/flat_multimap/1.cc +++ b/libstdc++-v3/testsuite/23_containers/flat_multimap/1.cc @@ -252,6 +252,103 @@ test09() using value_type = std::pair<int, int>; } +template<typename T> +struct throwing_vector : std::vector<T> +{ + static inline bool throw_on_move = false; + + throwing_vector() = default; + throwing_vector(const throwing_vector&) = default; + throwing_vector& operator=(const throwing_vector&) = default; + + throwing_vector(throwing_vector&& other) + : std::vector<T>(std::move(other)) + { + if (throw_on_move) + throw std::runtime_error("move ctor"); + } + + throwing_vector& + operator=(throwing_vector&& other) + { + static_cast<std::vector<T>&>(*this) = std::move(other); + if (throw_on_move) + throw std::runtime_error("move assign"); + return *this; + } +}; + +template<template<typename> class KC, template<typename> class MC> +void +test10() +{ +#if __cpp_exceptions + using flat_multimap = std::flat_multimap<int, int, std::less<int>, KC<int>, MC<int>>; + + auto is_really_empty = [](const flat_multimap& m) { + return m.empty() && m.keys().empty() && m.values().empty(); + }; + throwing_vector<int>::throw_on_move = true; + + // Verify invariant preservation upon throwing move construction. + flat_multimap source; + source.insert({{1, 100}, {2, 200}}); + try + { + flat_multimap target(std::move(source)); + VERIFY( false ); + } + catch (const std::runtime_error&) + { + VERIFY( is_really_empty(source) ); + } + + // Verify invariant preservation upon throwing move assignment. + source.clear(); + source.insert({{1, 100}, {2, 200}}); + flat_multimap target; + target.insert({{3, 300}, {4, 400}}); + try + { + target = std::move(source); + VERIFY( false ); + } + catch (const std::runtime_error&) + { + VERIFY( is_really_empty(source) ); + VERIFY( is_really_empty(target) ); + } + + // Verify invariant preservation upon throwing swap. + source.clear(); + source.insert({{1, 100}, {2, 200}}); + target.clear(); + target.insert({{3, 300}, {4, 400}}); + try + { + source.swap(target); + VERIFY( false ); + } + catch (const std::runtime_error&) + { + VERIFY( is_really_empty(source) ); + VERIFY( is_really_empty(target) ); + } +#endif +} + +constexpr +void +test11() +{ + // Verify usability of flat_multimap::insert_range(sorted_equivalent_t, Rg&&). + std::flat_multimap<int, int> m = {{2, 200}};; + std::pair<int, int> s[] = {{1, 100}, {3, 300}}; + m.insert_range(std::sorted_equivalent, s); + VERIFY( std::ranges::equal(m.keys(), (int[]){1, 2, 3}) ); + VERIFY( std::ranges::equal(m.values(), (int[]){100, 200, 300}) ); +} + void test() { @@ -266,6 +363,9 @@ test() test06(); test07(); test09(); + test10<std::vector, throwing_vector>(); + test10<throwing_vector, std::vector>(); + test11(); } constexpr @@ -280,6 +380,8 @@ test_constexpr() test06(); test07(); test09(); + // test10() is non-constexpr + test11(); return true; } diff --git a/libstdc++-v3/testsuite/23_containers/flat_multiset/1.cc b/libstdc++-v3/testsuite/23_containers/flat_multiset/1.cc index be330fefdd05..4544adb50770 100644 --- a/libstdc++-v3/testsuite/23_containers/flat_multiset/1.cc +++ b/libstdc++-v3/testsuite/23_containers/flat_multiset/1.cc @@ -265,6 +265,96 @@ test09() VERIFY( std::ranges::equal(s, (int[]){2,2,4}) ); } +template<typename T> +struct throwing_vector : std::vector<T> +{ + static inline bool throw_on_move = false; + + throwing_vector() = default; + throwing_vector(const throwing_vector&) = default; + throwing_vector& operator=(const throwing_vector&) = default; + + throwing_vector(throwing_vector&& other) + : std::vector<T>(std::move(other)) + { + if (throw_on_move) + throw std::runtime_error("move ctor"); + } + + throwing_vector& + operator=(throwing_vector&& other) + { + static_cast<std::vector<T>&>(*this) = std::move(other); + if (throw_on_move) + throw std::runtime_error("move assign"); + return *this; + } +}; + +void +test10() +{ +#if __cpp_exceptions + using flat_multiset = std::flat_multiset<int, std::less<int>, throwing_vector<int>>; + + throwing_vector<int>::throw_on_move = true; + + // Verify invariant preservation upon throwing move construction. + flat_multiset source = {1, 2}; + try + { + flat_multiset target(std::move(source)); + VERIFY( false ); + } + catch (const std::runtime_error&) + { + VERIFY( source.empty() ); + } + + // Verify invariant preservation upon throwing move assignment. + source.clear(); + source.insert({1, 2}); + flat_multiset target = {3, 4}; + try + { + target = std::move(source); + VERIFY( false ); + } + catch (const std::runtime_error&) + { + VERIFY( source.empty() ); + VERIFY( target.empty() ); + } + + // Verify invariant preservation upon throwing swap. + source.clear(); + source.insert({1, 2}); + target.clear(); + target.insert({3, 4}); + try + { + source.swap(target); + VERIFY( false ); + } + catch (const std::runtime_error&) + { + VERIFY( source.empty() ); + VERIFY( target.empty() ); + } +#endif +} + +constexpr +void +test11() +{ + // Verify usability of flat_multiset::insert_range(sorted_equivalent_t, Rg&&). + std::flat_multiset<int> m = {2}; + int s[] = {1, 3}; + m.insert_range(std::sorted_equivalent, s); + VERIFY( std::ranges::equal(m, (int[]){1, 2, 3}) ); +} + void test() { @@ -278,6 +368,8 @@ test() test07(); test08(); test09(); + test10(); + test11(); } constexpr @@ -292,6 +384,8 @@ test_constexpr() test07(); test08(); test09(); + // test10() is non-constexpr + test11(); return true; } diff --git a/libstdc++-v3/testsuite/23_containers/flat_set/1.cc b/libstdc++-v3/testsuite/23_containers/flat_set/1.cc index a14d03476e18..ac12815bd0d4 100644 --- a/libstdc++-v3/testsuite/23_containers/flat_set/1.cc +++ b/libstdc++-v3/testsuite/23_containers/flat_set/1.cc @@ -2,7 +2,7 @@ #include <flat_set> -#if __cpp_lib_flat_set != 202207L +#if __cpp_lib_flat_set != 202511L # error "Feature-test macro __cpp_lib_flat_set has wrong value in <flat_set>" #endif @@ -277,6 +277,96 @@ test09() VERIFY( std::ranges::equal(s, (int[]){2,4}) ); } +template<typename T> +struct throwing_vector : std::vector<T> +{ + static inline bool throw_on_move = false; + + throwing_vector() = default; + throwing_vector(const throwing_vector&) = default; + throwing_vector& operator=(const throwing_vector&) = default; + + throwing_vector(throwing_vector&& other) + : std::vector<T>(std::move(other)) + { + if (throw_on_move) + throw std::runtime_error("move ctor"); + } + + throwing_vector& + operator=(throwing_vector&& other) + { + static_cast<std::vector<T>&>(*this) = std::move(other); + if (throw_on_move) + throw std::runtime_error("move assign"); + return *this; + } +}; + +void +test10() +{ +#if __cpp_exceptions + using flat_set = std::flat_set<int, std::less<int>, throwing_vector<int>>; + + throwing_vector<int>::throw_on_move = true; + + // Verify invariant preservation upon throwing move construction. + flat_set source = {1, 2}; + try + { + flat_set target(std::move(source)); + VERIFY( false ); + } + catch (const std::runtime_error&) + { + VERIFY( source.empty() ); + } + + // Verify invariant preservation upon throwing move assignment. + source.clear(); + source.insert({1, 2}); + flat_set target = {3, 4}; + try + { + target = std::move(source); + VERIFY( false ); + } + catch (const std::runtime_error&) + { + VERIFY( source.empty() ); + VERIFY( target.empty() ); + } + + // Verify invariant preservation upon throwing swap. + source.clear(); + source.insert({1, 2}); + target.clear(); + target.insert({3, 4}); + try + { + source.swap(target); + VERIFY( false ); + } + catch (const std::runtime_error&) + { + VERIFY( source.empty() ); + VERIFY( target.empty() ); + } +#endif +} + +constexpr +void +test11() +{ + // Verify usability of flat_set::insert_range(sorted_unique_t, Rg&&). + std::flat_set<int> m = {2}; + int s[] = {1, 3}; + m.insert_range(std::sorted_unique, s); + VERIFY( std::ranges::equal(m, (int[]){1, 2, 3}) ); +} + void test() { @@ -290,6 +380,8 @@ test() test07(); test08(); test09(); + test10(); + test11(); } constexpr @@ -304,6 +396,8 @@ test_constexpr() test07(); test08(); test09(); + // test10() is non-constexpr + test11(); return true; }
