Skip to content

Commit 46c28fc

Browse files
committed
Implement ranges::transform
1 parent d531112 commit 46c28fc

File tree

7 files changed

+343
-0
lines changed

7 files changed

+343
-0
lines changed

stl/inc/algorithm

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2990,6 +2990,120 @@ _FwdIt3 transform(_ExPo&& _Exec, const _FwdIt1 _First1, const _FwdIt1 _Last1, co
29902990
_Fn _Func) noexcept; // terminates
29912991
#endif // _HAS_CXX17
29922992

2993+
#ifdef __cpp_lib_concepts
2994+
namespace ranges {
2995+
2996+
// ALIAS TEMPLATE unary_transform_result
2997+
template <class _In, class _Out>
2998+
using unary_transform_result = in_out_result<_In, _Out>;
2999+
3000+
// ALIAS TEMPLATE binary_transform_result
3001+
template <class _In1, class _In2, class _Out>
3002+
using binary_transform_result = in_in_out_result<_In1, _In2, _Out>;
3003+
3004+
// VARIABLE ranges::transform
3005+
class _Transform_fn : private _Not_quite_object {
3006+
public:
3007+
using _Not_quite_object::_Not_quite_object;
3008+
3009+
// clang-format off
3010+
template <input_iterator _It, sentinel_for<_It> _Se, weakly_incrementable _Out, copy_constructible _Fn,
3011+
class _Pj = identity>
3012+
requires indirectly_writable<_Out, indirect_result_t<_Fn&, projected<_It, _Pj>>>
3013+
constexpr unary_transform_result<_It, _Out> operator()(
3014+
_It _First, _Se _Last, _Out _Result, _Fn _Func, _Pj _Proj = {}) const {
3015+
_Adl_verify_range(_First, _Last);
3016+
auto _UResult = _Transform_unchecked(_Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last)),
3017+
_STD move(_Result), _Pass_fn(_Func), _Pass_fn(_Proj));
3018+
3019+
_Seek_wrapped(_First, _STD move(_UResult.in));
3020+
return {_STD move(_First), _STD move(_UResult.out)};
3021+
}
3022+
3023+
template <input_range _Rng, weakly_incrementable _Out, copy_constructible _Fn, class _Pj = identity>
3024+
requires indirectly_writable<_Out, indirect_result_t<_Fn&, projected<iterator_t<_Rng>, _Pj>>>
3025+
constexpr unary_transform_result<borrowed_iterator_t<_Rng>, _Out> operator()(
3026+
_Rng&& _Range, _Out _Result, _Fn _Func, _Pj _Proj = {}) const {
3027+
auto _First = _RANGES begin(_Range);
3028+
auto _UResult = _Transform_unchecked(
3029+
_Get_unwrapped(_STD move(_First)), _Uend(_Range), _STD move(_Result), _Pass_fn(_Func), _Pass_fn(_Proj));
3030+
3031+
_Seek_wrapped(_First, _STD move(_UResult.in));
3032+
return {_STD move(_First), _STD move(_UResult.out)};
3033+
}
3034+
3035+
template <input_iterator _It1, sentinel_for<_It1> _Se1, input_iterator _It2, sentinel_for<_It2> _Se2,
3036+
weakly_incrementable _Out, copy_constructible _Fn, class _Pj1 = identity, class _Pj2 = identity>
3037+
requires indirectly_writable<_Out, indirect_result_t<_Fn&, projected<_It1, _Pj1>, projected<_It2, _Pj2>>>
3038+
constexpr binary_transform_result<_It1, _It2, _Out> operator()(_It1 _First1, _Se1 _Last1, _It2 _First2,
3039+
_Se2 _Last2, _Out _Result, _Fn _Func, _Pj1 _Proj1 = {}, _Pj2 _Proj2 = {}) const {
3040+
_Adl_verify_range(_First1, _Last1);
3041+
_Adl_verify_range(_First2, _Last2);
3042+
auto _UResult = _Transform_unchecked(_Get_unwrapped(_STD move(_First1)), _Get_unwrapped(_STD move(_Last1)),
3043+
_Get_unwrapped(_STD move(_First2)), _Get_unwrapped(_STD move(_Last2)), _STD move(_Result),
3044+
_Pass_fn(_Func), _Pass_fn(_Proj1), _Pass_fn(_Proj2));
3045+
3046+
_Seek_wrapped(_First1, _STD move(_UResult.in1));
3047+
_Seek_wrapped(_First2, _STD move(_UResult.in2));
3048+
return {_STD move(_First1), _STD move(_First2), _STD move(_UResult.out)};
3049+
}
3050+
3051+
template <input_range _Rng1, input_range _Rng2, weakly_incrementable _Out, copy_constructible _Fn,
3052+
class _Pj1 = identity, class _Pj2 = identity>
3053+
requires indirectly_writable<_Out, indirect_result_t<_Fn&, projected<iterator_t<_Rng1>, _Pj1>,
3054+
projected<iterator_t<_Rng2>, _Pj2>>>
3055+
constexpr binary_transform_result<borrowed_iterator_t<_Rng1>, borrowed_iterator_t<_Rng2>, _Out> operator()(
3056+
_Rng1&& _Range1, _Rng2&& _Range2, _Out _Result, _Fn _Func, _Pj1 _Proj1 = {}, _Pj2 _Proj2 = {}) const {
3057+
auto _First1 = _RANGES begin(_Range1);
3058+
auto _First2 = _RANGES begin(_Range2);
3059+
auto _UResult = _Transform_unchecked(_Get_unwrapped(_STD move(_First1)), _Uend(_Range1),
3060+
_Get_unwrapped(_STD move(_First2)), _Uend(_Range2), _STD move(_Result), _Pass_fn(_Func),
3061+
_Pass_fn(_Proj1), _Pass_fn(_Proj2));
3062+
3063+
_Seek_wrapped(_First1, _STD move(_UResult.in1));
3064+
_Seek_wrapped(_First2, _STD move(_UResult.in2));
3065+
return {_STD move(_First1), _STD move(_First2), _STD move(_UResult.out)};
3066+
}
3067+
// clang-format on
3068+
3069+
private:
3070+
template <class _It, class _Se, class _Out, class _Fn, class _Pj>
3071+
_NODISCARD static constexpr unary_transform_result<_It, _Out> _Transform_unchecked(
3072+
_It _First, const _Se _Last, _Out _Result, _Fn _Func, _Pj _Proj) {
3073+
// transform projected [_First, _Last) with _Func
3074+
_STL_INTERNAL_STATIC_ASSERT(input_iterator<_It>);
3075+
_STL_INTERNAL_STATIC_ASSERT(weakly_incrementable<_Out>);
3076+
_STL_INTERNAL_STATIC_ASSERT(indirectly_writable<_Out, indirect_result_t<_Fn&, projected<_It, _Pj>>>);
3077+
3078+
for (; _First != _Last; ++_First, (void) ++_Result) {
3079+
*_Result = _STD invoke(_Func, _STD invoke(_Proj, *_First));
3080+
}
3081+
3082+
return {_STD move(_First), _STD move(_Result)};
3083+
}
3084+
3085+
template <class _It1, class _Se1, class _It2, class _Se2, class _Out, class _Fn, class _Pj1, class _Pj2>
3086+
_NODISCARD static constexpr binary_transform_result<_It1, _It2, _Out> _Transform_unchecked(_It1 _First1,
3087+
const _Se1 _Last1, _It2 _First2, const _Se2 _Last2, _Out _Result, _Fn _Func, _Pj1 _Proj1, _Pj2 _Proj2) {
3088+
// transform projected [_First1, _Last1) and projected [_First2, Last2) with _Func
3089+
_STL_INTERNAL_STATIC_ASSERT(input_iterator<_It1>);
3090+
_STL_INTERNAL_STATIC_ASSERT(input_iterator<_It2>);
3091+
_STL_INTERNAL_STATIC_ASSERT(weakly_incrementable<_Out>);
3092+
_STL_INTERNAL_STATIC_ASSERT(
3093+
indirectly_writable<_Out, indirect_result_t<_Fn&, projected<_It1, _Pj1>, projected<_It2, _Pj2>>>);
3094+
3095+
for (; _First1 != _Last1 && _First2 != _Last2; ++_First1, (void) ++_First2, ++_Result) {
3096+
*_Result = _STD invoke(_Func, _STD invoke(_Proj1, *_First1), _STD invoke(_Proj2, *_First2));
3097+
}
3098+
3099+
return {_STD move(_First1), _STD move(_First2), _STD move(_Result)};
3100+
}
3101+
};
3102+
3103+
inline constexpr _Transform_fn transform{_Not_quite_object::_Construct_tag{}};
3104+
} // namespace ranges
3105+
#endif // __cpp_lib_concepts
3106+
29933107
// FUNCTION TEMPLATE replace
29943108
template <class _FwdIt, class _Ty>
29953109
_CONSTEXPR20 void replace(const _FwdIt _First, const _FwdIt _Last, const _Ty& _Oldval, const _Ty& _Newval) {

tests/std/include/range_algorithm_support.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -966,6 +966,11 @@ constexpr void test_read_write() {
966966
with_input_iterators<with_writable_iterators<Instantiator, Element2>, Element1>::call();
967967
}
968968

969+
template <class Instantiator, class Element1, class Element2, class Element3>
970+
constexpr void test_in_in_write() {
971+
with_input_ranges<with_input_ranges<with_writable_iterators<Instantiator, Element3>, Element2>, Element1>::call();
972+
}
973+
969974
template <size_t I>
970975
struct get_nth_fn {
971976
template <class T>

tests/std/test.lst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,8 @@ tests\P0896R4_ranges_alg_replace_if
265265
tests\P0896R4_ranges_alg_search
266266
tests\P0896R4_ranges_alg_search_n
267267
tests\P0896R4_ranges_alg_swap_ranges
268+
tests\P0896R4_ranges_alg_transform_binary
269+
tests\P0896R4_ranges_alg_transform_unary
268270
tests\P0896R4_ranges_iterator_machinery
269271
tests\P0896R4_ranges_range_machinery
270272
tests\P0896R4_ranges_subrange
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3+
4+
RUNALL_INCLUDE ..\concepts_matrix.lst
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3+
4+
#include <algorithm>
5+
#include <cassert>
6+
#include <concepts>
7+
#include <ranges>
8+
#include <utility>
9+
10+
#include <range_algorithm_support.hpp>
11+
12+
using namespace std;
13+
using P = pair<int, int>;
14+
15+
// Validate that binary_transform_result aliases in_in_out_result
16+
STATIC_ASSERT(same_as<ranges::binary_transform_result<int, int, double>, ranges::in_in_out_result<int, int, double>>);
17+
18+
// Validate dangling story
19+
STATIC_ASSERT(
20+
same_as<decltype(ranges::transform(borrowed<false>{}, borrowed<false>{}, static_cast<int*>(nullptr), plus{})),
21+
ranges::binary_transform_result<ranges::dangling, ranges::dangling, int*>>);
22+
STATIC_ASSERT(
23+
same_as<decltype(ranges::transform(borrowed<false>{}, borrowed<true>{}, static_cast<int*>(nullptr), plus{})),
24+
ranges::binary_transform_result<ranges::dangling, int*, int*>>);
25+
STATIC_ASSERT(
26+
same_as<decltype(ranges::transform(borrowed<true>{}, borrowed<false>{}, static_cast<int*>(nullptr), plus{})),
27+
ranges::binary_transform_result<int*, ranges::dangling, int*>>);
28+
STATIC_ASSERT(
29+
same_as<decltype(ranges::transform(borrowed<true>{}, borrowed<true>{}, static_cast<int*>(nullptr), plus{})),
30+
ranges::binary_transform_result<int*, int*, int*>>);
31+
32+
struct instantiator {
33+
static constexpr P input1[3] = {{1, 99}, {4, 98}, {5, 97}};
34+
static constexpr P input2[3] = {{99, 6}, {98, 7}, {97, 8}};
35+
static constexpr int expected[3] = {7, 11, 13};
36+
37+
static constexpr P shortInput1[2] = {{1, 99}, {4, 98}};
38+
static constexpr P shortInput2[2] = {{99, 6}, {98, 7}};
39+
static constexpr int shortExpected[2] = {7, 11};
40+
41+
template <ranges::input_range Read1, ranges::input_range Read2, weakly_incrementable Write>
42+
static constexpr void call() {
43+
using ranges::transform, ranges::binary_transform_result, ranges::iterator_t;
44+
{ // Validate iterator + sentinel overload
45+
int output[3] = {-1, -1, -1};
46+
Read1 wrapped_in1{input1};
47+
Read2 wrapped_in2{input2};
48+
49+
auto result = transform(wrapped_in1.begin(), wrapped_in1.end(), wrapped_in2.begin(), wrapped_in2.end(),
50+
Write{output}, plus{}, get_first, get_second);
51+
STATIC_ASSERT(
52+
same_as<decltype(result), binary_transform_result<iterator_t<Read1>, iterator_t<Read2>, Write>>);
53+
assert(result.in1 == wrapped_in1.end());
54+
assert(result.in2 == wrapped_in2.end());
55+
assert(result.out.peek() == output + 3);
56+
assert(ranges::equal(output, expected));
57+
}
58+
{ // Validate range overload
59+
int output[3] = {-1, -1, -1};
60+
Read1 wrapped_in1{input1};
61+
Read2 wrapped_in2{input2};
62+
63+
auto result = transform(wrapped_in1, wrapped_in2, Write{output}, plus{}, get_first, get_second);
64+
STATIC_ASSERT(
65+
same_as<decltype(result), binary_transform_result<iterator_t<Read1>, iterator_t<Read2>, Write>>);
66+
assert(result.in1 == wrapped_in1.end());
67+
assert(result.in2 == wrapped_in2.end());
68+
assert(result.out.peek() == output + 3);
69+
assert(ranges::equal(output, expected));
70+
}
71+
{ // Validate range overload first range shorter
72+
int output[2] = {-1, -1};
73+
Read1 wrapped_in1{shortInput1};
74+
Read2 wrapped_in2{input2};
75+
76+
auto result = transform(wrapped_in1, wrapped_in2, Write{output}, plus{}, get_first, get_second);
77+
STATIC_ASSERT(
78+
same_as<decltype(result), binary_transform_result<iterator_t<Read1>, iterator_t<Read2>, Write>>);
79+
assert(result.in1 == wrapped_in1.end());
80+
assert(next(result.in2) == wrapped_in2.end());
81+
assert(result.out.peek() == output + 2);
82+
assert(ranges::equal(output, shortExpected));
83+
}
84+
{ // Validate range overload second range shorter
85+
int output[2] = {-1, -1};
86+
Read1 wrapped_in1{input1};
87+
Read2 wrapped_in2{shortInput2};
88+
89+
auto result = transform(wrapped_in1, wrapped_in2, Write{output}, plus{}, get_first, get_second);
90+
STATIC_ASSERT(
91+
same_as<decltype(result), binary_transform_result<iterator_t<Read1>, iterator_t<Read2>, Write>>);
92+
assert(next(result.in1) == wrapped_in1.end());
93+
assert(result.in2 == wrapped_in2.end());
94+
assert(result.out.peek() == output + 2);
95+
assert(ranges::equal(output, shortExpected));
96+
}
97+
}
98+
};
99+
100+
using Elem1 = const P;
101+
using Elem2 = const P;
102+
using Elem3 = int;
103+
104+
#ifdef TEST_EVERYTHING
105+
int main() {
106+
// No constexpr test here; the test_in_in_write call exceeds the maximum number of steps in a constexpr computation.
107+
test_in_in_write<instantiator, Elem1, Elem2, Elem3>();
108+
}
109+
#else // ^^^ test all range combinations // test only interesting range combos vvv
110+
template <class Elem, test::Sized IsSized>
111+
using fwd_test_range = test::range<forward_iterator_tag, Elem, IsSized, test::CanDifference::no, test::Common::no,
112+
test::CanCompare::yes, test::ProxyRef::yes>;
113+
template <class Elem, test::Sized IsSized, test::Common IsCommon>
114+
using random_test_range = test::range<random_access_iterator_tag, Elem, IsSized, test::CanDifference::no, IsCommon,
115+
test::CanCompare::yes, test::ProxyRef::no>;
116+
template <class Elem>
117+
using out_test_iterator =
118+
test::iterator<output_iterator_tag, Elem, test::CanDifference::no, test::CanCompare::yes, test::ProxyRef::yes>;
119+
120+
constexpr bool run_tests() {
121+
// All (except contiguous) proxy reference types, since the algorithm doesn't really care.
122+
using test::Common, test::Sized;
123+
124+
// both forward, non-common, and sized or unsized
125+
instantiator::call<fwd_test_range<Elem1, Sized::no>, fwd_test_range<Elem2, Sized::no>, out_test_iterator<Elem3>>();
126+
instantiator::call<fwd_test_range<Elem1, Sized::yes>, fwd_test_range<Elem2, Sized::yes>,
127+
out_test_iterator<Elem3>>();
128+
129+
// both random-access, and sized or unsized; all permutations of common
130+
instantiator::call<random_test_range<Elem1, Sized::no, Common::no>, random_test_range<Elem2, Sized::no, Common::no>,
131+
out_test_iterator<Elem3>>();
132+
instantiator::call<random_test_range<Elem1, Sized::no, Common::no>,
133+
random_test_range<Elem2, Sized::no, Common::yes>, out_test_iterator<Elem3>>();
134+
instantiator::call<random_test_range<Elem1, Sized::no, Common::yes>,
135+
random_test_range<Elem2, Sized::no, Common::no>, out_test_iterator<Elem3>>();
136+
instantiator::call<random_test_range<Elem1, Sized::no, Common::yes>,
137+
random_test_range<Elem2, Sized::no, Common::yes>, out_test_iterator<Elem3>>();
138+
instantiator::call<random_test_range<Elem1, Sized::yes, Common::no>,
139+
random_test_range<Elem2, Sized::yes, Common::no>, out_test_iterator<Elem3>>();
140+
instantiator::call<random_test_range<Elem1, Sized::yes, Common::no>,
141+
random_test_range<Elem2, Sized::yes, Common::yes>, out_test_iterator<Elem3>>();
142+
instantiator::call<random_test_range<Elem1, Sized::yes, Common::yes>,
143+
random_test_range<Elem2, Sized::yes, Common::no>, out_test_iterator<Elem3>>();
144+
instantiator::call<random_test_range<Elem1, Sized::yes, Common::yes>,
145+
random_test_range<Elem2, Sized::yes, Common::yes>, out_test_iterator<Elem3>>();
146+
147+
return true;
148+
}
149+
150+
int main() {
151+
STATIC_ASSERT(run_tests());
152+
run_tests();
153+
}
154+
#endif // TEST_EVERYTHING
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3+
4+
RUNALL_INCLUDE ..\concepts_matrix.lst
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3+
4+
#include <algorithm>
5+
#include <array>
6+
#include <cassert>
7+
#include <concepts>
8+
#include <ranges>
9+
#include <utility>
10+
11+
#include <range_algorithm_support.hpp>
12+
13+
using namespace std;
14+
using P = pair<int, int>;
15+
16+
// Validate that unary_transform_result aliases in_out_result
17+
STATIC_ASSERT(same_as<ranges::unary_transform_result<int, double>, ranges::in_out_result<int, double>>);
18+
19+
constexpr auto minus_one = [](int x) -> int { return x - 1; };
20+
21+
// Validate dangling story
22+
STATIC_ASSERT(same_as<decltype(ranges::transform(borrowed<false>{}, static_cast<int*>(nullptr), minus_one)),
23+
ranges::unary_transform_result<ranges::dangling, int*>>);
24+
STATIC_ASSERT(same_as<decltype(ranges::transform(borrowed<true>{}, static_cast<int*>(nullptr), minus_one)),
25+
ranges::unary_transform_result<int*, int*>>);
26+
27+
struct instantiator {
28+
static constexpr P input[3] = {{1, 99}, {4, 98}, {5, 97}};
29+
static constexpr int expected[3] = {0, 3, 4};
30+
31+
template <ranges::input_range Read, weakly_incrementable Write>
32+
static constexpr void call() {
33+
using ranges::transform, ranges::unary_transform_result, ranges::iterator_t;
34+
{ // Validate iterator + sentinel overload
35+
int output[3] = {-1, -1, -1};
36+
Read wrapped_in{input};
37+
38+
auto result = transform(wrapped_in.begin(), wrapped_in.end(), Write{output}, minus_one, get_first);
39+
STATIC_ASSERT(same_as<decltype(result), unary_transform_result<iterator_t<Read>, Write>>);
40+
assert(result.in == wrapped_in.end());
41+
assert(result.out.peek() == output + 3);
42+
assert(ranges::equal(output, expected));
43+
}
44+
{ // Validate range overload
45+
int output[3] = {-1, -1, -1};
46+
Read wrapped_in{input};
47+
48+
auto result = transform(wrapped_in, Write{output}, minus_one, get_first);
49+
STATIC_ASSERT(same_as<decltype(result), unary_transform_result<iterator_t<Read>, Write>>);
50+
assert(result.in == wrapped_in.end());
51+
assert(result.out.peek() == output + 3);
52+
assert(ranges::equal(output, expected));
53+
}
54+
}
55+
};
56+
57+
int main() {
58+
STATIC_ASSERT((test_in_write<instantiator, P const, int>(), true));
59+
test_in_write<instantiator, P const, int>();
60+
}

0 commit comments

Comments
 (0)