diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 945936ee546..b72e8a35a55 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -3266,6 +3266,120 @@ _FwdIt3 transform(_ExPo&& _Exec, const _FwdIt1 _First1, const _FwdIt1 _Last1, co _Fn _Func) noexcept; // terminates #endif // _HAS_CXX17 +#ifdef __cpp_lib_concepts +namespace ranges { + // ALIAS TEMPLATE unary_transform_result + template + using unary_transform_result = in_out_result<_In, _Out>; + + // ALIAS TEMPLATE binary_transform_result + template + using binary_transform_result = in_in_out_result<_In1, _In2, _Out>; + + // VARIABLE ranges::transform + class _Transform_fn : private _Not_quite_object { + public: + using _Not_quite_object::_Not_quite_object; + + // clang-format off + template _Se, weakly_incrementable _Out, copy_constructible _Fn, + class _Pj = identity> + requires indirectly_writable<_Out, indirect_result_t<_Fn&, projected<_It, _Pj>>> + constexpr unary_transform_result<_It, _Out> operator()( + _It _First, _Se _Last, _Out _Result, _Fn _Func, _Pj _Proj = {}) const { + _Adl_verify_range(_First, _Last); + auto _UResult = _Transform_unary_unchecked(_Get_unwrapped(_STD move(_First)), + _Get_unwrapped(_STD move(_Last)), _STD move(_Result), _Pass_fn(_Func), _Pass_fn(_Proj)); + + _Seek_wrapped(_First, _STD move(_UResult.in)); + return {_STD move(_First), _STD move(_UResult.out)}; + } + + template + requires indirectly_writable<_Out, indirect_result_t<_Fn&, projected, _Pj>>> + constexpr unary_transform_result, _Out> operator()( + _Rng&& _Range, _Out _Result, _Fn _Func, _Pj _Proj = {}) const { + auto _First = _RANGES begin(_Range); + auto _UResult = _Transform_unary_unchecked( + _Get_unwrapped(_STD move(_First)), _Uend(_Range), _STD move(_Result), _Pass_fn(_Func), _Pass_fn(_Proj)); + + _Seek_wrapped(_First, _STD move(_UResult.in)); + return {_STD move(_First), _STD move(_UResult.out)}; + } + + template _Se1, input_iterator _It2, sentinel_for<_It2> _Se2, + weakly_incrementable _Out, copy_constructible _Fn, class _Pj1 = identity, class _Pj2 = identity> + requires indirectly_writable<_Out, indirect_result_t<_Fn&, projected<_It1, _Pj1>, projected<_It2, _Pj2>>> + constexpr binary_transform_result<_It1, _It2, _Out> operator()(_It1 _First1, _Se1 _Last1, _It2 _First2, + _Se2 _Last2, _Out _Result, _Fn _Func, _Pj1 _Proj1 = {}, _Pj2 _Proj2 = {}) const { + _Adl_verify_range(_First1, _Last1); + _Adl_verify_range(_First2, _Last2); + auto _UResult = + _Transform_binary_unchecked(_Get_unwrapped(_STD move(_First1)), _Get_unwrapped(_STD move(_Last1)), + _Get_unwrapped(_STD move(_First2)), _Get_unwrapped(_STD move(_Last2)), _STD move(_Result), + _Pass_fn(_Func), _Pass_fn(_Proj1), _Pass_fn(_Proj2)); + + _Seek_wrapped(_First1, _STD move(_UResult.in1)); + _Seek_wrapped(_First2, _STD move(_UResult.in2)); + return {_STD move(_First1), _STD move(_First2), _STD move(_UResult.out)}; + } + + template + requires indirectly_writable<_Out, indirect_result_t<_Fn&, projected, _Pj1>, + projected, _Pj2>>> + constexpr binary_transform_result, borrowed_iterator_t<_Rng2>, _Out> operator()( + _Rng1&& _Range1, _Rng2&& _Range2, _Out _Result, _Fn _Func, _Pj1 _Proj1 = {}, _Pj2 _Proj2 = {}) const { + auto _First1 = _RANGES begin(_Range1); + auto _First2 = _RANGES begin(_Range2); + auto _UResult = _Transform_binary_unchecked(_Get_unwrapped(_STD move(_First1)), _Uend(_Range1), + _Get_unwrapped(_STD move(_First2)), _Uend(_Range2), _STD move(_Result), _Pass_fn(_Func), + _Pass_fn(_Proj1), _Pass_fn(_Proj2)); + + _Seek_wrapped(_First1, _STD move(_UResult.in1)); + _Seek_wrapped(_First2, _STD move(_UResult.in2)); + return {_STD move(_First1), _STD move(_First2), _STD move(_UResult.out)}; + } + // clang-format on + + private: + template + _NODISCARD static constexpr unary_transform_result<_It, _Out> _Transform_unary_unchecked( + _It _First, const _Se _Last, _Out _Result, _Fn _Func, _Pj _Proj) { + // transform projected [_First, _Last) with _Func + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It>); + _STL_INTERNAL_STATIC_ASSERT(weakly_incrementable<_Out>); + _STL_INTERNAL_STATIC_ASSERT(indirectly_writable<_Out, indirect_result_t<_Fn&, projected<_It, _Pj>>>); + + for (; _First != _Last; ++_First, (void) ++_Result) { + *_Result = _STD invoke(_Func, _STD invoke(_Proj, *_First)); + } + + return {_STD move(_First), _STD move(_Result)}; + } + + template + _NODISCARD static constexpr binary_transform_result<_It1, _It2, _Out> _Transform_binary_unchecked(_It1 _First1, + const _Se1 _Last1, _It2 _First2, const _Se2 _Last2, _Out _Result, _Fn _Func, _Pj1 _Proj1, _Pj2 _Proj2) { + // transform projected [_First1, _Last1) and projected [_First2, _Last2) with _Func + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It1>); + _STL_INTERNAL_STATIC_ASSERT(input_iterator<_It2>); + _STL_INTERNAL_STATIC_ASSERT(weakly_incrementable<_Out>); + _STL_INTERNAL_STATIC_ASSERT( + indirectly_writable<_Out, indirect_result_t<_Fn&, projected<_It1, _Pj1>, projected<_It2, _Pj2>>>); + + for (; _First1 != _Last1 && _First2 != _Last2; ++_First1, (void) ++_First2, ++_Result) { + *_Result = _STD invoke(_Func, _STD invoke(_Proj1, *_First1), _STD invoke(_Proj2, *_First2)); + } + + return {_STD move(_First1), _STD move(_First2), _STD move(_Result)}; + } + }; + + inline constexpr _Transform_fn transform{_Not_quite_object::_Construct_tag{}}; +} // namespace ranges +#endif // __cpp_lib_concepts + // FUNCTION TEMPLATE replace template _CONSTEXPR20 void replace(const _FwdIt _First, const _FwdIt _Last, const _Ty& _Oldval, const _Ty& _Newval) { diff --git a/tests/std/include/range_algorithm_support.hpp b/tests/std/include/range_algorithm_support.hpp index d5fa87ddd08..4795d82cb3e 100644 --- a/tests/std/include/range_algorithm_support.hpp +++ b/tests/std/include/range_algorithm_support.hpp @@ -1018,6 +1018,11 @@ constexpr void test_read_write() { with_input_iterators, Element1>::call(); } +template +constexpr void test_in_in_write() { + with_input_ranges, Element2>, Element1>::call(); +} + template struct get_nth_fn { template diff --git a/tests/std/test.lst b/tests/std/test.lst index 4c9d6bf510c..ff3cf7ba453 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -273,6 +273,8 @@ tests\P0896R4_ranges_alg_replace_if tests\P0896R4_ranges_alg_search tests\P0896R4_ranges_alg_search_n tests\P0896R4_ranges_alg_swap_ranges +tests\P0896R4_ranges_alg_transform_binary +tests\P0896R4_ranges_alg_transform_unary tests\P0896R4_ranges_iterator_machinery tests\P0896R4_ranges_range_machinery tests\P0896R4_ranges_subrange diff --git a/tests/std/tests/P0896R4_ranges_alg_copy/test.cpp b/tests/std/tests/P0896R4_ranges_alg_copy/test.cpp index 6fd9130e399..da5250e5256 100644 --- a/tests/std/tests/P0896R4_ranges_alg_copy/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_copy/test.cpp @@ -15,10 +15,9 @@ using namespace std; STATIC_ASSERT(same_as, ranges::in_out_result>); // Validate dangling story -STATIC_ASSERT(same_as{}, static_cast(nullptr))), - ranges::copy_result>); STATIC_ASSERT( - same_as{}, static_cast(nullptr))), ranges::copy_result>); + same_as{}, nullptr_to)), ranges::copy_result>); +STATIC_ASSERT(same_as{}, nullptr_to)), ranges::copy_result>); struct instantiator { static constexpr int input[3] = {13, 42, 1729}; diff --git a/tests/std/tests/P0896R4_ranges_alg_copy_if/test.cpp b/tests/std/tests/P0896R4_ranges_alg_copy_if/test.cpp index aa257403649..95795d1ab9a 100644 --- a/tests/std/tests/P0896R4_ranges_alg_copy_if/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_copy_if/test.cpp @@ -65,10 +65,10 @@ constexpr auto is_odd = [](int const x) { return x % 2 != 0; }; STATIC_ASSERT(same_as, ranges::in_out_result>); // Validate dangling story -STATIC_ASSERT(same_as{}, static_cast(nullptr), is_odd)), +STATIC_ASSERT(same_as{}, nullptr_to, is_odd)), ranges::copy_if_result>); -STATIC_ASSERT(same_as{}, static_cast(nullptr), is_odd)), - ranges::copy_if_result>); +STATIC_ASSERT( + same_as{}, nullptr_to, is_odd)), ranges::copy_if_result>); struct instantiator { static constexpr P input[3] = {{1, 99}, {4, 98}, {5, 97}}; diff --git a/tests/std/tests/P0896R4_ranges_alg_move/test.cpp b/tests/std/tests/P0896R4_ranges_alg_move/test.cpp index c97f016e4bc..34c485670ef 100644 --- a/tests/std/tests/P0896R4_ranges_alg_move/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_move/test.cpp @@ -27,10 +27,9 @@ struct int_wrapper { STATIC_ASSERT(same_as, ranges::in_out_result>); // Validate dangling story -STATIC_ASSERT(same_as{}, static_cast(nullptr))), - ranges::move_result>); STATIC_ASSERT( - same_as{}, static_cast(nullptr))), ranges::move_result>); + same_as{}, nullptr_to)), ranges::move_result>); +STATIC_ASSERT(same_as{}, nullptr_to)), ranges::move_result>); struct instantiator { static constexpr int_wrapper expected_output[3] = {13, 55, 12345}; diff --git a/tests/std/tests/P0896R4_ranges_alg_replace_copy/test.cpp b/tests/std/tests/P0896R4_ranges_alg_replace_copy/test.cpp index 965c5ecd5ac..b020fbcd8c2 100644 --- a/tests/std/tests/P0896R4_ranges_alg_replace_copy/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_replace_copy/test.cpp @@ -15,9 +15,9 @@ using P = pair; STATIC_ASSERT(same_as, ranges::in_out_result>); // Validate dangling story -STATIC_ASSERT(same_as{}, static_cast(nullptr), 42, 5)), +STATIC_ASSERT(same_as{}, nullptr_to, 42, 5)), ranges::replace_copy_result>); -STATIC_ASSERT(same_as{}, static_cast(nullptr), 42, 5)), +STATIC_ASSERT(same_as{}, nullptr_to, 42, 5)), ranges::replace_copy_result>); struct instantiator { diff --git a/tests/std/tests/P0896R4_ranges_alg_replace_copy_if/test.cpp b/tests/std/tests/P0896R4_ranges_alg_replace_copy_if/test.cpp index 0048304155c..57afc1c7226 100644 --- a/tests/std/tests/P0896R4_ranges_alg_replace_copy_if/test.cpp +++ b/tests/std/tests/P0896R4_ranges_alg_replace_copy_if/test.cpp @@ -17,9 +17,9 @@ constexpr auto matches = [](int const val) { return val == 47; }; STATIC_ASSERT(same_as, ranges::in_out_result>); // Validate dangling story -STATIC_ASSERT(same_as{}, static_cast(nullptr), matches, 5)), +STATIC_ASSERT(same_as{}, nullptr_to, matches, 5)), ranges::replace_copy_if_result>); -STATIC_ASSERT(same_as{}, static_cast(nullptr), matches, 5)), +STATIC_ASSERT(same_as{}, nullptr_to, matches, 5)), ranges::replace_copy_if_result>); struct instantiator { diff --git a/tests/std/tests/P0896R4_ranges_alg_transform_binary/env.lst b/tests/std/tests/P0896R4_ranges_alg_transform_binary/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_transform_binary/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_transform_binary/test.cpp b/tests/std/tests/P0896R4_ranges_alg_transform_binary/test.cpp new file mode 100644 index 00000000000..11c31b21f0c --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_transform_binary/test.cpp @@ -0,0 +1,128 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include + +#include + +using namespace std; +using P = pair; + +// Validate that binary_transform_result aliases in_in_out_result +STATIC_ASSERT(same_as, ranges::in_in_out_result>); + +// Validate dangling story +STATIC_ASSERT(same_as{}, borrowed{}, nullptr_to, plus{})), + ranges::binary_transform_result>); +STATIC_ASSERT(same_as{}, borrowed{}, nullptr_to, plus{})), + ranges::binary_transform_result>); +STATIC_ASSERT(same_as{}, borrowed{}, nullptr_to, plus{})), + ranges::binary_transform_result>); +STATIC_ASSERT(same_as{}, borrowed{}, nullptr_to, plus{})), + ranges::binary_transform_result>); + +struct instantiator { + static constexpr P input1[3] = {{1, 99}, {4, 98}, {5, 97}}; + static constexpr P input2[3] = {{99, 6}, {98, 7}, {97, 8}}; + static constexpr int expected[3] = {7, 11, 13}; + + static constexpr P shortInput1[2] = {{1, 99}, {4, 98}}; + static constexpr P shortInput2[2] = {{99, 6}, {98, 7}}; + static constexpr int shortExpected[2] = {7, 11}; + + template + static constexpr void call() { + using ranges::transform, ranges::binary_transform_result, ranges::iterator_t; + { // Validate iterator + sentinel overload, first range shorter + int output[2] = {-1, -1}; + Read1 wrapped_in1{shortInput1}; + Read2 wrapped_in2{input2}; + + auto result = transform(wrapped_in1.begin(), wrapped_in1.end(), wrapped_in2.begin(), wrapped_in2.end(), + Write{output}, plus{}, get_first, get_second); + STATIC_ASSERT( + same_as, iterator_t, Write>>); + assert(result.in1 == wrapped_in1.end()); + assert(next(result.in2) == wrapped_in2.end()); + assert(result.out.peek() == output + 2); + assert(ranges::equal(output, shortExpected)); + } + { // Validate iterator + sentinel overload, second range shorter + int output[2] = {-1, -1}; + Read1 wrapped_in1{input1}; + Read2 wrapped_in2{shortInput2}; + + auto result = transform(wrapped_in1.begin(), wrapped_in1.end(), wrapped_in2.begin(), wrapped_in2.end(), + Write{output}, plus{}, get_first, get_second); + STATIC_ASSERT( + same_as, iterator_t, Write>>); + assert(next(result.in1) == wrapped_in1.end()); + assert(result.in2 == wrapped_in2.end()); + assert(result.out.peek() == output + 2); + assert(ranges::equal(output, shortExpected)); + } + { // Validate range overload, first range shorter + int output[2] = {-1, -1}; + Read1 wrapped_in1{shortInput1}; + Read2 wrapped_in2{input2}; + + auto result = transform(wrapped_in1, wrapped_in2, Write{output}, plus{}, get_first, get_second); + STATIC_ASSERT( + same_as, iterator_t, Write>>); + assert(result.in1 == wrapped_in1.end()); + assert(next(result.in2) == wrapped_in2.end()); + assert(result.out.peek() == output + 2); + assert(ranges::equal(output, shortExpected)); + } + { // Validate range overload, second range shorter + int output[2] = {-1, -1}; + Read1 wrapped_in1{input1}; + Read2 wrapped_in2{shortInput2}; + + auto result = transform(wrapped_in1, wrapped_in2, Write{output}, plus{}, get_first, get_second); + STATIC_ASSERT( + same_as, iterator_t, Write>>); + assert(next(result.in1) == wrapped_in1.end()); + assert(result.in2 == wrapped_in2.end()); + assert(result.out.peek() == output + 2); + assert(ranges::equal(output, shortExpected)); + } + } +}; + +using Elem1 = const P; +using Elem2 = const P; +using Elem3 = int; + +#ifdef TEST_EVERYTHING +int main() { + // No constexpr test here; the test_in_in_write call exceeds the maximum number of steps in a constexpr computation. + test_in_in_write(); +} +#else // ^^^ test all range combinations // test only interesting range combos vvv +template +using in_test_range = test::range; +template +using out_test_iterator = + test::iterator; + +constexpr bool run_tests() { + // All (except contiguous) proxy reference types, since the algorithm doesn't really care. + using test::Common, test::Sized; + + // both input, non-common, and sized or unsized + instantiator::call, in_test_range, out_test_iterator>(); + instantiator::call, in_test_range, out_test_iterator>(); + return true; +} + +int main() { + STATIC_ASSERT(run_tests()); + run_tests(); +} +#endif // TEST_EVERYTHING diff --git a/tests/std/tests/P0896R4_ranges_alg_transform_unary/env.lst b/tests/std/tests/P0896R4_ranges_alg_transform_unary/env.lst new file mode 100644 index 00000000000..f3ccc8613c6 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_transform_unary/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_transform_unary/test.cpp b/tests/std/tests/P0896R4_ranges_alg_transform_unary/test.cpp new file mode 100644 index 00000000000..3d65ceb8996 --- /dev/null +++ b/tests/std/tests/P0896R4_ranges_alg_transform_unary/test.cpp @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include + +#include + +using namespace std; +using P = pair; + +// Validate that unary_transform_result aliases in_out_result +STATIC_ASSERT(same_as, ranges::in_out_result>); + +constexpr auto minus_one = [](const int x) { return x - 1; }; + +// Validate dangling story +STATIC_ASSERT(same_as{}, nullptr_to, minus_one)), + ranges::unary_transform_result>); +STATIC_ASSERT(same_as{}, nullptr_to, minus_one)), + ranges::unary_transform_result>); + +struct instantiator { + static constexpr P input[3] = {{1, 99}, {4, 98}, {5, 97}}; + static constexpr int expected[3] = {0, 3, 4}; + + template + static constexpr void call() { + using ranges::transform, ranges::unary_transform_result, ranges::iterator_t; + { // Validate iterator + sentinel overload + int output[3] = {-1, -1, -1}; + Read wrapped_in{input}; + + auto result = transform(wrapped_in.begin(), wrapped_in.end(), Write{output}, minus_one, get_first); + STATIC_ASSERT(same_as, Write>>); + assert(result.in == wrapped_in.end()); + assert(result.out.peek() == output + 3); + assert(ranges::equal(output, expected)); + } + { // Validate range overload + int output[3] = {-1, -1, -1}; + Read wrapped_in{input}; + + auto result = transform(wrapped_in, Write{output}, minus_one, get_first); + STATIC_ASSERT(same_as, Write>>); + assert(result.in == wrapped_in.end()); + assert(result.out.peek() == output + 3); + assert(ranges::equal(output, expected)); + } + } +}; + +int main() { + STATIC_ASSERT((test_in_write(), true)); + test_in_write(); +}