diff --git a/libcxx/docs/Status/RangesAlgorithms.csv b/libcxx/docs/Status/RangesAlgorithms.csv --- a/libcxx/docs/Status/RangesAlgorithms.csv +++ b/libcxx/docs/Status/RangesAlgorithms.csv @@ -51,8 +51,8 @@ Write,remove_copy_if,Nikolas Klauser,n/a,Not started Write,replace,Nikolas Klauser,`D126283 `_,✅ Write,replace_if,Nikolas Klauser,`D126283 `_,✅ -Write,replace_copy,Nikolas Klauser,`D129806 `_,Under review -Write,replace_copy_if,Nikolas Klauser,`D129806 `_,Under review +Write,replace_copy,Nikolas Klauser,`D129806 `_,✅ +Write,replace_copy_if,Nikolas Klauser,`D129806 `_,✅ Write,swap_ranges,Nikolas Klauser,`D116303 `_,✅ Write,reverse_copy,Nikolas Klauser,`D127211 `_,✅ Write,rotate_copy,Nikolas Klauser,`D127211 `_,✅ diff --git a/libcxx/include/__algorithm/ranges_replace_copy.h b/libcxx/include/__algorithm/ranges_replace_copy.h --- a/libcxx/include/__algorithm/ranges_replace_copy.h +++ b/libcxx/include/__algorithm/ranges_replace_copy.h @@ -10,19 +10,16 @@ #define _LIBCPP___ALGORITHM_RANGES_REPLACE_COPY_H #include <__algorithm/in_out_result.h> -#include <__algorithm/make_projected.h> -#include <__algorithm/replace_copy.h> +#include <__algorithm/ranges_replace_copy_if.h> #include <__config> #include <__functional/identity.h> #include <__functional/invoke.h> #include <__functional/ranges_operations.h> #include <__iterator/concepts.h> -#include <__iterator/iterator_traits.h> #include <__iterator/projected.h> #include <__ranges/access.h> #include <__ranges/concepts.h> #include <__ranges/dangling.h> -#include <__utility/forward.h> #include <__utility/move.h> #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) @@ -40,35 +37,45 @@ namespace __replace_copy { -struct __fn { - - template _Sent, class _Type1, class _Type2, - output_iterator _OutIter, class _Proj = identity> - requires indirectly_copyable<_InIter, _OutIter> && - indirect_binary_predicate, const _Type1*> - _LIBCPP_HIDE_FROM_ABI constexpr - replace_copy_result<_InIter, _OutIter> - operator()(_InIter __first, _Sent __last, _OutIter __result, const _Type1& __old_value, const _Type2& __new_value, + struct __fn { + template _Sent, + class _OldType, + class _NewType, + output_iterator _OutIter, + class _Proj = identity> + requires indirectly_copyable<_InIter, _OutIter> && + indirect_binary_predicate, const _OldType*> + _LIBCPP_HIDE_FROM_ABI constexpr replace_copy_result<_InIter, _OutIter> + operator()(_InIter __first, + _Sent __last, + _OutIter __result, + const _OldType& __old_value, + const _NewType& __new_value, _Proj __proj = {}) const { - // TODO: implement - (void)__first; (void)__last; (void)__result; (void)__old_value; (void)__new_value; (void)__proj; - return {}; - } - - template _OutIter, - class _Proj = identity> - requires indirectly_copyable, _OutIter> && - indirect_binary_predicate, _Proj>, const _Type1*> - _LIBCPP_HIDE_FROM_ABI constexpr - replace_copy_result, _OutIter> - operator()(_Range&& __range, _OutIter __result, const _Type1& __old_value, const _Type2& __new_value, + auto __pred = [&](const auto& __value) { return __value == __old_value; }; + return ranges::__replace_copy_if_impl( + std::move(__first), std::move(__last), std::move(__result), __pred, __new_value, __proj); + } + + template _OutIter, + class _Proj = identity> + requires indirectly_copyable, _OutIter> && + indirect_binary_predicate, _Proj>, const _OldType*> + _LIBCPP_HIDE_FROM_ABI constexpr replace_copy_result, _OutIter> + operator()(_Range&& __range, + _OutIter __result, + const _OldType& __old_value, + const _NewType& __new_value, _Proj __proj = {}) const { - // TODO: implement - (void)__range; (void)__result; (void)__old_value; (void)__new_value; (void)__proj; - return {}; - } - -}; + auto __pred = [&](const auto& __value) { return __value == __old_value; }; + return ranges::__replace_copy_if_impl( + ranges::begin(__range), ranges::end(__range), std::move(__result), __pred, __new_value, __proj); + } + }; } // namespace __replace_copy diff --git a/libcxx/include/__algorithm/ranges_replace_copy_if.h b/libcxx/include/__algorithm/ranges_replace_copy_if.h --- a/libcxx/include/__algorithm/ranges_replace_copy_if.h +++ b/libcxx/include/__algorithm/ranges_replace_copy_if.h @@ -10,19 +10,14 @@ #define _LIBCPP___ALGORITHM_RANGES_REPLACE_COPY_IF_H #include <__algorithm/in_out_result.h> -#include <__algorithm/make_projected.h> -#include <__algorithm/replace_copy_if.h> #include <__config> #include <__functional/identity.h> #include <__functional/invoke.h> -#include <__functional/ranges_operations.h> #include <__iterator/concepts.h> -#include <__iterator/iterator_traits.h> #include <__iterator/projected.h> #include <__ranges/access.h> #include <__ranges/concepts.h> #include <__ranges/dangling.h> -#include <__utility/forward.h> #include <__utility/move.h> #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) @@ -38,34 +33,51 @@ template using replace_copy_if_result = in_out_result<_InIter, _OutIter>; -namespace __replace_copy_if { - -struct __fn { - - template _Sent, class _Type, output_iterator _OutIter, - class _Proj = identity, indirect_unary_predicate> _Pred> - requires indirectly_copyable<_InIter, _OutIter> - _LIBCPP_HIDE_FROM_ABI constexpr - replace_copy_if_result<_InIter, _OutIter> - operator()(_InIter __first, _Sent __last, _OutIter __result, _Pred __pred, const _Type& __new_value, - _Proj __proj = {}) const { - // TODO: implement - (void)__first; (void)__last; (void)__result; (void)__pred; (void)__new_value; (void)__proj; - return {}; +template +_LIBCPP_HIDE_FROM_ABI constexpr replace_copy_if_result<_InIter, _OutIter> __replace_copy_if_impl( + _InIter __first, _Sent __last, _OutIter __result, _Pred& __pred, const _Type& __new_value, _Proj& __proj) { + while (__first != __last) { + if (std::invoke(__pred, std::invoke(__proj, *__first))) + *__result = __new_value; + else + *__result = *__first; + + ++__first; + ++__result; } - template _OutIter, class _Proj = identity, - indirect_unary_predicate, _Proj>> _Pred> - requires indirectly_copyable, _OutIter> - _LIBCPP_HIDE_FROM_ABI constexpr - replace_copy_if_result, _OutIter> - operator()(_Range&& __range, _OutIter __result, _Pred __pred, const _Type& __new_value, _Proj __proj = {}) const { - // TODO: implement - (void)__range; (void)__result; (void)__pred; (void)__new_value; (void)__proj; - return {}; - } + return {std::move(__first), std::move(__result)}; +} + +namespace __replace_copy_if { -}; + struct __fn { + template _Sent, + class _Type, + output_iterator _OutIter, + class _Proj = identity, + indirect_unary_predicate> _Pred> + requires indirectly_copyable<_InIter, _OutIter> + _LIBCPP_HIDE_FROM_ABI constexpr replace_copy_if_result<_InIter, _OutIter> operator()( + _InIter __first, _Sent __last, _OutIter __result, _Pred __pred, const _Type& __new_value, _Proj __proj = {}) + const { + return ranges::__replace_copy_if_impl( + std::move(__first), std::move(__last), std::move(__result), __pred, __new_value, __proj); + } + + template _OutIter, + class _Proj = identity, + indirect_unary_predicate, _Proj>> _Pred> + requires indirectly_copyable, _OutIter> + _LIBCPP_HIDE_FROM_ABI constexpr replace_copy_if_result, _OutIter> + operator()(_Range&& __range, _OutIter __result, _Pred __pred, const _Type& __new_value, _Proj __proj = {}) const { + return ranges::__replace_copy_if_impl( + ranges::begin(__range), ranges::end(__range), std::move(__result), __pred, __new_value, __proj); + } + }; } // namespace __replace_copy_if diff --git a/libcxx/include/algorithm b/libcxx/include/algorithm --- a/libcxx/include/algorithm +++ b/libcxx/include/algorithm @@ -912,6 +912,44 @@ indirectly_copyable_storable, O>) constexpr unique_copy_result, O> unique_copy(R&& r, O result, C comp = {}, Proj proj = {}); // Since C++20 + + template + using replace_copy_result = in_out_result; // Since C++20 + + template S, class T1, class T2, + output_iterator O, class Proj = identity> + requires indirectly_copyable && + indirect_binary_predicate, const T1*> + constexpr replace_copy_result + replace_copy(I first, S last, O result, const T1& old_value, const T2& new_value, + Proj proj = {}); // Since C++20 + + template O, + class Proj = identity> + requires indirectly_copyable, O> && + indirect_binary_predicate, Proj>, const T1*> + constexpr replace_copy_result, O> + replace_copy(R&& r, O result, const T1& old_value, const T2& new_value, + Proj proj = {}); // Since C++20 + + template + using replace_copy_if_result = in_out_result; // Since C++20 + + template S, class T, output_iterator O, + class Proj = identity, indirect_unary_predicate> Pred> + requires indirectly_copyable + constexpr replace_copy_if_result + replace_copy_if(I first, S last, O result, Pred pred, const T& new_value, + Proj proj = {}); // Since C++20 + + template O, class Proj = identity, + indirect_unary_predicate, Proj>> Pred> + requires indirectly_copyable, O> + constexpr replace_copy_if_result, O> + replace_copy_if(R&& r, O result, Pred pred, const T& new_value, + Proj proj = {}); // Since C++20 + } constexpr bool // constexpr in C++20 @@ -1696,6 +1734,8 @@ #include <__algorithm/ranges_remove.h> #include <__algorithm/ranges_remove_if.h> #include <__algorithm/ranges_replace.h> +#include <__algorithm/ranges_replace_copy.h> +#include <__algorithm/ranges_replace_copy_if.h> #include <__algorithm/ranges_replace_if.h> #include <__algorithm/ranges_reverse.h> #include <__algorithm/ranges_reverse_copy.h> diff --git a/libcxx/test/libcxx/algorithms/ranges_robust_against_copying_comparators.pass.cpp b/libcxx/test/libcxx/algorithms/ranges_robust_against_copying_comparators.pass.cpp --- a/libcxx/test/libcxx/algorithms/ranges_robust_against_copying_comparators.pass.cpp +++ b/libcxx/test/libcxx/algorithms/ranges_robust_against_copying_comparators.pass.cpp @@ -191,8 +191,8 @@ //(void)std::ranges::remove_copy_if(a, first2, UnaryTrue(&copies)); assert(copies == 0); (void)std::ranges::remove_if(first, last, UnaryTrue(&copies)); assert(copies == 0); (void)std::ranges::remove_if(a, UnaryTrue(&copies)); assert(copies == 0); - //(void)std::ranges::replace_copy_if(first, last, first2, UnaryTrue(&copies), value); assert(copies == 0); - //(void)std::ranges::replace_copy_if(a, first2, UnaryTrue(&copies), value); assert(copies == 0); + (void)std::ranges::replace_copy_if(first, last, first2, UnaryTrue(&copies), value); assert(copies == 0); + (void)std::ranges::replace_copy_if(a, first2, UnaryTrue(&copies), value); assert(copies == 0); (void)std::ranges::replace_if(first, last, UnaryTrue(&copies), value); assert(copies == 0); (void)std::ranges::replace_if(a, UnaryTrue(&copies), value); assert(copies == 0); (void)std::ranges::search(first, last, first2, mid2, Equal(&copies)); assert(copies == 0); diff --git a/libcxx/test/libcxx/algorithms/ranges_robust_against_copying_projections.pass.cpp b/libcxx/test/libcxx/algorithms/ranges_robust_against_copying_projections.pass.cpp --- a/libcxx/test/libcxx/algorithms/ranges_robust_against_copying_projections.pass.cpp +++ b/libcxx/test/libcxx/algorithms/ranges_robust_against_copying_projections.pass.cpp @@ -178,10 +178,10 @@ (void)std::ranges::remove(a, value, Proj(&copies)); assert(copies == 0); (void)std::ranges::remove_if(first, last, UnaryTrue(), Proj(&copies)); assert(copies == 0); (void)std::ranges::remove_if(a, UnaryTrue(), Proj(&copies)); assert(copies == 0); - //(void)std::ranges::replace_copy(first, last, first2, value, T(), Proj(&copies)); assert(copies == 0); - //(void)std::ranges::replace_copy(a, first2, value, T(), Proj(&copies)); assert(copies == 0); - //(void)std::ranges::replace_copy_if(first, last, first2, UnaryTrue(), T(), Proj(&copies)); assert(copies == 0); - //(void)std::ranges::replace_copy_if(a, first2, UnaryTrue(), T(), Proj(&copies)); assert(copies == 0); + (void)std::ranges::replace_copy(first, last, first2, value, T(), Proj(&copies)); assert(copies == 0); + (void)std::ranges::replace_copy(a, first2, value, T(), Proj(&copies)); assert(copies == 0); + (void)std::ranges::replace_copy_if(first, last, first2, UnaryTrue(), T(), Proj(&copies)); assert(copies == 0); + (void)std::ranges::replace_copy_if(a, first2, UnaryTrue(), T(), Proj(&copies)); assert(copies == 0); (void)std::ranges::replace(first, last, value, T(), Proj(&copies)); assert(copies == 0); (void)std::ranges::replace(a, value, T(), Proj(&copies)); assert(copies == 0); (void)std::ranges::replace_if(first, last, UnaryTrue(), T(), Proj(&copies)); assert(copies == 0); diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.replace/ranges_replace_copy.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.replace/ranges_replace_copy.pass.cpp --- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.replace/ranges_replace_copy.pass.cpp +++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.replace/ranges_replace_copy.pass.cpp @@ -26,23 +26,223 @@ // replace_copy(R&& r, O result, const T1& old_value, const T2& new_value, // Proj proj = {}); // Since C++20 -// TODO: synopsis - #include #include #include #include #include +#include #include "almost_satisfies_types.h" +#include "counting_projection.h" #include "test_iterators.h" -// TODO: SFINAE tests. +template , class OutIter = int*> +concept HasReplaceCopyIter = + requires(Iter&& first, Sent&& last, OutIter&& result) { + std::ranges::replace_copy( + std::forward(first), std::forward(last), std::forward(result), 0, 0); +}; + +static_assert(HasReplaceCopyIter); + +// !input_iterator +static_assert(!HasReplaceCopyIter); +static_assert(!HasReplaceCopyIter); +static_assert(!HasReplaceCopyIter); + +// !sentinel_for +static_assert(!HasReplaceCopyIter); +static_assert(!HasReplaceCopyIter); + +// !output_iterator +static_assert(!HasReplaceCopyIter); +static_assert(!HasReplaceCopyIter); + +// !indirectly_copyable +static_assert(!HasReplaceCopyIter); + +// !indirect_binary_predicate, const T1*> +static_assert(!HasReplaceCopyIter); + +template +concept HasReplaceCopyRange = requires(Range&& range, OutIter&& result) { + std::ranges::replace_copy(std::forward(range), std::forward(result), 0, 0); +}; + +template +using R = UncheckedRange; + +static_assert(HasReplaceCopyRange>); + +// !input_range +static_assert(!HasReplaceCopyRange); +static_assert(!HasReplaceCopyRange); +static_assert(!HasReplaceCopyRange); +static_assert(!HasReplaceCopyRange); +static_assert(!HasReplaceCopyRange); + +// !output_iterator +static_assert(!HasReplaceCopyRange, OutputIteratorNotIndirectlyWritable>); +static_assert(!HasReplaceCopyRange, OutputIteratorNotInputOrOutputIterator>); + +// !indirectly_copyable, O> +static_assert(!HasReplaceCopyRange, R>); + +// !indirect_binary_predicate, Proj>, const T1*> +static_assert(!HasReplaceCopyRange>); + +template +struct Data { + std::array input; + int old_value; + int new_value; + std::array expected; +}; + +template +constexpr void test(Data d) { + { // iterator overload + std::array output; + + auto first = InIter(d.input.data()); + auto last = Sent(InIter(d.input.data() + d.input.size())); + auto result = OutIter(output.data()); + + std::same_as> decltype(auto) ret = + std::ranges::replace_copy(std::move(first), std::move(last), std::move(result), d.old_value, d.new_value); + assert(base(ret.in) == d.input.data() + d.input.size()); + assert(base(ret.out) == output.data() + output.size()); + assert(d.expected == output); + } + + { // range overload + std::array output; + + auto range = std::ranges::subrange(InIter(d.input.data()), Sent(InIter(d.input.data() + d.input.size()))); + auto result = OutIter(output.data()); + + std::same_as> decltype(auto) ret = + std::ranges::replace_copy(range, result, d.old_value, d.new_value); + assert(base(ret.in) == d.input.data() + d.input.size()); + assert(base(ret.out) == output.data() + output.size()); + assert(d.expected == output); + } +} + +template +constexpr void tests() { + // simple test + test({.input = {1, 2, 3, 4}, .old_value = 2, .new_value = 5, .expected = {1, 5, 3, 4}}); + // empty range + test({.input = {}, .old_value = 2, .new_value = 5, .expected = {}}); + // all elements match + test({.input = {1, 1, 1, 1}, .old_value = 1, .new_value = 2, .expected = {2, 2, 2, 2}}); + // no element matches + test({.input = {1, 1, 1, 1}, .old_value = 2, .new_value = 3, .expected = {1, 1, 1, 1}}); + // old_value and new_value are identical - match + test({.input = {1, 1, 1, 1}, .old_value = 1, .new_value = 1, .expected = {1, 1, 1, 1}}); + // old_value and new_value are identical - no match + test({.input = {1, 1, 1, 1}, .old_value = 2, .new_value = 2, .expected = {1, 1, 1, 1}}); + // more elements + test( + {.input = {1, 2, 3, 4, 5, 6, 7}, .old_value = 2, .new_value = 3, .expected = {1, 3, 3, 4, 5, 6, 7}}); + // single element - match + test({.input = {1}, .old_value = 1, .new_value = 5, .expected = {5}}); + // single element - no match + test({.input = {2}, .old_value = 1, .new_value = 5, .expected = {2}}); +} + +template +constexpr void test_output_iterators() { + tests>(); + tests>(); + tests>(); + tests>(); + tests>(); + tests(); +} + +template +constexpr void test_sentinels() { + test_output_iterators(); + test_output_iterators>(); + test_output_iterators>(); +} constexpr bool test() { - // TODO: main tests. - // TODO: A custom comparator works. - // TODO: A custom projection works. + test_output_iterators, sentinel_wrapper>>(); + test_output_iterators, sentinel_wrapper>>(); + test_sentinels>(); + test_sentinels>(); + test_sentinels>(); + test_sentinels>(); + test_sentinels(); + test_sentinels(); + + { // check that a custom projection works + struct S { + int i; + }; + + { // iterator overload + S a[] = {{1}, {2}, {3}, {4}}; + S b[4]; + auto ret = std::ranges::replace_copy(std::begin(a), std::end(a), std::begin(b), 1, S{2}, &S::i); + assert(ret.in == std::end(a)); + assert(ret.out == std::end(b)); + } + + { // range overload + S a[] = {{1}, {2}, {3}, {4}}; + S b[4]; + auto ret = std::ranges::replace_copy(a, std::begin(b), 1, S{2}, &S::i); + assert(ret.in == std::end(a)); + assert(ret.out == std::end(b)); + } + } + + { // Complexity: exactly `last - first` applications of the corresponding predicate and any projection. + { // iterator overload + int proj_count = 0; + int a[] = {1, 2, 3, 4}; + int b[4]; + std::ranges::replace_copy( + std::begin(a), std::end(a), std::begin(b), 0, 0, counting_projection(proj_count)); + assert(proj_count == 4); + } + + { // range overload + int proj_count = 0; + int a[] = {1, 2, 3, 4}; + int b[4]; + std::ranges::replace_copy(a, std::begin(b), 0, 0, counting_projection(proj_count)); + assert(proj_count == 4); + } + } + + { // using different types for the old and new values works + struct S { + constexpr operator int() const { return 0; } + constexpr bool operator==(const S&) const = default; + constexpr bool operator==(int i) const { return i == 0; } + }; + struct T { + constexpr operator int() const { return 1; } + }; + { + int a[] = {0, 1, 2, 3}; + int b[4]; + std::ranges::replace_copy(std::begin(a), std::end(a), std::begin(b), S{}, T{}); + assert(std::ranges::equal(b, std::array{1, 1, 2, 3})); + } + { + int a[] = {0, 1, 2, 3}; + int b[4]; + std::ranges::replace_copy(a, std::begin(b), S{}, T{}); + assert(std::ranges::equal(b, std::array{1, 1, 2, 3})); + } + } return true; } diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.replace/ranges_replace_copy_if.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.replace/ranges_replace_copy_if.pass.cpp --- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.replace/ranges_replace_copy_if.pass.cpp +++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.replace/ranges_replace_copy_if.pass.cpp @@ -30,16 +30,229 @@ #include #include #include +#include #include "almost_satisfies_types.h" +#include "counting_predicates.h" +#include "counting_projection.h" #include "test_iterators.h" -// TODO: SFINAE tests. +struct FalsePredicate { + constexpr bool operator()(int) { return false; } +}; + +template , class OutIter = int*> +concept HasReplaceCopyIfIter = requires(Iter&& first, Sent&& last, OutIter&& result) { + std::ranges::replace_copy_if( + std::forward(first), std::forward(last), std::forward(result), FalsePredicate{}, 0); +}; + +static_assert(HasReplaceCopyIfIter); + +// !input_iterator +static_assert(!HasReplaceCopyIfIter); +static_assert(!HasReplaceCopyIfIter); +static_assert(!HasReplaceCopyIfIter); + +// !sentinel_for +static_assert(!HasReplaceCopyIfIter); +static_assert(!HasReplaceCopyIfIter); + +// !output_iterator +static_assert(!HasReplaceCopyIfIter); +static_assert(!HasReplaceCopyIfIter); + +// !indirect_unary_predicate> Pred> +static_assert(!HasReplaceCopyIfIter); +static_assert(!HasReplaceCopyIfIter); + +// !indirectly_copyable +static_assert(!HasReplaceCopyIfIter); + +template +concept HasReplaceCopyIfRange = requires(Range&& range, OutIter&& result) { + std::ranges::replace_copy_if(std::forward(range), std::forward(result), FalsePredicate{}, 0); +}; + +template +using R = UncheckedRange; + +static_assert(HasReplaceCopyIfRange>); + +// !input_range +static_assert(!HasReplaceCopyIfRange); +static_assert(!HasReplaceCopyIfRange); +static_assert(!HasReplaceCopyIfRange); +static_assert(!HasReplaceCopyIfRange); +static_assert(!HasReplaceCopyIfRange); + +// !output_iterator +static_assert(!HasReplaceCopyIfRange, OutputIteratorNotIndirectlyWritable>); +static_assert(!HasReplaceCopyIfRange, OutputIteratorNotInputOrOutputIterator>); + +// !indirect_unary_predicate, Proj>> Pred> +static_assert(!HasReplaceCopyIfRange>); + +// !indirectly_copyable, O> +static_assert(!HasReplaceCopyIfRange, int**>); + +template +struct Data { + std::array input; + int cutoff; + int new_value; + std::array expected; +}; + +template +constexpr void test(Data d) { + { // iterator overload + std::array output; + + auto first = InIter(d.input.data()); + auto last = Sent(InIter(d.input.data() + d.input.size())); + auto result = OutIter(output.data()); + + auto pred = [&](int i) { return i < d.cutoff; }; + + std::same_as> decltype(auto) ret = + std::ranges::replace_copy_if(std::move(first), std::move(last), std::move(result), pred, d.new_value); + assert(base(ret.in) == d.input.data() + d.input.size()); + assert(base(ret.out) == output.data() + output.size()); + assert(d.expected == output); + } + + { // range overload + std::array output; + + auto range = std::ranges::subrange(InIter(d.input.data()), Sent(InIter(d.input.data() + d.input.size()))); + auto result = OutIter(output.data()); + + auto pred = [&](int i) { return i < d.cutoff; }; + + std::same_as> decltype(auto) ret = + std::ranges::replace_copy_if(range, result, pred, d.new_value); + assert(base(ret.in) == d.input.data() + d.input.size()); + assert(base(ret.out) == output.data() + output.size()); + assert(d.expected == output); + } +} + +template +constexpr void tests() { + // simple test + test({.input = {1, 2, 3, 4}, .cutoff = 2, .new_value = 5, .expected = {5, 2, 3, 4}}); + // empty range + test({.input = {}, .cutoff = 2, .new_value = 5, .expected = {}}); + // all elements match + test({.input = {1, 1, 1, 1}, .cutoff = 2, .new_value = 2, .expected = {2, 2, 2, 2}}); + // no element matches + test({.input = {1, 1, 1, 1}, .cutoff = 1, .new_value = 3, .expected = {1, 1, 1, 1}}); + // more elements + test( + {.input = {1, 2, 3, 4, 5, 6, 7}, .cutoff = 3, .new_value = 3, .expected = {3, 3, 3, 4, 5, 6, 7}}); + // single element - match + test({.input = {1}, .cutoff = 2, .new_value = 5, .expected = {5}}); + // single element - no match + test({.input = {2}, .cutoff = 2, .new_value = 5, .expected = {2}}); +} + +template +constexpr void test_output_iterators() { + tests>(); + tests>(); + tests>(); + tests>(); + tests>(); + tests(); +} + +template +constexpr void test_sentinels() { + test_output_iterators(); + test_output_iterators>(); + test_output_iterators>(); +} constexpr bool test() { - // TODO: main tests. - // TODO: A custom comparator works. - // TODO: A custom projection works. + test_output_iterators, sentinel_wrapper>>(); + test_output_iterators, sentinel_wrapper>>(); + test_sentinels>(); + test_sentinels>(); + test_sentinels>(); + test_sentinels>(); + test_sentinels(); + test_sentinels(); + + { // check that a custom projection works + struct S { + int i; + }; + + { // iterator overload + S a[] = {{1}, {2}, {3}, {4}}; + S b[4]; + auto ret = std::ranges::replace_copy_if(std::begin(a), std::end(a), std::begin(b), FalsePredicate{}, S{2}, &S::i); + assert(ret.in == std::end(a)); + assert(ret.out == std::end(b)); + assert(std::ranges::equal(a, b, {}, &S::i, &S::i)); + } + + { // range overload + S a[] = {{1}, {2}, {3}, {4}}; + S b[4]; + auto ret = std::ranges::replace_copy_if(a, std::begin(b), FalsePredicate{}, S{2}, &S::i); + assert(ret.in == std::end(a)); + assert(ret.out == std::end(b)); + assert(std::ranges::equal(a, b, {}, &S::i, &S::i)); + } + } + + { // Complexity: exactly `last - first` applications of the corresponding predicate and any projection. + { // iterator overload + int pred_count = 0; + int proj_count = 0; + int a[] = {1, 2, 3, 4}; + int b[4]; + + std::ranges::replace_copy_if( + std::begin(a), std::end(a), std::begin(b), + counting_predicate(FalsePredicate{}, pred_count), 0, counting_projection(proj_count)); + assert(pred_count == 4); + assert(proj_count == 4); + } + + { // range overload + int pred_count = 0; + int proj_count = 0; + int a[] = {1, 2, 3, 4}; + int b[4]; + + std::ranges::replace_copy_if(a, std::begin(b), + counting_predicate(FalsePredicate{}, pred_count), 0, counting_projection(proj_count)); + assert(pred_count == 4); + assert(proj_count == 4); + } + } + + { // using different types for the old and new values works + struct S { + constexpr operator int() const { return 1; } + }; + { + int a[] = {0, 0, 2, 3}; + int b[4]; + std::ranges::replace_copy_if(std::begin(a), std::end(a), std::begin(b), [](int i) { return i < 2; }, S{}); + assert(std::ranges::equal(b, std::array{1, 1, 2, 3})); + } + + { + int a[] = {0, 0, 2, 3}; + int b[4]; + std::ranges::replace_copy_if(a, std::begin(b), [](int i) { return i < 2; }, S{}); + assert(std::ranges::equal(b, std::array{1, 1, 2, 3})); + } + } return true; } diff --git a/libcxx/test/std/algorithms/ranges_result_alias_declarations.compile.pass.cpp b/libcxx/test/std/algorithms/ranges_result_alias_declarations.compile.pass.cpp --- a/libcxx/test/std/algorithms/ranges_result_alias_declarations.compile.pass.cpp +++ b/libcxx/test/std/algorithms/ranges_result_alias_declarations.compile.pass.cpp @@ -33,8 +33,8 @@ static_assert(std::is_same_v, partial_sort_copy_result>); // static_assert(std::is_same_v, remove_copy_result>); // static_assert(std::is_same_v, remove_copy_if_result>); -// static_assert(std::is_same_v, replace_copy_result>); -// static_assert(std::is_same_v, replace_copy_if_result>); +static_assert(std::is_same_v, replace_copy_result>); +static_assert(std::is_same_v, replace_copy_if_result>); // static_assert(std::is_same_v, reverse_copy_result>); // static_assert(std::is_same_v, rotate_copy_result>); static_assert(std::is_same_v, set_difference_result>); diff --git a/libcxx/test/std/algorithms/ranges_robust_against_dangling.pass.cpp b/libcxx/test/std/algorithms/ranges_robust_against_dangling.pass.cpp --- a/libcxx/test/std/algorithms/ranges_robust_against_dangling.pass.cpp +++ b/libcxx/test/std/algorithms/ranges_robust_against_dangling.pass.cpp @@ -80,6 +80,8 @@ using std::ranges::partition_copy_result; //using std::ranges::remove_copy_result; //using std::ranges::remove_copy_if_result; + using std::ranges::replace_copy_result; + using std::ranges::replace_copy_if_result; using std::ranges::reverse_copy_result; using std::ranges::rotate_copy_result; using std::ranges::set_difference_result; @@ -150,8 +152,8 @@ //dangling_1st>(std::ranges::remove_copy_if, in, out, unary_pred); dangling_1st(std::ranges::replace, in, x, x); dangling_1st(std::ranges::replace_if, in, std::identity{}, x); - //dangling_1st(std::ranges::replace_copy, in, out, x, x); - //dangling_1st(std::ranges::replace_copy_if, in, out, x, x); + dangling_1st>(std::ranges::replace_copy, in, out, x, x); + dangling_1st>(std::ranges::replace_copy_if, in, out, unary_pred, x); dangling_1st>(std::ranges::swap_ranges, in, in2); dangling_2nd>(std::ranges::swap_ranges, in, in2); dangling_both>(std::ranges::swap_ranges, in, in2); diff --git a/libcxx/test/std/algorithms/ranges_robust_against_nonbool_predicates.pass.cpp b/libcxx/test/std/algorithms/ranges_robust_against_nonbool_predicates.pass.cpp --- a/libcxx/test/std/algorithms/ranges_robust_against_nonbool_predicates.pass.cpp +++ b/libcxx/test/std/algorithms/ranges_robust_against_nonbool_predicates.pass.cpp @@ -114,7 +114,7 @@ test(std::ranges::copy_if, in, out, unary_pred); //test(std::ranges::remove_copy_if, in, out, unary_pred); test(std::ranges::replace_if, in, unary_pred, x); - //test(std::ranges::replace_copy_if, in, out, unary_pred, x); + test(std::ranges::replace_copy_if, in, out, unary_pred, x); test(std::ranges::unique_copy, in, out, binary_pred); test(std::ranges::partition_copy, in, out, out2, unary_pred); test(std::ranges::partial_sort_copy, in, in2, binary_pred); diff --git a/libcxx/test/std/algorithms/ranges_robust_against_omitting_invoke.pass.cpp b/libcxx/test/std/algorithms/ranges_robust_against_omitting_invoke.pass.cpp --- a/libcxx/test/std/algorithms/ranges_robust_against_omitting_invoke.pass.cpp +++ b/libcxx/test/std/algorithms/ranges_robust_against_omitting_invoke.pass.cpp @@ -134,8 +134,8 @@ // `replace*` algorithms only use the projection to compare the elements, not to write them. test(std::ranges::replace, in, x, a, &Bar::val); test(std::ranges::replace_if, in, &Foo::unary_pred, a, &Bar::val); - //test(std::ranges::replace_copy, in, out, x, a, &Bar::val); - //test(std::ranges::replace_copy_if, in, out, pred, a, &Bar::val); + test(std::ranges::replace_copy, in, out, x, a, &Bar::val); + test(std::ranges::replace_copy_if, in, out, &Foo::unary_pred, a, &Bar::val); // `swap_ranges` has neither a projection nor a predicate. // `reverse_copy` has neither a projection nor a predicate. // `rotate_copy` has neither a projection nor a predicate. diff --git a/libcxx/test/std/algorithms/ranges_robust_against_proxy_iterators.pass.cpp b/libcxx/test/std/algorithms/ranges_robust_against_proxy_iterators.pass.cpp --- a/libcxx/test/std/algorithms/ranges_robust_against_proxy_iterators.pass.cpp +++ b/libcxx/test/std/algorithms/ranges_robust_against_proxy_iterators.pass.cpp @@ -129,8 +129,8 @@ //test(std::ranges::remove_copy_if, in, out, unary_pred); test(std::ranges::replace, in, x, x); test(std::ranges::replace_if, in, unary_pred, x); - //test(std::ranges::replace_copy, in, out, x, x); - //test(std::ranges::replace_copy_if, in, out, unary_pred, x); + test(std::ranges::replace_copy, in, out, x, x); + test(std::ranges::replace_copy_if, in, out, unary_pred, x); } test(std::ranges::swap_ranges, in, in2); if constexpr (std::copyable) { diff --git a/libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp b/libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp --- a/libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp +++ b/libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp @@ -123,8 +123,8 @@ //static_assert(test(std::ranges::remove_copy_if, a, a, odd)); static_assert(test(std::ranges::remove_if, a, odd)); static_assert(test(std::ranges::replace, a, 42, 43)); -//static_assert(test(std::ranges::replace_copy, a, a, 42, 43)); -//static_assert(test(std::ranges::replace_copy_if, a, a, odd, 43)); +static_assert(test(std::ranges::replace_copy, a, a, 42, 43)); +static_assert(test(std::ranges::replace_copy_if, a, a, odd, 43)); static_assert(test(std::ranges::replace_if, a, odd, 43)); static_assert(test(std::ranges::reverse, a)); static_assert(test(std::ranges::reverse_copy, a, a)); diff --git a/libcxx/test/support/counting_predicates.h b/libcxx/test/support/counting_predicates.h --- a/libcxx/test/support/counting_predicates.h +++ b/libcxx/test/support/counting_predicates.h @@ -62,6 +62,12 @@ constexpr counting_predicate() = default; constexpr counting_predicate(Predicate pred, int& count) : pred_(std::move(pred)), count_(&count) {} + template + constexpr decltype(auto) operator()(Args&& ...args) { + ++(*count_); + return pred_(std::forward(args)...); + } + template constexpr decltype(auto) operator()(Args&& ...args) const { ++(*count_);