Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions stl/inc/algorithm
Original file line number Diff line number Diff line change
Expand Up @@ -3061,6 +3061,72 @@ _FwdIt2 replace_copy(_ExPo&&, _FwdIt1 _First, _FwdIt1 _Last, _FwdIt2 _Dest, cons
}
#endif // _HAS_CXX17

#ifdef __cpp_lib_concepts
namespace ranges {
// ALIAS TEMPLATE replace_copy_result
template <class _In, class _Out>
using replace_copy_result = in_out_result<_In, _Out>;

// VARIABLE ranges::replace_copy
class _Replace_copy_fn : private _Not_quite_object {
public:
using _Not_quite_object::_Not_quite_object;

// clang-format off
template <input_iterator _It, sentinel_for<_It> _Se, class _Ty1, class _Ty2, output_iterator<const _Ty2&> _Out,
class _Pj = identity>
requires indirectly_copyable<_It, _Out>
&& indirect_binary_predicate<ranges::equal_to, projected<_It, _Pj>, const _Ty1*>
constexpr replace_copy_result<_It, _Out> operator()(
_It _First, _Se _Last, _Out _Result, const _Ty1& _Oldval, const _Ty2& _Newval, _Pj _Proj = {}) const {
_Adl_verify_range(_First, _Last);
auto _UResult = _Replace_copy_unchecked(_Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last)),
_STD move(_Result), _Oldval, _Newval, _Pass_fn(_Proj));

_Seek_wrapped(_First, _STD move(_UResult.in));
return {_STD move(_First), _STD move(_UResult.out)};
}

template <input_range _Rng, class _Ty1, class _Ty2, output_iterator<const _Ty2&> _Out, class _Pj = identity>
requires indirectly_copyable<iterator_t<_Rng>, _Out>
&& indirect_binary_predicate<ranges::equal_to, projected<iterator_t<_Rng>, _Pj>, const _Ty1*>
constexpr replace_copy_result<borrowed_iterator_t<_Rng>, _Out> operator()(
_Rng&& _Range, _Out _Result, const _Ty1& _Oldval, const _Ty2& _Newval, _Pj _Proj = {}) const {
auto _First = _RANGES begin(_Range);
auto _UResult = _Replace_copy_unchecked(_Get_unwrapped(_STD move(_First)), _Uend(_Range),
_STD move(_Result), _Oldval, _Newval, _Pass_fn(_Proj));

_Seek_wrapped(_First, _STD move(_UResult.in));
return {_STD move(_First), _STD move(_UResult.out)};
}
// clang-format on
private:
template <class _It, class _Se, class _Ty1, class _Ty2, class _Out, class _Pj>
_NODISCARD static constexpr replace_copy_result<_It, _Out> _Replace_copy_unchecked(
_It _First, const _Se _Last, _Out _Result, const _Ty1& _Oldval, const _Ty2& _Newval, _Pj _Proj) {
// copy [_First, _Last) to _Out while replacing projected _Oldval with _Newval
_STL_INTERNAL_STATIC_ASSERT(input_iterator<_It>);
_STL_INTERNAL_STATIC_ASSERT(sentinel_for<_Se, _It>);
_STL_INTERNAL_STATIC_ASSERT(output_iterator<_Out, const _Ty2&>);
_STL_INTERNAL_STATIC_ASSERT(indirectly_copyable<_It, _Out>);
_STL_INTERNAL_STATIC_ASSERT(indirect_binary_predicate<equal_to, projected<_It, _Pj>, const _Ty1*>);

for (; _First != _Last; ++_First, (void) ++_Result) {
if (_STD invoke(_Proj, *_First) == _Oldval) {
*_Result = _Newval;
} else {
*_Result = *_First;
}
}

return {_STD move(_First), _STD move(_Result)};
}
};

inline constexpr _Replace_copy_fn replace_copy{_Not_quite_object::_Construct_tag{}};
} // namespace ranges
#endif // __cpp_lib_concepts

// FUNCTION TEMPLATE replace_copy_if
template <class _InIt, class _OutIt, class _Pr, class _Ty>
_CONSTEXPR20 _OutIt replace_copy_if(_InIt _First, _InIt _Last, _OutIt _Dest, _Pr _Pred, const _Ty& _Val) {
Expand Down
1 change: 1 addition & 0 deletions tests/std/test.lst
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ tests\P0896R4_ranges_alg_minmax
tests\P0896R4_ranges_alg_mismatch
tests\P0896R4_ranges_alg_move
tests\P0896R4_ranges_alg_none_of
tests\P0896R4_ranges_alg_replace_copy
tests\P0896R4_ranges_alg_search
tests\P0896R4_ranges_alg_search_n
tests\P0896R4_ranges_alg_swap_ranges
Expand Down
4 changes: 4 additions & 0 deletions tests/std/tests/P0896R4_ranges_alg_replace_copy/env.lst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

RUNALL_INCLUDE ..\concepts_matrix.lst
57 changes: 57 additions & 0 deletions tests/std/tests/P0896R4_ranges_alg_replace_copy/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include <algorithm>
#include <cassert>
#include <concepts>
#include <ranges>
#include <utility>

#include <range_algorithm_support.hpp>
using namespace std;
using P = pair<int, int>;

// Validate that replace_copy_result aliases in_out_result
STATIC_ASSERT(same_as<ranges::replace_copy_result<int, double>, ranges::in_out_result<int, double>>);

// Validate dangling story
STATIC_ASSERT(same_as<decltype(ranges::replace_copy(borrowed<false>{}, static_cast<int*>(nullptr), 42, 5)),
ranges::replace_copy_result<ranges::dangling, int*>>);
STATIC_ASSERT(same_as<decltype(ranges::replace_copy(borrowed<true>{}, static_cast<int*>(nullptr), 42, 5)),
ranges::replace_copy_result<int*, int*>>);

struct instantiator {
static constexpr P input[5] = {{0, 99}, {1, 47}, {2, 99}, {3, 47}, {4, 99}};
static constexpr P expected[5] = {{0, 99}, {47, 1}, {2, 99}, {47, 1}, {4, 99}};

template <ranges::input_range Read, indirectly_writable<ranges::range_reference_t<Read>> Write>
static constexpr void call() {
using ranges::replace_copy, ranges::replace_copy_result, ranges::iterator_t;
{ // Validate iterator + sentinel overload
P output[5] = {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}};
Read wrapped_input{input};

auto result =
replace_copy(wrapped_input.begin(), wrapped_input.end(), Write{output}, 47, P{47, 1}, get_second);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The compiler error is:

C:\agent\_work\1\s\tests\std\tests\P0896R4_ranges_alg_replace_copy\test.cpp(35,17): error: no matching function for call to object of type 'const std::ranges::_Replace_copy_fn'
                replace_copy(wrapped_input.begin(), wrapped_input.end(), Write{output}, 47, P{47, 1}, get_second);
                ^~~~~~~~~~~~
C:/agent/_work/1/s/tests/std/include\range_algorithm_support.hpp(603,32): note: in instantiation of function template specialization 'instantiator::call<test::range<std::input_iterator_tag, const std::pair<int, int>, test::Sized::no, test::CanDifference::no, test::Common::no, test::CanCompare::no, test::ProxyRef::no>, test::iterator<std::input_iterator_tag, std::pair<int, int>, test::CanDifference::no, test::CanCompare::no, test::ProxyRef::no, test::IsWrapped::yes> >' requested here
        Continuation::template call<Args...,
                               ^
C:/agent/_work/1/s/tests/std/include\range_algorithm_support.hpp(770,32): note: in instantiation of function template specialization 'with_writable_iterators<instantiator, std::pair<int, int> >::call<test::range<std::input_iterator_tag, const std::pair<int, int>, test::Sized::no, test::CanDifference::no, test::Common::no, test::CanCompare::no, test::ProxyRef::no> >' requested here
        Continuation::template call<Args...,
                               ^
C:/agent/_work/1/s/tests/std/include\range_algorithm_support.hpp(945,83): note: in instantiation of function template specialization 'with_input_ranges<with_writable_iterators<instantiator, std::pair<int, int> >, const std::pair<int, int> >::call<>' requested here
    with_input_ranges<with_writable_iterators<Instantiator, Element2>, Element1>::call();
                                                                                  ^
C:\agent\_work\1\s\tests\std\tests\P0896R4_ranges_alg_replace_copy\test.cpp(55,20): note: in instantiation of function template specialization 'test_in_write<instantiator, const std::pair<int, int>, std::pair<int, int> >' requested here
    STATIC_ASSERT((test_in_write<instantiator, P const, P>(), true));
                   ^
D:\build\x64\out\inc\algorithm(3080,50): note: candidate template ignored: constraints not satisfied [with _It = test::iterator<std::input_iterator_tag, const std::pair<int, int>, test::CanDifference::no, test::CanCompare::no, test::ProxyRef::no, test::IsWrapped::yes>, _Se = test::sentinel<const std::pair<int, int>, test::IsWrapped::yes>, _Ty1 = int, _Ty2 = std::pair<int, int>, _Out = test::iterator<std::input_iterator_tag, std::pair<int, int>, test::CanDifference::no, test::CanCompare::no, test::ProxyRef::no, test::IsWrapped::yes>, _Pj = get_nth_fn<1>]
        constexpr replace_copy_result<_It, _Out> operator()(
                                                 ^
D:\build\x64\out\inc\algorithm(3076,86): note: because 'output_iterator<test::iterator<std::input_iterator_tag, std::pair<int, int>, test::CanDifference::no, test::CanCompare::no, test::ProxyRef::no, test::IsWrapped::yes>, const std::pair<int, int> &>' evaluated to false
        template <input_iterator _It, sentinel_for<_It> _Se, class _Ty1, class _Ty2, output_iterator<const _Ty2&> _Out,
                                                                                     ^
D:\build\x64\out\inc\xutility(844,9): note: because '*__i++ = static_cast<_Ty &&>(__t)' would be invalid: indirection requires pointer operand ('void' invalid)
        *__i++ = static_cast<_Ty&&>(__t);
        ^

The template machinery here is almost beyond my comprehension, but I observe that (1) your test differs from the other tests currently using test_in_write in that its instantiator actually verifies the indirectly_writable concept, and (2) with_writable_iterators appears to pass iterator<input, MEOW> which doesn't seem correct:

Continuation::template call<Args...,
iterator<input, Element, CanDifference::no, CanCompare::no, ProxyRef::no>>();
Continuation::template call<Args...,
iterator<input, Element, CanDifference::no, CanCompare::no, ProxyRef::yes>>();

I'm not sure if this analysis is correct.

STATIC_ASSERT(same_as<decltype(result), replace_copy_result<iterator_t<Read>, Write>>);
assert(result.in == wrapped_input.end());
assert(result.out.peek() == output + 5);
assert(ranges::equal(output, expected));
}
{ // Validate range overload
P output[5] = {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}};
Read wrapped_input{input};

auto result = replace_copy(wrapped_input, Write{output}, 47, P{47, 1}, get_second);
STATIC_ASSERT(same_as<decltype(result), replace_copy_result<iterator_t<Read>, Write>>);
assert(result.in == wrapped_input.end());
assert(result.out.peek() == output + 5);
assert(ranges::equal(output, expected));
}
}
};

int main() {
STATIC_ASSERT((test_in_write<instantiator, P const, P>(), true));
test_in_write<instantiator, P const, P>();
}