From 1fc8f31544fe29356702eab9add9c6e3c3ecbb4a Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Tue, 12 May 2020 12:24:36 -0700 Subject: [PATCH 01/13] Implement ranges::is_permutation Includes implementation of concepts `indirect_equivalence_relation` and `indirect_strict_weak_order`, and new `` helper `_Find_last_iterator(first, last, count)` which - given `[first, last)` of length `count` - returns the iterator that corresponds to `last`. Drive-by: * Move `std::is_permutation` from `` into ``, leaving behind the `_Check_match_counts` helper which is used elsewhere in the STL. * Allow clang-format to handle the algorithm result types since they now have no requires-clauses. --- stl/inc/algorithm | 374 ++++++++++++++++-- stl/inc/xutility | 155 ++------ tests/std/test.lst | 1 + .../P0896R4_ranges_alg_is_permutation/env.lst | 4 + .../test.cpp | 105 +++++ .../test.cpp | 34 +- .../test.cpp | 4 +- tests/std/tests/P0898R3_concepts/test.cpp | 4 +- 8 files changed, 523 insertions(+), 158 deletions(-) create mode 100644 tests/std/tests/P0896R4_ranges_alg_is_permutation/env.lst create mode 100644 tests/std/tests/P0896R4_ranges_alg_is_permutation/test.cpp diff --git a/stl/inc/algorithm b/stl/inc/algorithm index b6a009c14d7..22c3aeb8029 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -89,9 +89,8 @@ namespace ranges { [[no_unique_address]] _In in; [[no_unique_address]] _Fn fun; - // clang-format off template <_Convertible_from _IIn, _Convertible_from _FFn> - constexpr operator in_fun_result<_IIn, _FFn>() const & { + constexpr operator in_fun_result<_IIn, _FFn>() const& { return {in, fun}; } @@ -99,7 +98,6 @@ namespace ranges { constexpr operator in_fun_result<_IIn, _FFn>() && { return {_STD move(in), _STD move(fun)}; } - // clang-format on }; // STRUCT TEMPLATE in_in_result @@ -108,9 +106,8 @@ namespace ranges { [[no_unique_address]] _In1 in1; [[no_unique_address]] _In2 in2; - // clang-format off template <_Convertible_from _IIn1, _Convertible_from _IIn2> - constexpr operator in_in_result<_IIn1, _IIn2>() const & { + constexpr operator in_in_result<_IIn1, _IIn2>() const& { return {in1, in2}; } @@ -118,7 +115,6 @@ namespace ranges { constexpr operator in_in_result<_IIn1, _IIn2>() && { return {_STD move(in1), _STD move(in2)}; } - // clang-format on }; // STRUCT TEMPLATE in_out_result @@ -127,9 +123,8 @@ namespace ranges { [[no_unique_address]] _In in; [[no_unique_address]] _Out out; - // clang-format off template <_Convertible_from _IIn, _Convertible_from _OOut> - constexpr operator in_out_result<_IIn, _OOut>() const & { + constexpr operator in_out_result<_IIn, _OOut>() const& { return {in, out}; } @@ -137,7 +132,6 @@ namespace ranges { constexpr operator in_out_result<_IIn, _OOut>() && { return {_STD move(in), _STD move(out)}; } - // clang-format on }; // STRUCT TEMPLATE in_in_out_result @@ -147,10 +141,9 @@ namespace ranges { [[no_unique_address]] _In2 in2; [[no_unique_address]] _Out out; - // clang-format off template <_Convertible_from _IIn1, _Convertible_from _IIn2, _Convertible_from _OOut> - constexpr operator in_in_out_result<_IIn1, _IIn2, _OOut>() const & { + constexpr operator in_in_out_result<_IIn1, _IIn2, _OOut>() const& { return {in1, in2, out}; } @@ -158,7 +151,6 @@ namespace ranges { constexpr operator in_in_out_result<_IIn1, _IIn2, _OOut>() && { return {_STD move(in1), _STD move(in2), _STD move(out)}; } - // clang-format on }; // STRUCT TEMPLATE in_out_out_result @@ -168,10 +160,9 @@ namespace ranges { [[no_unique_address]] _Out1 out1; [[no_unique_address]] _Out2 out2; - // clang-format off template <_Convertible_from _IIn, _Convertible_from _OOut1, _Convertible_from _OOut2> - constexpr operator in_out_out_result<_IIn, _OOut1, _OOut2>() const & { + constexpr operator in_out_out_result<_IIn, _OOut1, _OOut2>() const& { return {in, out1, out2}; } @@ -179,7 +170,6 @@ namespace ranges { constexpr operator in_out_out_result<_IIn, _OOut1, _OOut2>() && { return {_STD move(in), _STD move(out1), _STD move(out2)}; } - // clang-format on }; // STRUCT TEMPLATE min_max_result @@ -188,9 +178,8 @@ namespace ranges { [[no_unique_address]] _Ty min; [[no_unique_address]] _Ty max; - // clang-format off template <_Convertible_from _Ty2> - constexpr operator min_max_result<_Ty2>() const & { + constexpr operator min_max_result<_Ty2>() const& { return {min, max}; } @@ -198,7 +187,6 @@ namespace ranges { constexpr operator min_max_result<_Ty2>() && { return {_STD move(min), _STD move(max)}; } - // clang-format on }; // STRUCT TEMPLATE in_found_result @@ -207,9 +195,8 @@ namespace ranges { [[no_unique_address]] _In in; bool found; - // clang-format off template <_Convertible_from _IIn> - constexpr operator in_found_result<_IIn>() const & { + constexpr operator in_found_result<_IIn>() const& { return {in, found}; } @@ -217,7 +204,6 @@ namespace ranges { constexpr operator in_found_result<_IIn>() && { return {_STD move(in), found}; } - // clang-format on }; #ifdef __clang__ @@ -379,7 +365,6 @@ namespace ranges { public: using _Not_quite_object::_Not_quite_object; - // clang-format off template _Se, class _Pj = identity, indirect_unary_predicate> _Pr> _NODISCARD constexpr _It operator()(_It _First, _Se _Last, _Pr _Pred, _Pj _Proj = {}) const { @@ -401,7 +386,6 @@ namespace ranges { _NODISCARD constexpr borrowed_iterator_t<_Rng> operator()(_Rng&& _Range, _Pr _Pred, _Pj _Proj = {}) const { return (*this)(_RANGES begin(_Range), _RANGES end(_Range), _Pass_fn(_Pred), _Pass_fn(_Proj)); } - // clang-format on }; inline constexpr _Find_if_fn find_if{_Not_quite_object::_Construct_tag{}}; @@ -437,7 +421,6 @@ namespace ranges { public: using _Not_quite_object::_Not_quite_object; - // clang-format off template _Se, class _Pj = identity, indirect_unary_predicate> _Pr> _NODISCARD constexpr _It operator()(_It _First, _Se _Last, _Pr _Pred, _Pj _Proj = {}) const { @@ -459,7 +442,6 @@ namespace ranges { _NODISCARD constexpr borrowed_iterator_t<_Rng> operator()(_Rng&& _Range, _Pr _Pred, _Pj _Proj = {}) const { return (*this)(_RANGES begin(_Range), _RANGES end(_Range), _Pass_fn(_Pred), _Pass_fn(_Proj)); } - // clang-format on }; inline constexpr _Find_if_not_fn find_if_not{_Not_quite_object::_Construct_tag{}}; @@ -611,7 +593,6 @@ namespace ranges { public: using _Not_quite_object::_Not_quite_object; - // clang-format off template _Se, class _Pj = identity, indirect_unary_predicate> _Pr> _NODISCARD constexpr iter_difference_t<_It> operator()(_It _First, _Se _Last, _Pr _Pred, _Pj _Proj = {}) const { @@ -633,7 +614,6 @@ namespace ranges { _NODISCARD constexpr range_difference_t<_Rng> operator()(_Rng&& _Range, _Pr _Pred, _Pj _Proj = {}) const { return (*this)(_RANGES begin(_Range), _RANGES end(_Range), _Pass_fn(_Pred), _Pass_fn(_Proj)); } - // clang-format on }; inline constexpr _Count_if_fn count_if{_Not_quite_object::_Construct_tag{}}; @@ -961,6 +941,340 @@ namespace ranges { } // namespace ranges #endif // __cpp_lib_concepts +// FUNCTION TEMPLATE is_permutation +template +_NODISCARD _CONSTEXPR20 bool _Is_permutation_unchecked(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _Pr _Pred) { + // test if [_First1, _Last1) == permuted [_First2, ...), using _Pred + for (; _First1 != _Last1; ++_First1, (void) ++_First2) { // trim matching prefix + if (!_Pred(*_First1, *_First2)) { + // found first inequality, check match counts in suffix + // + // narrowing _Iter_diff_t<_FwdIt1> to _Iter_diff_t<_FwdIt2> is OK because if the second range is shorter + // than the first, the user already triggered UB + auto _Last2 = _STD next(_First2, static_cast<_Iter_diff_t<_FwdIt2>>(_STD distance(_First1, _Last1))); + return _Check_match_counts(_First1, _Last1, _First2, _Last2, _Pred); + } + } + + return true; +} + +template +_NODISCARD _CONSTEXPR20 bool is_permutation(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _Pr _Pred) { + // test if [_First1, _Last1) == permuted [_First2, ...), using _Pred + _Adl_verify_range(_First1, _Last1); + const auto _UFirst1 = _Get_unwrapped(_First1); + const auto _ULast1 = _Get_unwrapped(_Last1); + const auto _UFirst2 = _Get_unwrapped_n(_First2, _Idl_distance<_FwdIt1>(_UFirst1, _ULast1)); + return _Is_permutation_unchecked(_UFirst1, _ULast1, _UFirst2, _Pass_fn(_Pred)); +} + +template +_NODISCARD _CONSTEXPR20 bool is_permutation(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2) { + // test if [_First1, _Last1) == permuted [_First2, ...) + return _STD is_permutation(_First1, _Last1, _First2, equal_to<>()); +} + +template +_CONSTEXPR20 bool _Is_permutation_unchecked(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _FwdIt2 _Last2, _Pr _Pred, + forward_iterator_tag, forward_iterator_tag) { + // test if [_First1, _Last1) == permuted [_First2, _Last2), using _Pred, arbitrary iterators + for (;;) { // trim matching prefix + if (_First1 == _Last1) { + return _First2 == _Last2; + } + + if (_First2 == _Last2) { + return false; + } + + if (!_Pred(*_First1, *_First2)) { // found first inequality, check match counts in suffix + break; + } + + ++_First1; + ++_First2; + } + + auto _Next1 = _First1; + auto _Next2 = _First2; + for (;;) { // check for same lengths + if (_Next1 == _Last1) { + if (_Next2 == _Last2) { + return _Check_match_counts(_First1, _Last1, _First2, _Last2, _Pred); + } + + return false; // sequence 1 is shorter than sequence 2, not a permutation + } + + if (_Next2 == _Last2) { + return false; // sequence 1 is longer than sequence 2, not a permutation + } + + ++_Next1; + ++_Next2; + } +} + +template +_CONSTEXPR20 bool _Is_permutation_unchecked(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _FwdIt2 _Last2, _Pr _Pred, + random_access_iterator_tag, random_access_iterator_tag) { + // test if [_First1, _Last1) == permuted [_First2, _Last2), using _Pred, random-access iterators + if (_Last1 - _First1 != _Last2 - _First2) { + return false; + } + + for (; _First1 != _Last1; ++_First1, (void) ++_First2) { // trim matching prefix + if (!_Pred(*_First1, *_First2)) { + // found first inequality, check match counts in suffix + return _Check_match_counts(_First1, _Last1, _First2, _Last2, _Pred); + } + } + + return true; +} + +template +_NODISCARD _CONSTEXPR20 bool is_permutation( + _FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _FwdIt2 _Last2, _Pr _Pred) { + // test if [_First1, _Last1) == permuted [_First2, _Last2), using _Pred + _Adl_verify_range(_First1, _Last1); + _Adl_verify_range(_First2, _Last2); + return _Is_permutation_unchecked(_Get_unwrapped(_First1), _Get_unwrapped(_Last1), _Get_unwrapped(_First2), + _Get_unwrapped(_Last2), _Pass_fn(_Pred), _Iter_cat_t<_FwdIt1>(), _Iter_cat_t<_FwdIt2>()); +} + +template +_NODISCARD _CONSTEXPR20 bool is_permutation(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _FwdIt2 _Last2) { + // test if [_First1, _Last1) == permuted [_First2, _Last2) + return _STD is_permutation(_First1, _Last1, _First2, _Last2, equal_to<>()); +} + +#ifdef __cpp_lib_concepts +namespace ranges { + // clang-format off + template + concept _Bidi_common = is_same_v<_It, _Se> && bidirectional_iterator<_It>; + template + concept _Bidi_common_range = common_range<_Rng>&& bidirectional_iterator>; + // clang-format on + + // VARIABLE ranges::is_permutation + class _Is_permutation_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + template _Se1, forward_iterator _It2, sentinel_for<_It2> _Se2, + class _Pj1 = identity, class _Pj2 = identity, + indirect_equivalence_relation, projected<_It2, _Pj2>> _Pr = ranges::equal_to> + _NODISCARD constexpr bool operator()(_It1 _First1, _Se1 _Last1, _It2 _First2, _Se2 _Last2, _Pr _Pred = {}, + _Pj1 _Proj1 = {}, _Pj2 _Proj2 = {}) const { + _Adl_verify_range(_First1, _Last1); + _Adl_verify_range(_First2, _Last2); + auto _UFirst1 = _Get_unwrapped(_STD move(_First1)); + auto _ULast1 = _Get_unwrapped(_STD move(_Last1)); + auto _UFirst2 = _Get_unwrapped(_STD move(_First2)); + auto _ULast2 = _Get_unwrapped(_STD move(_Last2)); + + if constexpr (sized_sentinel_for<_Se1, _It1> && sized_sentinel_for<_Se2, _It2>) { + const auto _Count = _ULast1 - _UFirst1; + if (_ULast2 - _UFirst2 != _Count) { + return false; + } + + return _Sized_ranges(_STD move(_UFirst1), _STD move(_ULast1), _STD move(_UFirst2), _STD move(_ULast2), + _Count, _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); + } else { + return _Unsized_ranges(_STD move(_UFirst1), _STD move(_ULast1), _STD move(_UFirst2), _STD move(_ULast2), + _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); + } + } + + template , _Pj1>, projected, _Pj2>> _Pr = + ranges::equal_to> + _NODISCARD constexpr bool operator()( + _Rng1&& _Range1, _Rng2&& _Range2, _Pr _Pred = {}, _Pj1 _Proj1 = {}, _Pj2 _Proj2 = {}) const { + if constexpr (sized_range<_Rng1> && sized_range<_Rng2>) { + const auto _Count = _RANGES distance(_Range1); + if (_RANGES distance(_Range2) != _Count) { + return false; + } + + return _Sized_ranges(_Get_unwrapped(_RANGES begin(_Range1)), _Get_unwrapped(_RANGES end(_Range1)), + _Get_unwrapped(_RANGES begin(_Range2)), _Get_unwrapped(_RANGES end(_Range2)), _Count, + _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); + } else { + return _Unsized_ranges(_Get_unwrapped(_RANGES begin(_Range1)), _Get_unwrapped(_RANGES end(_Range1)), + _Get_unwrapped(_RANGES begin(_Range2)), _Get_unwrapped(_RANGES end(_Range2)), _Pass_fn(_Pred), + _Pass_fn(_Proj1), _Pass_fn(_Proj2)); + } + } + + private: + template + _NODISCARD static constexpr bool _Sized_ranges(_It1 _First1, _Se1 _End1, _It2 _First2, _Se2 _End2, + iter_difference_t<_It1> _Count, _Pr _Pred, _Pj1 _Proj1, _Pj2 _Proj2) { + _STL_INTERNAL_CHECK(_RANGES distance(_First1, _End1) == _Count); + _STL_INTERNAL_CHECK(_RANGES distance(_First2, _End2) == _Count); + + for (;;) { // Trim matching prefixes + if (_Count == 0) { // Everything matched + return true; + } + + if (!_STD invoke(_Pred, _STD invoke(_Proj1, *_First1), _STD invoke(_Proj2, *_First2))) { // mismatch + if (_Count == 1) { // Single non-matching characters remain; not a permutation + return false; + } + + break; + } + + ++_First1; + ++_First2; + --_Count; + } + // If we get here, _Count > 1 and initial elements do not match. + + if constexpr (bidirectional_iterator<_It1> && bidirectional_iterator<_It2>) { + // Determine final iterator values + auto _Last1 = _Find_last_iterator(_First1, _End1, _Count); + auto _Last2 = _Find_last_iterator(_First2, _End2, _Count); + + for (;;) { // Trim matching suffixes + --_Last1; + --_Last2; + if (!_STD invoke(_Pred, _STD invoke(_Proj1, *_Last1), _STD invoke(_Proj2, *_Last2))) { // mismatch + ++_Last1; + ++_Last2; + break; + } + + if (--_Count == 1) { + return false; // initial elements did not match when trimming prefixes + } + } + // If we get here, _Count > 1, initial elements do not match, and final elements do not match. + + return _Match_counts(_STD move(_First1), _STD move(_Last1), _STD move(_First2), _STD move(_Last2), + _Pred, _Proj1, _Proj2); + } else { + return _Match_counts( + _STD move(_First1), _STD move(_End1), _STD move(_First2), _STD move(_End2), _Pred, _Proj1, _Proj2); + } + } + + template + _NODISCARD static constexpr bool _Unsized_ranges( + _It1 _First1, _Se1 _End1, _It2 _First2, _Se2 _End2, _Pr _Pred, _Pj1 _Proj1, _Pj2 _Proj2) { + for (;;) { // Trim matching prefixes + if (_First1 == _End1) { // first range is a (possibly proper?) prefix of the second + return _First2 == _End2; + } else if (_First2 == _End2) { // second range is a proper prefix of the first + return false; + } + + if (!_STD invoke(_Pred, _STD invoke(_Proj1, *_First1), _STD invoke(_Proj2, *_First2))) { // mismatch + break; + } + + ++_First1; + ++_First2; + } + // If we get here, initial elements do not match. + + // Determine final iterator values and validate lengths + auto _Last1 = _First1; + auto _Last2 = _First2; + for (;;) { + ++_Last1; + ++_Last2; + if (_Last1 == _End1) { + if (_Last2 == _End2) { + break; // equal lengths + } + + return false; // different lengths; not a permutation + } + + if (_Last2 == _End2) { + return false; // Ditto + } + } + // if we get here, initial elements do not match and ranges have equal lengths. + + if constexpr (bidirectional_iterator<_It1> && bidirectional_iterator<_It2>) { + for (;;) { // Trim matching suffixes + if (--_Last1 == _First1) { + return false; // initial elements did not match when trimming prefixes. + } + if (--_Last2 == _First2) { + return false; // Ditto + } + if (!_STD invoke(_Pred, _STD invoke(_Proj1, *_Last1), _STD invoke(_Proj2, *_Last2))) { // mismatch + ++_Last1; + ++_Last2; + break; + } + } + // If we get here, initial elements do not match, final elements do not match, and ranges have length + // at least 2. + } + + return _Match_counts( + _STD move(_First1), _STD move(_Last1), _STD move(_First2), _STD move(_Last2), _Pred, _Proj1, _Proj2); + } + + template + _NODISCARD static constexpr bool _Match_counts(const _It1 _First1, const _Se1 _Last1, const _It2 _First2, + const _Se2 _Last2, _Pr _Pred, _Pj1 _Proj1, _Pj2 _Proj2) { + for (auto _Mid1 = _First1; _Mid1 != _Last1; ++_Mid1) { + bool _Found = false; + auto _Meow = _First1; + for (; _Meow != _Mid1; ++_Meow) { + if (_STD invoke(_Pred, _STD invoke(_Proj1, *_Mid1), _STD invoke(_Proj1, *_Meow))) { + // the same value appears earlier in the first range; we've already counted occurrences + _Found = true; + break; + } + } + if (_Found) { + continue; + } + + // count occurrences of this value in the first range + iter_difference_t<_It1> _Occurrences = 1; + while (++_Meow != _Last1) { + if (_STD invoke(_Pred, _STD invoke(_Proj1, *_Mid1), _STD invoke(_Proj1, *_Meow))) { + ++_Occurrences; + } + } + + // subtract occurrences in the second range + for (auto _Mid2 = _First2; _Mid2 != _Last2; ++_Mid2) { + if (_STD invoke(_Pred, _STD invoke(_Proj1, *_Mid1), _STD invoke(_Proj2, *_Mid2))) { + if (--_Occurrences < 0) { + // value appears more in second range than first; not a permutation + return false; + } + } + } + + if (_Occurrences != 0) { + // value appears more in first range than second; not a permutation + return false; + } + } + + return true; + } + }; + + inline constexpr _Is_permutation_fn is_permutation{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts + // FUNCTION TEMPLATE all_of template _NODISCARD _CONSTEXPR20 bool all_of(_InIt _First, _InIt _Last, _Pr _Pred) { // test if all elements satisfy _Pred @@ -2048,8 +2362,7 @@ namespace ranges { && random_access_iterator<_It2> && sized_sentinel_for<_Se2, _It2>) { return _Random_access_sized_ranges(_First1, _Last1 - _First1, _First2, _Last2 - _First2, _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); - } else if constexpr (bidirectional_iterator<_It1> && is_same_v<_It1, _Se1> - && bidirectional_iterator<_It2> && is_same_v<_It2, _Se2>) { + } else if constexpr (_Bidi_common<_It1, _Se1> && _Bidi_common<_It2, _Se2>) { return _Bidi_common_ranges(_First1, _Last1, _First2, _Last2, _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); } else { @@ -2069,8 +2382,7 @@ namespace ranges { _RANGES begin(_Range1), _RANGES distance(_Range1), _RANGES begin(_Range2), _RANGES distance(_Range2), _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); - } else if constexpr (bidirectional_range<_Rng1> && common_range<_Rng1> - && bidirectional_range<_Rng2> && common_range<_Rng2>) { + } else if constexpr (_Bidi_common_range<_Rng1> && _Bidi_common_range<_Rng2>) { return _Bidi_common_ranges( _RANGES begin(_Range1), _RANGES end(_Range1), _RANGES begin(_Range2), _RANGES end(_Range2), diff --git a/stl/inc/xutility b/stl/inc/xutility index e3ad16acb42..533a3a945a1 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -921,6 +921,28 @@ concept indirect_binary_predicate = indirectly_readable<_It1> && predicate<_Fn&, iter_reference_t<_It1>, iter_reference_t<_It2>> && predicate<_Fn&, iter_common_reference_t<_It1>, iter_common_reference_t<_It2>>; +// CONCEPT indirect_equivalence_relation +template +concept indirect_equivalence_relation = indirectly_readable<_It1> + && indirectly_readable<_It2> + && copy_constructible<_Fn> + && equivalence_relation<_Fn&, iter_value_t<_It1>&, iter_value_t<_It2>&> + && equivalence_relation<_Fn&, iter_value_t<_It1>&, iter_reference_t<_It2>> + && equivalence_relation<_Fn&, iter_reference_t<_It1>, iter_value_t<_It2>&> + && equivalence_relation<_Fn&, iter_reference_t<_It1>, iter_reference_t<_It2>> + && equivalence_relation<_Fn&, iter_common_reference_t<_It1>, iter_common_reference_t<_It2>>; + +// CONCEPT indirect_strict_weak_order +template +concept indirect_strict_weak_order = indirectly_readable<_It1> + && indirectly_readable<_It2> + && copy_constructible<_Fn> + && strict_weak_order<_Fn&, iter_value_t<_It1>&, iter_value_t<_It2>&> + && strict_weak_order<_Fn&, iter_value_t<_It1>&, iter_reference_t<_It2>> + && strict_weak_order<_Fn&, iter_reference_t<_It1>, iter_value_t<_It2>&> + && strict_weak_order<_Fn&, iter_reference_t<_It1>, iter_reference_t<_It2>> + && strict_weak_order<_Fn&, iter_common_reference_t<_It1>, iter_common_reference_t<_It2>>; + // ALIAS TEMPLATE indirect_result_t template requires (indirectly_readable<_Its> && ...) @@ -1001,15 +1023,16 @@ namespace ranges { private: enum class _St { _None, _Custom, _Swap, _Exchange }; - // clang-format off template _NODISCARD static _CONSTEVAL _Choice_t<_St> _Choose() noexcept { if constexpr (_Has_ADL<_Ty1, _Ty2>) { return {_St::_Custom, noexcept(iter_swap(_STD declval<_Ty1>(), _STD declval<_Ty2>()))}; } else if constexpr (_Can_swap_references<_Ty1, _Ty2>) { return {_St::_Swap, noexcept(_RANGES swap(*_STD declval<_Ty1>(), *_STD declval<_Ty2>()))}; - } else if constexpr (indirectly_movable_storable, remove_reference_t<_Ty2>> - && indirectly_movable_storable, remove_reference_t<_Ty1>>) { + } else if constexpr ( + indirectly_movable_storable, + remove_reference_t< + _Ty2>> && indirectly_movable_storable, remove_reference_t<_Ty1>>) { constexpr auto _Nothrow = noexcept( *_STD declval<_Ty1>() = _Iter_exchange_move(_STD declval<_Ty1>(), _STD declval<_Ty2>())); return {_St::_Exchange, _Nothrow}; @@ -1022,6 +1045,7 @@ namespace ranges { static constexpr _Choice_t<_St> _Choice = _Choose<_Ty1, _Ty2>(); public: + // clang-format off template requires (_Choice<_Ty1, _Ty2>._Strategy != _St::_None) constexpr void operator()(_Ty1&& _Val1, _Ty2&& _Val2) const noexcept(_Choice<_Ty1, _Ty2>._No_throw) { @@ -3148,6 +3172,21 @@ namespace ranges { // VARIABLE ranges::prev inline constexpr _Prev_fn prev{_Not_quite_object::_Construct_tag{}}; + // FUNCTION TEMPLATE _Find_last_iterator + template _Se> + _NODISCARD constexpr _It _Find_last_iterator( + const _It& _First, const _Se& _Last, const iter_difference_t<_It> _Count) { + // Find the iterator in [_First, _Last) (of length _Count) which equals _Last + _STL_INTERNAL_CHECK(_RANGES distance(_First, _Last) == _Count); + auto _Result = _First; + if constexpr (assignable_from<_It&, _Se>) { + _RANGES advance(_Result, _Last); + } else { + _RANGES advance(_Result, _Count); + } + return _Result; + } + // STRUCT ranges::equal_to struct equal_to { // clang-format off @@ -5233,7 +5272,7 @@ _NODISCARD _Iter_diff_t<_FwdIt> count( _ExPo&& _Exec, const _FwdIt _First, const _FwdIt _Last, const _Ty& _Val) noexcept; // terminates #endif // _HAS_CXX17 -// FUNCTION TEMPLATE is_permutation +// FUNCTION TEMPLATE _Check_match_counts template _NODISCARD constexpr _InIt _Find_pr(_InIt _First, const _InIt _Last, const _Ty& _Val, _Pr _Pred) { for (; _First != _Last; ++_First) { @@ -5314,114 +5353,6 @@ _NODISCARD _CONSTEXPR20 bool _Check_match_counts( return true; } -template -_NODISCARD _CONSTEXPR20 bool _Is_permutation_unchecked(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _Pr _Pred) { - // test if [_First1, _Last1) == permuted [_First2, ...), using _Pred - for (; _First1 != _Last1; ++_First1, (void) ++_First2) { // trim matching prefix - if (!_Pred(*_First1, *_First2)) { - // found first inequality, check match counts in suffix - // - // narrowing _Iter_diff_t<_FwdIt1> to _Iter_diff_t<_FwdIt2> is OK because if the second range is shorter - // than the first, the user already triggered UB - auto _Last2 = _STD next(_First2, static_cast<_Iter_diff_t<_FwdIt2>>(_STD distance(_First1, _Last1))); - return _Check_match_counts(_First1, _Last1, _First2, _Last2, _Pred); - } - } - - return true; -} - -template -_NODISCARD _CONSTEXPR20 bool is_permutation(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _Pr _Pred) { - // test if [_First1, _Last1) == permuted [_First2, ...), using _Pred - _Adl_verify_range(_First1, _Last1); - const auto _UFirst1 = _Get_unwrapped(_First1); - const auto _ULast1 = _Get_unwrapped(_Last1); - const auto _UFirst2 = _Get_unwrapped_n(_First2, _Idl_distance<_FwdIt1>(_UFirst1, _ULast1)); - return _Is_permutation_unchecked(_UFirst1, _ULast1, _UFirst2, _Pass_fn(_Pred)); -} - -template -_NODISCARD _CONSTEXPR20 bool is_permutation(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2) { - // test if [_First1, _Last1) == permuted [_First2, ...) - return _STD is_permutation(_First1, _Last1, _First2, equal_to<>()); -} - -template -_CONSTEXPR20 bool _Is_permutation_unchecked(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _FwdIt2 _Last2, _Pr _Pred, - forward_iterator_tag, forward_iterator_tag) { - // test if [_First1, _Last1) == permuted [_First2, _Last2), using _Pred, arbitrary iterators - for (;;) { // trim matching prefix - if (_First1 == _Last1) { - return _First2 == _Last2; - } - - if (_First2 == _Last2) { - return false; - } - - if (!_Pred(*_First1, *_First2)) { // found first inequality, check match counts in suffix - break; - } - - ++_First1; - ++_First2; - } - - auto _Next1 = _First1; - auto _Next2 = _First2; - for (;;) { // check for same lengths - if (_Next1 == _Last1) { - if (_Next2 == _Last2) { - return _Check_match_counts(_First1, _Last1, _First2, _Last2, _Pred); - } - - return false; // sequence 1 is shorter than sequence 2, not a permutation - } - - if (_Next2 == _Last2) { - return false; // sequence 1 is longer than sequence 2, not a permutation - } - - ++_Next1; - ++_Next2; - } -} - -template -_CONSTEXPR20 bool _Is_permutation_unchecked(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _FwdIt2 _Last2, _Pr _Pred, - random_access_iterator_tag, random_access_iterator_tag) { - // test if [_First1, _Last1) == permuted [_First2, _Last2), using _Pred, random-access iterators - if (_Last1 - _First1 != _Last2 - _First2) { - return false; - } - - for (; _First1 != _Last1; ++_First1, (void) ++_First2) { // trim matching prefix - if (!_Pred(*_First1, *_First2)) { - // found first inequality, check match counts in suffix - return _Check_match_counts(_First1, _Last1, _First2, _Last2, _Pred); - } - } - - return true; -} - -template -_NODISCARD _CONSTEXPR20 bool is_permutation( - _FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _FwdIt2 _Last2, _Pr _Pred) { - // test if [_First1, _Last1) == permuted [_First2, _Last2), using _Pred - _Adl_verify_range(_First1, _Last1); - _Adl_verify_range(_First2, _Last2); - return _Is_permutation_unchecked(_Get_unwrapped(_First1), _Get_unwrapped(_Last1), _Get_unwrapped(_First2), - _Get_unwrapped(_Last2), _Pass_fn(_Pred), _Iter_cat_t<_FwdIt1>(), _Iter_cat_t<_FwdIt2>()); -} - -template -_NODISCARD _CONSTEXPR20 bool is_permutation(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _FwdIt2 _Last2) { - // test if [_First1, _Last1) == permuted [_First2, _Last2) - return _STD is_permutation(_First1, _Last1, _First2, _Last2, equal_to<>()); -} - // FUNCTION TEMPLATE reverse #if _HAS_IF_CONSTEXPR template diff --git a/tests/std/test.lst b/tests/std/test.lst index cced084e677..735da610d45 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -249,6 +249,7 @@ tests\P0896R4_ranges_alg_find_if tests\P0896R4_ranges_alg_find_if_not tests\P0896R4_ranges_alg_for_each tests\P0896R4_ranges_alg_for_each_n +tests\P0896R4_ranges_alg_is_permutation tests\P0896R4_ranges_alg_mismatch tests\P0896R4_ranges_alg_none_of tests\P0896R4_ranges_iterator_machinery diff --git a/tests/std/tests/P0896R4_ranges_alg_is_permutation/env.lst b/tests/std/tests/P0896R4_ranges_alg_is_permutation/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_is_permutation/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\concepts_matrix.lst diff --git a/tests/std/tests/P0896R4_ranges_alg_is_permutation/test.cpp b/tests/std/tests/P0896R4_ranges_alg_is_permutation/test.cpp new file mode 100644 index 00000000000..16738c55ad9 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_is_permutation/test.cpp @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +// +#include + +using namespace std; + +struct instantiator { + using P = pair; + + static constexpr array pairs = { + P{0, 42}, P{1, 42}, P{0, 42}, P{1, 42}, P{0, 42}, P{1, 42}, P{0, 42}, P{1, 42}}; + static constexpr array not_pairs = {0, 0, 0, 0, 1, 1, 1, 1}; + + template + static constexpr void call() { + const Fwd1 range1{pairs}; + const Fwd2 range2{not_pairs}; + + // defaulted predicate and projections + assert(ranges::is_permutation(range1, range1)); + assert(ranges::is_permutation( + ranges::begin(range2), ranges::end(range2), ranges::begin(range2), ranges::end(range2))); + + // explicit predicate + assert(ranges::is_permutation(range2, range2, ranges::equal_to{})); + assert(ranges::is_permutation(ranges::begin(range1), ranges::end(range1), ranges::begin(range1), + ranges::end(range1), ranges::equal_to{})); + + // explicit predicate and one projection + assert(ranges::is_permutation(range1, range2, ranges::equal_to{}, get_first)); + assert(ranges::is_permutation(ranges::begin(range1), ranges::end(range1), ranges::begin(range2), + ranges::end(range2), ranges::equal_to{}, get_first)); + + // explicit predicate and two projections + assert(ranges::is_permutation(range1, range2, ranges::equal_to{}, get_first, identity{})); + assert(ranges::is_permutation(ranges::begin(range1), ranges::end(range1), ranges::begin(range2), + ranges::end(range2), ranges::equal_to{}, get_first, identity{})); + + // negative case + assert(!ranges::is_permutation(range1, range2, ranges::equal_to{}, get_second, identity{})); + assert(!ranges::is_permutation(ranges::begin(range1), ranges::end(range1), ranges::begin(range2), + ranges::end(range2), ranges::equal_to{}, get_second, identity{})); + } +}; + +template +using fwd_test_range = test::range; +template +using bidi_test_range = test::range; + +constexpr bool run_tests() { + using Elem1 = const pair; + using Elem2 = const int; + +#ifdef TEST_EVERYTHING + test_fwd_fwd(); +#else // ^^^ test all range combinations // test only interesting range combos vvv + // All proxy reference types, since the algorithm doesn't really care. + using test::Common, test::Sized; + + // both forward non-common and 0-/1-/2-sized + instantiator::call, fwd_test_range>(); + instantiator::call, fwd_test_range>(); + instantiator::call, fwd_test_range>(); + instantiator::call, fwd_test_range>(); + + // both bidi same common and 0-/1-/2-sized + + instantiator::call, bidi_test_range>(); + instantiator::call, + bidi_test_range>(); + instantiator::call, + bidi_test_range>(); + instantiator::call, + bidi_test_range>(); + + instantiator::call, + bidi_test_range>(); + instantiator::call, + bidi_test_range>(); + instantiator::call, + bidi_test_range>(); + instantiator::call, + bidi_test_range>(); +#endif // TEST_EVERYTHING + + return true; +} + +int main() { +#ifndef TEST_EVERYTHING // This crushes the constexpr step limit on our compilers + STATIC_ASSERT(run_tests()); +#endif // TEST_EVERYTHING + run_tests(); +} diff --git a/tests/std/tests/P0896R4_ranges_algorithm_machinery/test.cpp b/tests/std/tests/P0896R4_ranges_algorithm_machinery/test.cpp index d665e3cc7ad..6def8ab5dec 100644 --- a/tests/std/tests/P0896R4_ranges_algorithm_machinery/test.cpp +++ b/tests/std/tests/P0896R4_ranges_algorithm_machinery/test.cpp @@ -164,8 +164,7 @@ namespace indirect_unary_predicate_test { } // namespace indirect_unary_predicate_test namespace indirect_binary_predicate_test { - // Also validate indirect_result_t - using std::indirect_binary_predicate; + // Also validate indirect_equivalence_relation, indirect_strict_weak_order, and indirect_result_t template struct base {}; @@ -196,15 +195,28 @@ namespace indirect_binary_predicate_test { std::false_type operator()(simple_common_reference, simple_common_reference) const requires(I != 5); }; - STATIC_ASSERT(!indirect_binary_predicate, simple_iter_archetype<1>, simple_iter_archetype<1>>); - STATIC_ASSERT(!indirect_binary_predicate, simple_iter_archetype<1>, simple_iter_archetype<1>>); - STATIC_ASSERT(!indirect_binary_predicate, simple_iter_archetype<1>, simple_iter_archetype<1>>); - STATIC_ASSERT(!indirect_binary_predicate, simple_iter_archetype<1>, simple_iter_archetype<1>>); - STATIC_ASSERT(!indirect_binary_predicate, simple_iter_archetype<1>, simple_iter_archetype<1>>); - STATIC_ASSERT(!indirect_binary_predicate, simple_iter_archetype<1>, simple_iter_archetype<1>>); - STATIC_ASSERT(!indirect_binary_predicate, simple_iter_archetype<0>, simple_iter_archetype<1>>); - STATIC_ASSERT(!indirect_binary_predicate, simple_iter_archetype<1>, simple_iter_archetype<0>>); - STATIC_ASSERT(indirect_binary_predicate, simple_iter_archetype<1>, simple_iter_archetype<1>>); + template + constexpr bool test() { + using std::indirect_binary_predicate, std::indirect_equivalence_relation, std::indirect_strict_weak_order; + using F = Fn; + using I1 = simple_iter_archetype; + using I2 = simple_iter_archetype; + constexpr bool result = indirect_binary_predicate; + STATIC_ASSERT(indirect_equivalence_relation == result); + STATIC_ASSERT(indirect_strict_weak_order == result); + return result; + } + + STATIC_ASSERT(!test<0, 1, 1>()); + STATIC_ASSERT(!test<1, 1, 1>()); + STATIC_ASSERT(!test<2, 1, 1>()); + STATIC_ASSERT(!test<3, 1, 1>()); + STATIC_ASSERT(!test<4, 1, 1>()); + STATIC_ASSERT(!test<5, 1, 1>()); + + STATIC_ASSERT(!test<6, 0, 1>()); + STATIC_ASSERT(!test<6, 1, 0>()); + STATIC_ASSERT(test<6, 1, 1>()); STATIC_ASSERT(std::same_as, simple_iter_archetype<1>, simple_iter_archetype<1>>, std::true_type>); diff --git a/tests/std/tests/P0896R4_ranges_iterator_machinery/test.cpp b/tests/std/tests/P0896R4_ranges_iterator_machinery/test.cpp index 46a592a9d40..31143faaa0a 100644 --- a/tests/std/tests/P0896R4_ranges_iterator_machinery/test.cpp +++ b/tests/std/tests/P0896R4_ranges_iterator_machinery/test.cpp @@ -796,7 +796,7 @@ namespace iterator_traits_test { }; // clang-format off - template + template concept has_empty_traits = !(requires { typename iterator_traits::iterator_concept; } || requires { typename iterator_traits::iterator_category; } || requires { typename iterator_traits::value_type; } @@ -1700,7 +1700,7 @@ namespace unreachable_sentinel_test { STATIC_ASSERT(std::is_nothrow_move_assignable_v); // clang-format off - template + template concept Comparable = requires(T const& t) { { t == unreachable_sentinel } -> std::same_as; { t != unreachable_sentinel } -> std::same_as; diff --git a/tests/std/tests/P0898R3_concepts/test.cpp b/tests/std/tests/P0898R3_concepts/test.cpp index 68f4a4c3fd1..41a27ab4f63 100644 --- a/tests/std/tests/P0898R3_concepts/test.cpp +++ b/tests/std/tests/P0898R3_concepts/test.cpp @@ -2110,12 +2110,12 @@ namespace test_swappable_with { // clang-format off namespace ranges = std::ranges; - template U> + template U> void value_swap(T&& t, U&& u) { ranges::swap(std::forward(t), std::forward(u)); } - template + template void lv_swap(T& t1, T& t2) { ranges::swap(t1, t2); } From 481d23e2931dc03ff22c53d6b2517bcb3eb05ed1 Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Fri, 12 Jun 2020 11:57:35 -0700 Subject: [PATCH 02/13] Review comments; fix failing libc++ tests libc++ `unordered_meow` tests were assuming that `#include ` makes `std::is_permutation` available. Drive-by: Inline 4-argument `_Is_permutation_unchecked` into `is_permutation` since this `_unchecked` overload isn't used elsewhere. --- llvm-project | 2 +- stl/inc/algorithm | 70 +++++++------------ stl/inc/xutility | 10 +-- .../test.cpp | 14 ++-- 4 files changed, 41 insertions(+), 55 deletions(-) diff --git a/llvm-project b/llvm-project index 634a0acb307..6ffa6a300d3 160000 --- a/llvm-project +++ b/llvm-project @@ -1 +1 @@ -Subproject commit 634a0acb307ddad21c5542dc313e02b4df9b216e +Subproject commit 6ffa6a300d36ae59deeab5ecaa16923b32f98a4a diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 22c3aeb8029..26b94d38c4d 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -943,43 +943,40 @@ namespace ranges { // FUNCTION TEMPLATE is_permutation template -_NODISCARD _CONSTEXPR20 bool _Is_permutation_unchecked(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _Pr _Pred) { +_NODISCARD _CONSTEXPR20 bool is_permutation(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _Pr _Pred) { // test if [_First1, _Last1) == permuted [_First2, ...), using _Pred - for (; _First1 != _Last1; ++_First1, (void) ++_First2) { // trim matching prefix - if (!_Pred(*_First1, *_First2)) { - // found first inequality, check match counts in suffix - // - // narrowing _Iter_diff_t<_FwdIt1> to _Iter_diff_t<_FwdIt2> is OK because if the second range is shorter - // than the first, the user already triggered UB - auto _Last2 = _STD next(_First2, static_cast<_Iter_diff_t<_FwdIt2>>(_STD distance(_First1, _Last1))); - return _Check_match_counts(_First1, _Last1, _First2, _Last2, _Pred); + _Adl_verify_range(_First1, _Last1); + auto _UFirst1 = _Get_unwrapped(_First1); + const auto _ULast1 = _Get_unwrapped(_Last1); + auto _UFirst2 = _Get_unwrapped_n(_First2, _Idl_distance<_FwdIt1>(_UFirst1, _ULast1)); + + for (;; ++_UFirst1, (void) ++_UFirst2) { // trim matching prefix + if (_UFirst1 == _ULast1) { // everything matched + return true; + } + + if (!_Pred(*_UFirst1, *_UFirst2)) { // found first inequality, check match counts in suffix + break; } } - return true; -} - -template -_NODISCARD _CONSTEXPR20 bool is_permutation(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _Pr _Pred) { - // test if [_First1, _Last1) == permuted [_First2, ...), using _Pred - _Adl_verify_range(_First1, _Last1); - const auto _UFirst1 = _Get_unwrapped(_First1); - const auto _ULast1 = _Get_unwrapped(_Last1); - const auto _UFirst2 = _Get_unwrapped_n(_First2, _Idl_distance<_FwdIt1>(_UFirst1, _ULast1)); - return _Is_permutation_unchecked(_UFirst1, _ULast1, _UFirst2, _Pass_fn(_Pred)); + // Narrowing _Iter_diff_t<_FwdIt1> to _Iter_diff_t<_FwdIt2> is OK because the second range must be at least as long + // as the first. + const auto _Dist2 = static_cast<_Iter_diff_t<_FwdIt2>>(_STD distance(_UFirst1, _ULast1)); + return _Check_match_counts(_UFirst1, _ULast1, _UFirst2, _STD next(_UFirst2, _Dist2), _Pred); } template _NODISCARD _CONSTEXPR20 bool is_permutation(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2) { // test if [_First1, _Last1) == permuted [_First2, ...) - return _STD is_permutation(_First1, _Last1, _First2, equal_to<>()); + return _STD is_permutation(_First1, _Last1, _First2, equal_to<>{}); } template _CONSTEXPR20 bool _Is_permutation_unchecked(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _FwdIt2 _Last2, _Pr _Pred, forward_iterator_tag, forward_iterator_tag) { // test if [_First1, _Last1) == permuted [_First2, _Last2), using _Pred, arbitrary iterators - for (;;) { // trim matching prefix + for (;; ++_First1, (void) ++_First2) { // trim matching prefix if (_First1 == _Last1) { return _First2 == _Last2; } @@ -991,14 +988,11 @@ _CONSTEXPR20 bool _Is_permutation_unchecked(_FwdIt1 _First1, _FwdIt1 _Last1, _Fw if (!_Pred(*_First1, *_First2)) { // found first inequality, check match counts in suffix break; } - - ++_First1; - ++_First2; } auto _Next1 = _First1; auto _Next2 = _First2; - for (;;) { // check for same lengths + for (;; ++_Next1, (void) ++_Next2) { // check for same lengths if (_Next1 == _Last1) { if (_Next2 == _Last2) { return _Check_match_counts(_First1, _Last1, _First2, _Last2, _Pred); @@ -1010,9 +1004,6 @@ _CONSTEXPR20 bool _Is_permutation_unchecked(_FwdIt1 _First1, _FwdIt1 _Last1, _Fw if (_Next2 == _Last2) { return false; // sequence 1 is longer than sequence 2, not a permutation } - - ++_Next1; - ++_Next2; } } @@ -1041,13 +1032,13 @@ _NODISCARD _CONSTEXPR20 bool is_permutation( _Adl_verify_range(_First1, _Last1); _Adl_verify_range(_First2, _Last2); return _Is_permutation_unchecked(_Get_unwrapped(_First1), _Get_unwrapped(_Last1), _Get_unwrapped(_First2), - _Get_unwrapped(_Last2), _Pass_fn(_Pred), _Iter_cat_t<_FwdIt1>(), _Iter_cat_t<_FwdIt2>()); + _Get_unwrapped(_Last2), _Pass_fn(_Pred), _Iter_cat_t<_FwdIt1>{}, _Iter_cat_t<_FwdIt2>{}); } template _NODISCARD _CONSTEXPR20 bool is_permutation(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _FwdIt2 _Last2) { // test if [_First1, _Last1) == permuted [_First2, _Last2) - return _STD is_permutation(_First1, _Last1, _First2, _Last2, equal_to<>()); + return _STD is_permutation(_First1, _Last1, _First2, _Last2, equal_to<>{}); } #ifdef __cpp_lib_concepts @@ -1118,7 +1109,7 @@ namespace ranges { _STL_INTERNAL_CHECK(_RANGES distance(_First1, _End1) == _Count); _STL_INTERNAL_CHECK(_RANGES distance(_First2, _End2) == _Count); - for (;;) { // Trim matching prefixes + for (;; ++_First1, (void) ++_First2, --_Count) { // Trim matching prefixes if (_Count == 0) { // Everything matched return true; } @@ -1130,10 +1121,6 @@ namespace ranges { break; } - - ++_First1; - ++_First2; - --_Count; } // If we get here, _Count > 1 and initial elements do not match. @@ -1168,7 +1155,7 @@ namespace ranges { template _NODISCARD static constexpr bool _Unsized_ranges( _It1 _First1, _Se1 _End1, _It2 _First2, _Se2 _End2, _Pr _Pred, _Pj1 _Proj1, _Pj2 _Proj2) { - for (;;) { // Trim matching prefixes + for (;; ++_First1, (void) ++_First2) { // Trim matching prefixes if (_First1 == _End1) { // first range is a (possibly proper?) prefix of the second return _First2 == _End2; } else if (_First2 == _End2) { // second range is a proper prefix of the first @@ -1178,9 +1165,6 @@ namespace ranges { if (!_STD invoke(_Pred, _STD invoke(_Proj1, *_First1), _STD invoke(_Proj2, *_First2))) { // mismatch break; } - - ++_First1; - ++_First2; } // If we get here, initial elements do not match. @@ -1196,10 +1180,8 @@ namespace ranges { } return false; // different lengths; not a permutation - } - - if (_Last2 == _End2) { - return false; // Ditto + } else if (_Last2 == _End2) { + return false; // Ditto different lengths } } // if we get here, initial elements do not match and ranges have equal lengths. diff --git a/stl/inc/xutility b/stl/inc/xutility index 533a3a945a1..77d81729867 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -1009,6 +1009,10 @@ namespace ranges { concept _Can_swap_references = indirectly_readable> && indirectly_readable> && swappable_with, iter_reference_t<_Ty2>>; + + template + concept _Bi_movable_storable = indirectly_movable_storable, remove_reference_t<_Ty2>> + && indirectly_movable_storable, remove_reference_t<_Ty1>>; // clang-format on template @@ -1019,6 +1023,7 @@ namespace ranges { return _Tmp; } + class _Cpo { private: enum class _St { _None, _Custom, _Swap, _Exchange }; @@ -1029,10 +1034,7 @@ namespace ranges { return {_St::_Custom, noexcept(iter_swap(_STD declval<_Ty1>(), _STD declval<_Ty2>()))}; } else if constexpr (_Can_swap_references<_Ty1, _Ty2>) { return {_St::_Swap, noexcept(_RANGES swap(*_STD declval<_Ty1>(), *_STD declval<_Ty2>()))}; - } else if constexpr ( - indirectly_movable_storable, - remove_reference_t< - _Ty2>> && indirectly_movable_storable, remove_reference_t<_Ty1>>) { + } else if constexpr (_Bi_movable_storable<_Ty1, _Ty2>) { constexpr auto _Nothrow = noexcept( *_STD declval<_Ty1>() = _Iter_exchange_move(_STD declval<_Ty1>(), _STD declval<_Ty2>())); return {_St::_Exchange, _Nothrow}; diff --git a/tests/std/tests/P0896R4_ranges_alg_is_permutation/test.cpp b/tests/std/tests/P0896R4_ranges_alg_is_permutation/test.cpp index 16738c55ad9..0dccd2d54b0 100644 --- a/tests/std/tests/P0896R4_ranges_alg_is_permutation/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_is_permutation/test.cpp @@ -51,6 +51,13 @@ struct instantiator { } }; +#ifdef TEST_EVERYTHING +int main() { + // No constepxr test here; this test_fwd_fwd call exceeds the maximum number of steps in a constexpr computation + test_fwd_fwd, const int>(); +} +#else // ^^^ test all range combinations // test only interesting range combos vvv + template using fwd_test_range = test::range; @@ -62,9 +69,6 @@ constexpr bool run_tests() { using Elem1 = const pair; using Elem2 = const int; -#ifdef TEST_EVERYTHING - test_fwd_fwd(); -#else // ^^^ test all range combinations // test only interesting range combos vvv // All proxy reference types, since the algorithm doesn't really care. using test::Common, test::Sized; @@ -92,14 +96,12 @@ constexpr bool run_tests() { bidi_test_range>(); instantiator::call, bidi_test_range>(); -#endif // TEST_EVERYTHING return true; } int main() { -#ifndef TEST_EVERYTHING // This crushes the constexpr step limit on our compilers STATIC_ASSERT(run_tests()); -#endif // TEST_EVERYTHING run_tests(); } +#endif // TEST_EVERYTHING From ca95a14c69366838be198ee02b8b0e52fd8025ef Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Fri, 12 Jun 2020 13:17:47 -0700 Subject: [PATCH 03/13] Ouch: missed a _Pass_fn --- stl/inc/algorithm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 26b94d38c4d..a4dd7032345 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -963,7 +963,7 @@ _NODISCARD _CONSTEXPR20 bool is_permutation(_FwdIt1 _First1, _FwdIt1 _Last1, _Fw // Narrowing _Iter_diff_t<_FwdIt1> to _Iter_diff_t<_FwdIt2> is OK because the second range must be at least as long // as the first. const auto _Dist2 = static_cast<_Iter_diff_t<_FwdIt2>>(_STD distance(_UFirst1, _ULast1)); - return _Check_match_counts(_UFirst1, _ULast1, _UFirst2, _STD next(_UFirst2, _Dist2), _Pred); + return _Check_match_counts(_UFirst1, _ULast1, _UFirst2, _STD next(_UFirst2, _Dist2), _Pass_fn(_Pred)); } template From 166eafb2c3ebf6e8af10b42ef3bec78f6d9ee0ef Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Fri, 12 Jun 2020 14:56:02 -0700 Subject: [PATCH 04/13] Fix more LLVM update fallout; remove errant "FIXME" --- llvm-project | 2 +- tests/libcxx/expected_results.txt | 2 ++ tests/libcxx/skipped_tests.txt | 2 ++ tests/std/tests/P0896R4_ranges_subrange/test.cpp | 3 +-- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/llvm-project b/llvm-project index 6ffa6a300d3..8b828e98386 160000 --- a/llvm-project +++ b/llvm-project @@ -1 +1 @@ -Subproject commit 6ffa6a300d36ae59deeab5ecaa16923b32f98a4a +Subproject commit 8b828e983868d5b26f3e7de9e4dc68e27d5bf28c diff --git a/tests/libcxx/expected_results.txt b/tests/libcxx/expected_results.txt index 1e1d62bcd84..55a4a6ab84d 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -585,9 +585,11 @@ std/containers/sequences/array/array.swap/swap.fail.cpp FAIL # STL bug: VSO-207715 We reject array. std/containers/sequences/array/array.cons/default.pass.cpp FAIL std/containers/sequences/array/array.cons/implicit_copy.pass.cpp FAIL +std/containers/sequences/array/array.cons/initialization.pass.cpp FAIL std/containers/sequences/array/array.data/data_const.pass.cpp FAIL std/containers/sequences/array/array.data/data.pass.cpp FAIL std/containers/sequences/array/begin.pass.cpp FAIL +std/containers/sequences/array/iterators.pass.cpp FAIL # STL bug: string_view doesn't enforce "non-array trivial standard-layout", related to LWG-3034. std/strings/string.view/char.bad.fail.cpp:0 FAIL diff --git a/tests/libcxx/skipped_tests.txt b/tests/libcxx/skipped_tests.txt index 17da4731102..8c69d8df319 100644 --- a/tests/libcxx/skipped_tests.txt +++ b/tests/libcxx/skipped_tests.txt @@ -585,9 +585,11 @@ containers\sequences\array\array.swap\swap.fail.cpp # STL bug: VSO-207715 We reject array. containers\sequences\array\array.cons\default.pass.cpp containers\sequences\array\array.cons\implicit_copy.pass.cpp +containers\sequences\array\array.cons\initialization.pass.cpp containers\sequences\array\array.data\data_const.pass.cpp containers\sequences\array\array.data\data.pass.cpp containers\sequences\array\begin.pass.cpp +containers\sequences\array\iterators.pass.cpp # STL bug: string_view doesn't enforce "non-array trivial standard-layout", related to LWG-3034. strings\string.view\char.bad.fail.cpp diff --git a/tests/std/tests/P0896R4_ranges_subrange/test.cpp b/tests/std/tests/P0896R4_ranges_subrange/test.cpp index 74ab977ce65..ecb59925915 100644 --- a/tests/std/tests/P0896R4_ranges_subrange/test.cpp +++ b/tests/std/tests/P0896R4_ranges_subrange/test.cpp @@ -1400,8 +1400,7 @@ namespace test_subrange { STATIC_ASSERT(test_ctad>()); - STATIC_ASSERT( - test_ctad>()); // FIXME: look at these + STATIC_ASSERT(test_ctad>()); STATIC_ASSERT(test_ctad>()); STATIC_ASSERT(test_ctad>()); STATIC_ASSERT(test_ctad>()); From 042d351a1a4ee8ed20c8bf05c396d1ea933696e1 Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Mon, 15 Jun 2020 17:26:06 -0700 Subject: [PATCH 05/13] Update LLVM reference to get my LIT fix --- llvm-project | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llvm-project b/llvm-project index 8b828e98386..d66428cb995 160000 --- a/llvm-project +++ b/llvm-project @@ -1 +1 @@ -Subproject commit 8b828e983868d5b26f3e7de9e4dc68e27d5bf28c +Subproject commit d66428cb995c7a04ecb02951d5021d815fc02b2b From 909c0262ccf83a2c85f3ca18fe98b9d09a7afd80 Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Thu, 18 Jun 2020 12:29:52 -0700 Subject: [PATCH 06/13] Review comments --- stl/inc/algorithm | 137 +++++++++--------- stl/inc/xutility | 18 ++- .../test.cpp | 62 ++++++-- .../test.cpp | 7 +- 4 files changed, 132 insertions(+), 92 deletions(-) diff --git a/stl/inc/algorithm b/stl/inc/algorithm index a4dd7032345..93ea67b9d88 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -1047,7 +1047,7 @@ namespace ranges { template concept _Bidi_common = is_same_v<_It, _Se> && bidirectional_iterator<_It>; template - concept _Bidi_common_range = common_range<_Rng>&& bidirectional_iterator>; + concept _Bidi_common_range = common_range<_Rng> && bidirectional_iterator>; // clang-format on // VARIABLE ranges::is_permutation @@ -1073,11 +1073,11 @@ namespace ranges { return false; } - return _Sized_ranges(_STD move(_UFirst1), _STD move(_ULast1), _STD move(_UFirst2), _STD move(_ULast2), - _Count, _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); + return _Is_permutation_with_lengths(_STD move(_UFirst1), _STD move(_ULast1), _STD move(_UFirst2), + _STD move(_ULast2), _Count, _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); } else { - return _Unsized_ranges(_STD move(_UFirst1), _STD move(_ULast1), _STD move(_UFirst2), _STD move(_ULast2), - _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); + return _Is_permutation_without_lengths(_STD move(_UFirst1), _STD move(_ULast1), _STD move(_UFirst2), + _STD move(_ULast2), _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); } } @@ -1092,73 +1092,73 @@ namespace ranges { return false; } - return _Sized_ranges(_Get_unwrapped(_RANGES begin(_Range1)), _Get_unwrapped(_RANGES end(_Range1)), - _Get_unwrapped(_RANGES begin(_Range2)), _Get_unwrapped(_RANGES end(_Range2)), _Count, - _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); + return _Is_permutation_with_lengths(_Get_unwrapped(_RANGES begin(_Range1)), + _Get_unwrapped(_RANGES end(_Range1)), _Get_unwrapped(_RANGES begin(_Range2)), + _Get_unwrapped(_RANGES end(_Range2)), _Count, _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); } else { - return _Unsized_ranges(_Get_unwrapped(_RANGES begin(_Range1)), _Get_unwrapped(_RANGES end(_Range1)), - _Get_unwrapped(_RANGES begin(_Range2)), _Get_unwrapped(_RANGES end(_Range2)), _Pass_fn(_Pred), - _Pass_fn(_Proj1), _Pass_fn(_Proj2)); + return _Is_permutation_without_lengths(_Get_unwrapped(_RANGES begin(_Range1)), + _Get_unwrapped(_RANGES end(_Range1)), _Get_unwrapped(_RANGES begin(_Range2)), + _Get_unwrapped(_RANGES end(_Range2)), _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); } } private: template - _NODISCARD static constexpr bool _Sized_ranges(_It1 _First1, _Se1 _End1, _It2 _First2, _Se2 _End2, - iter_difference_t<_It1> _Count, _Pr _Pred, _Pj1 _Proj1, _Pj2 _Proj2) { - _STL_INTERNAL_CHECK(_RANGES distance(_First1, _End1) == _Count); - _STL_INTERNAL_CHECK(_RANGES distance(_First2, _End2) == _Count); + _NODISCARD static constexpr bool _Is_permutation_with_lengths(_It1 _First1, _Se1 _Last1, _It2 _First2, + _Se2 _Last2, iter_difference_t<_It1> _Count, _Pr _Pred, _Pj1 _Proj1, _Pj2 _Proj2) { + _STL_INTERNAL_CHECK(_RANGES distance(_First1, _Last1) == _Count); + _STL_INTERNAL_CHECK(_RANGES distance(_First2, _Last2) == _Count); - for (;; ++_First1, (void) ++_First2, --_Count) { // Trim matching prefixes - if (_Count == 0) { // Everything matched + for (;; ++_First1, (void) ++_First2, --_Count) { // trim matching prefixes + if (_Count == 0) { // everything matched return true; } if (!_STD invoke(_Pred, _STD invoke(_Proj1, *_First1), _STD invoke(_Proj2, *_First2))) { // mismatch - if (_Count == 1) { // Single non-matching characters remain; not a permutation - return false; - } - break; } } + + if (_Count == 1) { // single non-matching elements remain; not a permutation + return false; + } // If we get here, _Count > 1 and initial elements do not match. if constexpr (bidirectional_iterator<_It1> && bidirectional_iterator<_It2>) { - // Determine final iterator values - auto _Last1 = _Find_last_iterator(_First1, _End1, _Count); - auto _Last2 = _Find_last_iterator(_First2, _End2, _Count); - - for (;;) { // Trim matching suffixes - --_Last1; - --_Last2; - if (!_STD invoke(_Pred, _STD invoke(_Proj1, *_Last1), _STD invoke(_Proj2, *_Last2))) { // mismatch - ++_Last1; - ++_Last2; + // determine final iterator values + auto _Final1 = _Find_last_iterator(_First1, _Last1, _Count); + auto _Final2 = _Find_last_iterator(_First2, _Last2, _Count); + + for (;;) { // trim matching suffixes + --_Final1; + --_Final2; + if (!_STD invoke(_Pred, _STD invoke(_Proj1, *_Final1), _STD invoke(_Proj2, *_Final2))) { // mismatch + ++_Final1; + ++_Final2; break; } if (--_Count == 1) { - return false; // initial elements did not match when trimming prefixes + return false; // initial elements still do not match } } // If we get here, _Count > 1, initial elements do not match, and final elements do not match. - return _Match_counts(_STD move(_First1), _STD move(_Last1), _STD move(_First2), _STD move(_Last2), + return _Match_counts(_STD move(_First1), _STD move(_Final1), _STD move(_First2), _STD move(_Final2), _Pred, _Proj1, _Proj2); } else { - return _Match_counts( - _STD move(_First1), _STD move(_End1), _STD move(_First2), _STD move(_End2), _Pred, _Proj1, _Proj2); + return _Match_counts(_STD move(_First1), _STD move(_Last1), _STD move(_First2), _STD move(_Last2), + _Pred, _Proj1, _Proj2); } } template - _NODISCARD static constexpr bool _Unsized_ranges( - _It1 _First1, _Se1 _End1, _It2 _First2, _Se2 _End2, _Pr _Pred, _Pj1 _Proj1, _Pj2 _Proj2) { - for (;; ++_First1, (void) ++_First2) { // Trim matching prefixes - if (_First1 == _End1) { // first range is a (possibly proper?) prefix of the second - return _First2 == _End2; - } else if (_First2 == _End2) { // second range is a proper prefix of the first + _NODISCARD static constexpr bool _Is_permutation_without_lengths( + _It1 _First1, _Se1 _Last1, _It2 _First2, _Se2 _Last2, _Pr _Pred, _Pj1 _Proj1, _Pj2 _Proj2) { + for (;; ++_First1, (void) ++_First2) { // trim matching prefixes + if (_First1 == _Last1) { // first range is a prefix of second + return _First2 == _Last2; + } else if (_First2 == _Last2) { // second range is a proper prefix of first return false; } @@ -1168,35 +1168,33 @@ namespace ranges { } // If we get here, initial elements do not match. - // Determine final iterator values and validate lengths - auto _Last1 = _First1; - auto _Last2 = _First2; + // determine final iterator values and validate lengths + auto _Final1 = _First1; + auto _Final2 = _First2; for (;;) { - ++_Last1; - ++_Last2; - if (_Last1 == _End1) { - if (_Last2 == _End2) { + ++_Final1; + ++_Final2; + if (_Final1 == _Last1) { + if (_Final2 == _Last2) { break; // equal lengths } return false; // different lengths; not a permutation - } else if (_Last2 == _End2) { - return false; // Ditto different lengths + } else if (_Final2 == _Last2) { + return false; // ditto different lengths } } - // if we get here, initial elements do not match and ranges have equal lengths. + // If we get here, initial elements do not match and ranges have equal lengths. if constexpr (bidirectional_iterator<_It1> && bidirectional_iterator<_It2>) { - for (;;) { // Trim matching suffixes - if (--_Last1 == _First1) { - return false; // initial elements did not match when trimming prefixes. - } - if (--_Last2 == _First2) { - return false; // Ditto + for (;;) { // trim matching suffixes + if (--_Final1 == _First1) { + return false; // initial elements still do not match } - if (!_STD invoke(_Pred, _STD invoke(_Proj1, *_Last1), _STD invoke(_Proj2, *_Last2))) { // mismatch - ++_Last1; - ++_Last2; + --_Final2; // since ranges have equal lengths, _Final2 cannot equal _First2 + if (!_STD invoke(_Pred, _STD invoke(_Proj1, *_Final1), _STD invoke(_Proj2, *_Final2))) { // mismatch + ++_Final1; + ++_Final2; break; } } @@ -1205,37 +1203,38 @@ namespace ranges { } return _Match_counts( - _STD move(_First1), _STD move(_Last1), _STD move(_First2), _STD move(_Last2), _Pred, _Proj1, _Proj2); + _STD move(_First1), _STD move(_Final1), _STD move(_First2), _STD move(_Final2), _Pred, _Proj1, _Proj2); } template _NODISCARD static constexpr bool _Match_counts(const _It1 _First1, const _Se1 _Last1, const _It2 _First2, const _Se2 _Last2, _Pr _Pred, _Pj1 _Proj1, _Pj2 _Proj2) { - for (auto _Mid1 = _First1; _Mid1 != _Last1; ++_Mid1) { + for (auto _Current = _First1; _Current != _Last1; ++_Current) { bool _Found = false; - auto _Meow = _First1; - for (; _Meow != _Mid1; ++_Meow) { - if (_STD invoke(_Pred, _STD invoke(_Proj1, *_Mid1), _STD invoke(_Proj1, *_Meow))) { - // the same value appears earlier in the first range; we've already counted occurrences + auto _Mid1 = _First1; + for (; _Mid1 != _Current; ++_Mid1) { + if (_STD invoke(_Pred, _STD invoke(_Proj1, *_Current), _STD invoke(_Proj1, *_Mid1))) { + // this value appears earlier in the first range so we've already counted occurrences _Found = true; break; } } + if (_Found) { continue; } // count occurrences of this value in the first range iter_difference_t<_It1> _Occurrences = 1; - while (++_Meow != _Last1) { - if (_STD invoke(_Pred, _STD invoke(_Proj1, *_Mid1), _STD invoke(_Proj1, *_Meow))) { + while (++_Mid1 != _Last1) { + if (_STD invoke(_Pred, _STD invoke(_Proj1, *_Current), _STD invoke(_Proj1, *_Mid1))) { ++_Occurrences; } } // subtract occurrences in the second range for (auto _Mid2 = _First2; _Mid2 != _Last2; ++_Mid2) { - if (_STD invoke(_Pred, _STD invoke(_Proj1, *_Mid1), _STD invoke(_Proj2, *_Mid2))) { + if (_STD invoke(_Pred, _STD invoke(_Proj1, *_Current), _STD invoke(_Proj2, *_Mid2))) { if (--_Occurrences < 0) { // value appears more in second range than first; not a permutation return false; diff --git a/stl/inc/xutility b/stl/inc/xutility index 77d81729867..f161f9d5ad9 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -1011,7 +1011,8 @@ namespace ranges { && swappable_with, iter_reference_t<_Ty2>>; template - concept _Bi_movable_storable = indirectly_movable_storable, remove_reference_t<_Ty2>> + concept _Symmetric_indirectly_movable_storable = + indirectly_movable_storable, remove_reference_t<_Ty2>> && indirectly_movable_storable, remove_reference_t<_Ty1>>; // clang-format on @@ -1023,7 +1024,6 @@ namespace ranges { return _Tmp; } - class _Cpo { private: enum class _St { _None, _Custom, _Swap, _Exchange }; @@ -1034,7 +1034,7 @@ namespace ranges { return {_St::_Custom, noexcept(iter_swap(_STD declval<_Ty1>(), _STD declval<_Ty2>()))}; } else if constexpr (_Can_swap_references<_Ty1, _Ty2>) { return {_St::_Swap, noexcept(_RANGES swap(*_STD declval<_Ty1>(), *_STD declval<_Ty2>()))}; - } else if constexpr (_Bi_movable_storable<_Ty1, _Ty2>) { + } else if constexpr (_Symmetric_indirectly_movable_storable<_Ty1, _Ty2>) { constexpr auto _Nothrow = noexcept( *_STD declval<_Ty1>() = _Iter_exchange_move(_STD declval<_Ty1>(), _STD declval<_Ty2>())); return {_St::_Exchange, _Nothrow}; @@ -3180,13 +3180,15 @@ namespace ranges { const _It& _First, const _Se& _Last, const iter_difference_t<_It> _Count) { // Find the iterator in [_First, _Last) (of length _Count) which equals _Last _STL_INTERNAL_CHECK(_RANGES distance(_First, _Last) == _Count); - auto _Result = _First; - if constexpr (assignable_from<_It&, _Se>) { - _RANGES advance(_Result, _Last); + (void) _First; + (void) _Last; + (void) _Count; + + if constexpr (is_same_v<_It, _Se>) { + return _Last; } else { - _RANGES advance(_Result, _Count); + return _RANGES next(_First, _Count); } - return _Result; } // STRUCT ranges::equal_to diff --git a/tests/std/tests/P0896R4_ranges_alg_is_permutation/test.cpp b/tests/std/tests/P0896R4_ranges_alg_is_permutation/test.cpp index 0dccd2d54b0..8552fe4e278 100644 --- a/tests/std/tests/P0896R4_ranges_alg_is_permutation/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_is_permutation/test.cpp @@ -6,18 +6,21 @@ #include #include #include +#include #include // #include using namespace std; -struct instantiator { - using P = pair; +using P = pair; +struct instantiator { static constexpr array pairs = { - P{0, 42}, P{1, 42}, P{0, 42}, P{1, 42}, P{0, 42}, P{1, 42}, P{0, 42}, P{1, 42}}; + P{0, 42}, P{0, 42}, P{1, 42}, P{1, 42}, P{0, 42}, P{0, 42}, P{1, 42}, P{1, 42}}; static constexpr array not_pairs = {0, 0, 0, 0, 1, 1, 1, 1}; + static constexpr array too_few_ones = {0, 0, 0, 0, 0, 1, 1, 1}; + static constexpr array too_many_ones = {0, 0, 0, 1, 1, 1, 1, 1}; template static constexpr void call() { @@ -44,20 +47,56 @@ struct instantiator { assert(ranges::is_permutation(ranges::begin(range1), ranges::end(range1), ranges::begin(range2), ranges::end(range2), ranges::equal_to{}, get_first, identity{})); - // negative case - assert(!ranges::is_permutation(range1, range2, ranges::equal_to{}, get_second, identity{})); - assert(!ranges::is_permutation(ranges::begin(range1), ranges::end(range1), ranges::begin(range2), - ranges::end(range2), ranges::equal_to{}, get_second, identity{})); + { + // negative case [different lengths] + const Fwd2 suffix2{std::span{not_pairs}.subspan<2>()}; + assert(!ranges::is_permutation(range1, suffix2, ranges::equal_to{}, get_first, identity{})); + assert(!ranges::is_permutation(suffix2, range1, ranges::equal_to{}, identity{}, get_first)); + assert(!ranges::is_permutation(ranges::begin(range1), ranges::end(range1), ranges::begin(suffix2), + ranges::end(suffix2), ranges::equal_to{}, get_first, identity{})); + assert(!ranges::is_permutation(ranges::begin(suffix2), ranges::end(suffix2), ranges::begin(range1), + ranges::end(range1), ranges::equal_to{}, identity{}, get_first)); + } + { + // negative case [1 appears more in first range than second + const Fwd2 r2{too_few_ones}; + assert(!ranges::is_permutation(range1, r2, ranges::equal_to{}, get_first, identity{})); + assert(!ranges::is_permutation(ranges::begin(range1), ranges::end(range1), ranges::begin(r2), + ranges::end(r2), ranges::equal_to{}, get_first, identity{})); + } + { + // negative case [1 appears more in second range than first + const Fwd2 r2{too_many_ones}; + assert(!ranges::is_permutation(range1, r2, ranges::equal_to{}, get_first, identity{})); + assert(!ranges::is_permutation(ranges::begin(range1), ranges::end(range1), ranges::begin(r2), + ranges::end(r2), ranges::equal_to{}, get_first, identity{})); + } + + { + // negative case [only final elements differ] + const Fwd1 r1{std::span{pairs}.subspan<0, 3>()}; + const Fwd2 r2{std::span{not_pairs}.subspan<0, 3>()}; + assert(!ranges::is_permutation(r1, r2, ranges::equal_to{}, get_first, identity{})); + assert(!ranges::is_permutation(ranges::begin(r1), ranges::end(r1), ranges::begin(r2), ranges::end(r2), + ranges::equal_to{}, get_first, identity{})); + } + { + // negative case [only initial elements differ] + const Fwd1 r1{std::span{pairs}.subspan<1, 3>()}; + const Fwd2 r2{std::span{not_pairs}.subspan<4, 3>()}; + assert(!ranges::is_permutation(r1, r2, ranges::equal_to{}, get_first, identity{})); + assert(!ranges::is_permutation(ranges::begin(r1), ranges::end(r1), ranges::begin(r2), ranges::end(r2), + ranges::equal_to{}, get_first, identity{})); + } } }; #ifdef TEST_EVERYTHING int main() { - // No constepxr test here; this test_fwd_fwd call exceeds the maximum number of steps in a constexpr computation - test_fwd_fwd, const int>(); + // No constexpr test here; this test_fwd_fwd call exceeds the maximum number of steps in a constexpr computation + test_fwd_fwd(); } #else // ^^^ test all range combinations // test only interesting range combos vvv - template using fwd_test_range = test::range; @@ -66,7 +105,7 @@ using bidi_test_range = test::range; constexpr bool run_tests() { - using Elem1 = const pair; + using Elem1 = const P; using Elem2 = const int; // All proxy reference types, since the algorithm doesn't really care. @@ -79,7 +118,6 @@ constexpr bool run_tests() { instantiator::call, fwd_test_range>(); // both bidi same common and 0-/1-/2-sized - instantiator::call, bidi_test_range>(); instantiator::call, bidi_test_range>(); diff --git a/tests/std/tests/P0896R4_ranges_algorithm_machinery/test.cpp b/tests/std/tests/P0896R4_ranges_algorithm_machinery/test.cpp index 6def8ab5dec..192aed0c171 100644 --- a/tests/std/tests/P0896R4_ranges_algorithm_machinery/test.cpp +++ b/tests/std/tests/P0896R4_ranges_algorithm_machinery/test.cpp @@ -198,9 +198,10 @@ namespace indirect_binary_predicate_test { template constexpr bool test() { using std::indirect_binary_predicate, std::indirect_equivalence_relation, std::indirect_strict_weak_order; - using F = Fn; - using I1 = simple_iter_archetype; - using I2 = simple_iter_archetype; + using F = Fn; + using I1 = simple_iter_archetype; + using I2 = simple_iter_archetype; + constexpr bool result = indirect_binary_predicate; STATIC_ASSERT(indirect_equivalence_relation == result); STATIC_ASSERT(indirect_strict_weak_order == result); From 4c0fa8fed4feeaa32a823acaafd252eae95a8746 Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Thu, 18 Jun 2020 12:46:19 -0700 Subject: [PATCH 07/13] One more comment; clang-format fix --- stl/inc/algorithm | 14 +++++++------- .../P0896R4_ranges_alg_is_permutation/test.cpp | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 93ea67b9d88..883acb1e3a4 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -1073,10 +1073,10 @@ namespace ranges { return false; } - return _Is_permutation_with_lengths(_STD move(_UFirst1), _STD move(_ULast1), _STD move(_UFirst2), + return _Is_permutation_sized(_STD move(_UFirst1), _STD move(_ULast1), _STD move(_UFirst2), _STD move(_ULast2), _Count, _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); } else { - return _Is_permutation_without_lengths(_STD move(_UFirst1), _STD move(_ULast1), _STD move(_UFirst2), + return _Is_permutation_unsized(_STD move(_UFirst1), _STD move(_ULast1), _STD move(_UFirst2), _STD move(_ULast2), _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); } } @@ -1092,11 +1092,11 @@ namespace ranges { return false; } - return _Is_permutation_with_lengths(_Get_unwrapped(_RANGES begin(_Range1)), + return _Is_permutation_sized(_Get_unwrapped(_RANGES begin(_Range1)), _Get_unwrapped(_RANGES end(_Range1)), _Get_unwrapped(_RANGES begin(_Range2)), _Get_unwrapped(_RANGES end(_Range2)), _Count, _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); } else { - return _Is_permutation_without_lengths(_Get_unwrapped(_RANGES begin(_Range1)), + return _Is_permutation_unsized(_Get_unwrapped(_RANGES begin(_Range1)), _Get_unwrapped(_RANGES end(_Range1)), _Get_unwrapped(_RANGES begin(_Range2)), _Get_unwrapped(_RANGES end(_Range2)), _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); } @@ -1104,8 +1104,8 @@ namespace ranges { private: template - _NODISCARD static constexpr bool _Is_permutation_with_lengths(_It1 _First1, _Se1 _Last1, _It2 _First2, - _Se2 _Last2, iter_difference_t<_It1> _Count, _Pr _Pred, _Pj1 _Proj1, _Pj2 _Proj2) { + _NODISCARD static constexpr bool _Is_permutation_sized(_It1 _First1, _Se1 _Last1, _It2 _First2, _Se2 _Last2, + iter_difference_t<_It1> _Count, _Pr _Pred, _Pj1 _Proj1, _Pj2 _Proj2) { _STL_INTERNAL_CHECK(_RANGES distance(_First1, _Last1) == _Count); _STL_INTERNAL_CHECK(_RANGES distance(_First2, _Last2) == _Count); @@ -1153,7 +1153,7 @@ namespace ranges { } template - _NODISCARD static constexpr bool _Is_permutation_without_lengths( + _NODISCARD static constexpr bool _Is_permutation_unsized( _It1 _First1, _Se1 _Last1, _It2 _First2, _Se2 _Last2, _Pr _Pred, _Pj1 _Proj1, _Pj2 _Proj2) { for (;; ++_First1, (void) ++_First2) { // trim matching prefixes if (_First1 == _Last1) { // first range is a prefix of second diff --git a/tests/std/tests/P0896R4_ranges_alg_is_permutation/test.cpp b/tests/std/tests/P0896R4_ranges_alg_is_permutation/test.cpp index 8552fe4e278..96fbe280581 100644 --- a/tests/std/tests/P0896R4_ranges_alg_is_permutation/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_is_permutation/test.cpp @@ -18,7 +18,7 @@ using P = pair; struct instantiator { static constexpr array pairs = { P{0, 42}, P{0, 42}, P{1, 42}, P{1, 42}, P{0, 42}, P{0, 42}, P{1, 42}, P{1, 42}}; - static constexpr array not_pairs = {0, 0, 0, 0, 1, 1, 1, 1}; + static constexpr array not_pairs = {0, 0, 0, 0, 1, 1, 1, 1}; static constexpr array too_few_ones = {0, 0, 0, 0, 0, 1, 1, 1}; static constexpr array too_many_ones = {0, 0, 0, 1, 1, 1, 1, 1}; From 767d05820e6d866f4b82cec34a0102a0fd42e5c4 Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Thu, 18 Jun 2020 16:57:44 -0700 Subject: [PATCH 08/13] Apply suggestions from code review Co-authored-by: mnatsuhara <46756417+mnatsuhara@users.noreply.github.com> --- stl/inc/algorithm | 1 + tests/std/tests/P0896R4_ranges_alg_is_permutation/test.cpp | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 883acb1e3a4..cffb1b17049 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -1192,6 +1192,7 @@ namespace ranges { return false; // initial elements still do not match } --_Final2; // since ranges have equal lengths, _Final2 cannot equal _First2 + if (!_STD invoke(_Pred, _STD invoke(_Proj1, *_Final1), _STD invoke(_Proj2, *_Final2))) { // mismatch ++_Final1; ++_Final2; diff --git a/tests/std/tests/P0896R4_ranges_alg_is_permutation/test.cpp b/tests/std/tests/P0896R4_ranges_alg_is_permutation/test.cpp index 96fbe280581..9cd422f5b6f 100644 --- a/tests/std/tests/P0896R4_ranges_alg_is_permutation/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_is_permutation/test.cpp @@ -58,14 +58,14 @@ struct instantiator { ranges::end(range1), ranges::equal_to{}, identity{}, get_first)); } { - // negative case [1 appears more in first range than second + // negative case [1 appears more in first range than second] const Fwd2 r2{too_few_ones}; assert(!ranges::is_permutation(range1, r2, ranges::equal_to{}, get_first, identity{})); assert(!ranges::is_permutation(ranges::begin(range1), ranges::end(range1), ranges::begin(r2), ranges::end(r2), ranges::equal_to{}, get_first, identity{})); } { - // negative case [1 appears more in second range than first + // negative case [1 appears more in second range than first] const Fwd2 r2{too_many_ones}; assert(!ranges::is_permutation(range1, r2, ranges::equal_to{}, get_first, identity{})); assert(!ranges::is_permutation(ranges::begin(range1), ranges::end(range1), ranges::begin(r2), @@ -93,7 +93,7 @@ struct instantiator { #ifdef TEST_EVERYTHING int main() { - // No constexpr test here; this test_fwd_fwd call exceeds the maximum number of steps in a constexpr computation + // No constexpr test here; this test_fwd_fwd call exceeds the maximum number of steps in a constexpr computation. test_fwd_fwd(); } #else // ^^^ test all range combinations // test only interesting range combos vvv From d0b270e0c22487fa080237535f594da736288e2d Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Thu, 18 Jun 2020 22:38:21 -0700 Subject: [PATCH 09/13] Document type requirements of internal functions ... with `_STL_INTERNAL_STATIC_ASSERT` when we don't want to pay the throughput cost of constraints. --- stl/inc/algorithm | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/stl/inc/algorithm b/stl/inc/algorithm index cffb1b17049..000d1e05060 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -1106,6 +1106,12 @@ namespace ranges { template _NODISCARD static constexpr bool _Is_permutation_sized(_It1 _First1, _Se1 _Last1, _It2 _First2, _Se2 _Last2, iter_difference_t<_It1> _Count, _Pr _Pred, _Pj1 _Proj1, _Pj2 _Proj2) { + _STL_INTERNAL_STATIC_ASSERT(forward_iterator<_It1>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se1, _It1>); + _STL_INTERNAL_STATIC_ASSERT(forward_iterator<_It2>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se2, _It2>); + _STL_INTERNAL_STATIC_ASSERT( + indirect_equivalence_relation<_Pr, projected<_It1, _Pj1>, projected<_It2, _Pj2>>); _STL_INTERNAL_CHECK(_RANGES distance(_First1, _Last1) == _Count); _STL_INTERNAL_CHECK(_RANGES distance(_First2, _Last2) == _Count); @@ -1155,6 +1161,13 @@ namespace ranges { template _NODISCARD static constexpr bool _Is_permutation_unsized( _It1 _First1, _Se1 _Last1, _It2 _First2, _Se2 _Last2, _Pr _Pred, _Pj1 _Proj1, _Pj2 _Proj2) { + _STL_INTERNAL_STATIC_ASSERT(forward_iterator<_It1>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se1, _It1>); + _STL_INTERNAL_STATIC_ASSERT(forward_iterator<_It2>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se2, _It2>); + _STL_INTERNAL_STATIC_ASSERT( + indirect_equivalence_relation<_Pr, projected<_It1, _Pj1>, projected<_It2, _Pj2>>); + for (;; ++_First1, (void) ++_First2) { // trim matching prefixes if (_First1 == _Last1) { // first range is a prefix of second return _First2 == _Last2; @@ -1210,6 +1223,13 @@ namespace ranges { template _NODISCARD static constexpr bool _Match_counts(const _It1 _First1, const _Se1 _Last1, const _It2 _First2, const _Se2 _Last2, _Pr _Pred, _Pj1 _Proj1, _Pj2 _Proj2) { + _STL_INTERNAL_STATIC_ASSERT(forward_iterator<_It1>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se1, _It1>); + _STL_INTERNAL_STATIC_ASSERT(forward_iterator<_It2>); + _STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se2, _It2>); + _STL_INTERNAL_STATIC_ASSERT( + indirect_equivalence_relation<_Pr, projected<_It1, _Pj1>, projected<_It2, _Pj2>>); + for (auto _Current = _First1; _Current != _Last1; ++_Current) { bool _Found = false; auto _Mid1 = _First1; From c04b3f33064dcbe134739def10935231217b6e16 Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Sat, 20 Jun 2020 09:26:03 -0700 Subject: [PATCH 10/13] Use _U(begin|end) --- stl/inc/algorithm | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 6ebf427c63f..a8bd4ef18fb 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -926,11 +926,10 @@ namespace ranges { if (_Count != static_cast<_Size2>(_RANGES size(_Range2))) { return false; } - return _Equal_count(_RANGES _Ubegin(_Range1), _RANGES _Ubegin(_Range2), - _Count, _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); + return _Equal_count(_Ubegin(_Range1), _Ubegin(_Range2), _Count, + _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); } else { - return _Equal_4(_RANGES _Ubegin(_Range1), _RANGES _Uend(_Range1), - _RANGES _Ubegin(_Range2), _RANGES _Uend(_Range2), + return _Equal_4(_Ubegin(_Range1), _Uend(_Range1), _Ubegin(_Range2), _Uend(_Range2), _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); } } @@ -1092,13 +1091,11 @@ namespace ranges { return false; } - return _Is_permutation_sized(_Get_unwrapped(_RANGES begin(_Range1)), - _Get_unwrapped(_RANGES end(_Range1)), _Get_unwrapped(_RANGES begin(_Range2)), - _Get_unwrapped(_RANGES end(_Range2)), _Count, _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); + return _Is_permutation_sized(_Ubegin(_Range1), _Uend(_Range1), _Ubegin(_Range2), _Uend(_Range2), _Count, + _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); } else { - return _Is_permutation_unsized(_Get_unwrapped(_RANGES begin(_Range1)), - _Get_unwrapped(_RANGES end(_Range1)), _Get_unwrapped(_RANGES begin(_Range2)), - _Get_unwrapped(_RANGES end(_Range2)), _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); + return _Is_permutation_unsized(_Ubegin(_Range1), _Uend(_Range1), _Ubegin(_Range2), _Uend(_Range2), + _Pass_fn(_Pred), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); } } From 5e33a4ff2e18250d9dc8becc8e64baaf123c2047 Mon Sep 17 00:00:00 2001 From: Casey Carter Date: Mon, 22 Jun 2020 16:58:06 -0700 Subject: [PATCH 11/13] Ditto remove unneeded warning suppression in _Find_last_iterator --- stl/inc/xutility | 4 ---- 1 file changed, 4 deletions(-) diff --git a/stl/inc/xutility b/stl/inc/xutility index caa3bf2735a..c4f918984bd 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -3293,10 +3293,6 @@ namespace ranges { const _It& _First, const _Se& _Last, const iter_difference_t<_It> _Count) { // Find the iterator in [_First, _Last) (of length _Count) which equals _Last _STL_INTERNAL_CHECK(_RANGES distance(_First, _Last) == _Count); - (void) _First; - (void) _Last; - (void) _Count; - if constexpr (is_same_v<_It, _Se>) { return _Last; } else { From 2337d01c281496adb308c000f49b35087436b5a0 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 23 Jun 2020 19:17:10 -0700 Subject: [PATCH 12/13] Update expected_results.txt and skipped_tests.txt for removed tests. --- tests/libcxx/expected_results.txt | 2 -- tests/libcxx/skipped_tests.txt | 2 -- 2 files changed, 4 deletions(-) diff --git a/tests/libcxx/expected_results.txt b/tests/libcxx/expected_results.txt index 55a4a6ab84d..5386ea4ba97 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -583,12 +583,10 @@ std/containers/sequences/array/array.fill/fill.fail.cpp FAIL std/containers/sequences/array/array.swap/swap.fail.cpp FAIL # STL bug: VSO-207715 We reject array. -std/containers/sequences/array/array.cons/default.pass.cpp FAIL std/containers/sequences/array/array.cons/implicit_copy.pass.cpp FAIL std/containers/sequences/array/array.cons/initialization.pass.cpp FAIL std/containers/sequences/array/array.data/data_const.pass.cpp FAIL std/containers/sequences/array/array.data/data.pass.cpp FAIL -std/containers/sequences/array/begin.pass.cpp FAIL std/containers/sequences/array/iterators.pass.cpp FAIL # STL bug: string_view doesn't enforce "non-array trivial standard-layout", related to LWG-3034. diff --git a/tests/libcxx/skipped_tests.txt b/tests/libcxx/skipped_tests.txt index 8c69d8df319..7a877efccc5 100644 --- a/tests/libcxx/skipped_tests.txt +++ b/tests/libcxx/skipped_tests.txt @@ -583,12 +583,10 @@ containers\sequences\array\array.fill\fill.fail.cpp containers\sequences\array\array.swap\swap.fail.cpp # STL bug: VSO-207715 We reject array. -containers\sequences\array\array.cons\default.pass.cpp containers\sequences\array\array.cons\implicit_copy.pass.cpp containers\sequences\array\array.cons\initialization.pass.cpp containers\sequences\array\array.data\data_const.pass.cpp containers\sequences\array\array.data\data.pass.cpp -containers\sequences\array\begin.pass.cpp containers\sequences\array\iterators.pass.cpp # STL bug: string_view doesn't enforce "non-array trivial standard-layout", related to LWG-3034. From 623dece127b1c6059eef1a2a37a9755538163dbe Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 23 Jun 2020 19:55:39 -0700 Subject: [PATCH 13/13] Update magic_comments.txt: libcxx removed c++98, sort. --- tests/libcxx/magic_comments.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/libcxx/magic_comments.txt b/tests/libcxx/magic_comments.txt index a905db0a4e7..b68a41e8387 100644 --- a/tests/libcxx/magic_comments.txt +++ b/tests/libcxx/magic_comments.txt @@ -1,6 +1,6 @@ +// REQUIRES: c++03 +// REQUIRES: c++03 || c++11 || c++14 +// REQUIRES: c++03 || c++11 || c++14 || c++17 // REQUIRES: c++11 // REQUIRES: c++11 || c++14 -// REQUIRES: c++98 || c++03 -// REQUIRES: c++98 || c++03 || c++11 || c++14 -// REQUIRES: c++98 || c++03 || c++11 || c++14 || c++17 // UNSUPPORTED: c++14, c++17, c++2a