Skip to content

Commit c108653

Browse files
author
Xiaoyang Liu
authored
[libc++][ranges] P2387R3: Pipe support for user-defined range adaptors (#89148)
This patch finalizes the std::ranges::range_adaptor_closure class template from https://wg21.link/P2387R3. // [range.adaptor.object], range adaptor objects template<class D> requires is_class_v<D> && same_as<D, remove_cv_t<D>> class range_adaptor_closure { }; The current implementation of __range_adaptor_closure was introduced in ee44dd8 and has served as the foundation for the range adaptors in libc++ for a while. This patch keeps its implementation, with the exception of the following changes: - __range_adaptor_closure now includes the missing constraints `is_class_v<D> && same_as<D, remove_cv_t<D>>` to restrict the type of class that can inherit from it. (https://eel.is/c++draft/ranges.syn) - The operator| of __range_adaptor_closure no longer requires its first argument to model viewable_range. (https://eel.is/c++draft/range.adaptor.object#1) - The _RangeAdaptorClosure concept is refined to exclude cases where T models range or where T has base classes of type range_adaptor_closure<U> for another type U. (https://eel.is/c++draft/range.adaptor.object#2)
1 parent adb0126 commit c108653

File tree

7 files changed

+186
-20
lines changed

7 files changed

+186
-20
lines changed

libcxx/docs/ReleaseNotes/19.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ Implemented Papers
4949
- P2302R4 - ``std::ranges::contains``
5050
- P1659R3 - ``std::ranges::starts_with`` and ``std::ranges::ends_with``
5151
- P3029R1 - Better ``mdspan``'s CTAD
52+
- P2387R3 - Pipe support for user-defined range adaptors
5253

5354
Improvements and New Features
5455
-----------------------------

libcxx/docs/Status/Cxx23.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ Paper Status
4343
.. [#note-P0533R9] P0533R9: ``isfinite``, ``isinf``, ``isnan`` and ``isnormal`` are implemented.
4444
.. [#note-P1413R3] P1413R3: ``std::aligned_storage_t`` and ``std::aligned_union_t`` are marked deprecated, but
4545
clang doesn't issue a diagnostic for deprecated using template declarations.
46-
.. [#note-P2387R3] P2387R3: ``bind_back`` only
4746
.. [#note-P2520R0] P2520R0: Libc++ implemented this paper as a DR in C++20 as well.
4847
.. [#note-P2711R1] P2711R1: ``join_with_view`` hasn't been done yet since this type isn't implemented yet.
4948
.. [#note-P2770R0] P2770R0: ``join_with_view`` hasn't been done yet since this type isn't implemented yet.

libcxx/docs/Status/Cxx23Papers.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"`P1413R3 <https://wg21.link/P1413R3>`__","LWG","Deprecate ``std::aligned_storage`` and ``std::aligned_union``","February 2022","|Complete| [#note-P1413R3]_",""
4646
"`P2255R2 <https://wg21.link/P2255R2>`__","LWG","A type trait to detect reference binding to temporary","February 2022","",""
4747
"`P2273R3 <https://wg21.link/P2273R3>`__","LWG","Making ``std::unique_ptr`` constexpr","February 2022","|Complete|","16.0"
48-
"`P2387R3 <https://wg21.link/P2387R3>`__","LWG","Pipe support for user-defined range adaptors","February 2022","|Partial| [#note-P2387R3]_","","|ranges|"
48+
"`P2387R3 <https://wg21.link/P2387R3>`__","LWG","Pipe support for user-defined range adaptors","February 2022","|Complete|","19.0","|ranges|"
4949
"`P2440R1 <https://wg21.link/P2440R1>`__","LWG","``ranges::iota``, ``ranges::shift_left`` and ``ranges::shift_right``","February 2022","","","|ranges|"
5050
"`P2441R2 <https://wg21.link/P2441R2>`__","LWG","``views::join_with``","February 2022","|In Progress|","","|ranges|"
5151
"`P2442R1 <https://wg21.link/P2442R1>`__","LWG","Windowing range adaptors: ``views::chunk`` and ``views::slide``","February 2022","","","|ranges|"
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Standard,Name,Assignee,CL,Status
22
C++23,`ranges::to <https://wg21.link/P1206R7>`_,Konstantin Varlamov,`D142335 <https://reviews.llvm.org/D142335>`_,Complete
3-
C++23,`Pipe support for user-defined range adaptors <https://wg21.link/P2387R3>`_,Unassigned,No patch yet,Not started
3+
C++23,`Pipe support for user-defined range adaptors <https://wg21.link/P2387R3>`_,"Louis Dionne, Jakub Mazurkiewicz, and Xiaoyang Liu",Various,Complete
44
C++23,`Formatting Ranges <https://wg21.link/P2286R8>`_,Mark de Wever,Various,Complete
55
C++20,`Stashing stashing iterators for proper flattening <https://wg21.link/P2770R0>`_,Jakub Mazurkiewicz,Various,In progress

libcxx/include/__ranges/range_adaptor.h

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <__functional/invoke.h>
2020
#include <__ranges/concepts.h>
2121
#include <__type_traits/decay.h>
22+
#include <__type_traits/is_class.h>
2223
#include <__type_traits/is_nothrow_constructible.h>
2324
#include <__type_traits/remove_cvref.h>
2425
#include <__utility/forward.h>
@@ -35,12 +36,15 @@ _LIBCPP_BEGIN_NAMESPACE_STD
3536

3637
#if _LIBCPP_STD_VER >= 20
3738

39+
namespace ranges {
40+
3841
// CRTP base that one can derive from in order to be considered a range adaptor closure
3942
// by the library. When deriving from this class, a pipe operator will be provided to
4043
// make the following hold:
4144
// - `x | f` is equivalent to `f(x)`
4245
// - `f1 | f2` is an adaptor closure `g` such that `g(x)` is equivalent to `f2(f1(x))`
4346
template <class _Tp>
47+
requires is_class_v<_Tp> && same_as<_Tp, remove_cv_t<_Tp>>
4448
struct __range_adaptor_closure;
4549

4650
// Type that wraps an arbitrary function object and makes it into a range adaptor closure,
@@ -52,27 +56,42 @@ struct __range_adaptor_closure_t : _Fn, __range_adaptor_closure<__range_adaptor_
5256
_LIBCPP_CTAD_SUPPORTED_FOR_TYPE(__range_adaptor_closure_t);
5357

5458
template <class _Tp>
55-
concept _RangeAdaptorClosure = derived_from<remove_cvref_t<_Tp>, __range_adaptor_closure<remove_cvref_t<_Tp>>>;
59+
_Tp __derived_from_range_adaptor_closure(__range_adaptor_closure<_Tp>*);
5660

5761
template <class _Tp>
58-
struct __range_adaptor_closure {
59-
template <ranges::viewable_range _View, _RangeAdaptorClosure _Closure>
60-
requires same_as<_Tp, remove_cvref_t<_Closure>> && invocable<_Closure, _View>
61-
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr decltype(auto)
62-
operator|(_View&& __view, _Closure&& __closure) noexcept(is_nothrow_invocable_v<_Closure, _View>) {
63-
return std::invoke(std::forward<_Closure>(__closure), std::forward<_View>(__view));
64-
}
65-
66-
template <_RangeAdaptorClosure _Closure, _RangeAdaptorClosure _OtherClosure>
67-
requires same_as<_Tp, remove_cvref_t<_Closure>> && constructible_from<decay_t<_Closure>, _Closure> &&
68-
constructible_from<decay_t<_OtherClosure>, _OtherClosure>
69-
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator|(_Closure&& __c1, _OtherClosure&& __c2) noexcept(
70-
is_nothrow_constructible_v<decay_t<_Closure>, _Closure> &&
71-
is_nothrow_constructible_v<decay_t<_OtherClosure>, _OtherClosure>) {
72-
return __range_adaptor_closure_t(std::__compose(std::forward<_OtherClosure>(__c2), std::forward<_Closure>(__c1)));
73-
}
62+
concept _RangeAdaptorClosure = !ranges::range<remove_cvref_t<_Tp>> && requires {
63+
// Ensure that `remove_cvref_t<_Tp>` is derived from `__range_adaptor_closure<remove_cvref_t<_Tp>>` and isn't derived
64+
// from `__range_adaptor_closure<U>` for any other type `U`.
65+
{ ranges::__derived_from_range_adaptor_closure((remove_cvref_t<_Tp>*)nullptr) } -> same_as<remove_cvref_t<_Tp>>;
7466
};
7567

68+
template <ranges::range _Range, _RangeAdaptorClosure _Closure>
69+
requires invocable<_Closure, _Range>
70+
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr decltype(auto)
71+
operator|(_Range&& __range, _Closure&& __closure) noexcept(is_nothrow_invocable_v<_Closure, _Range>) {
72+
return std::invoke(std::forward<_Closure>(__closure), std::forward<_Range>(__range));
73+
}
74+
75+
template <_RangeAdaptorClosure _Closure, _RangeAdaptorClosure _OtherClosure>
76+
requires constructible_from<decay_t<_Closure>, _Closure> && constructible_from<decay_t<_OtherClosure>, _OtherClosure>
77+
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator|(_Closure&& __c1, _OtherClosure&& __c2) noexcept(
78+
is_nothrow_constructible_v<decay_t<_Closure>, _Closure> &&
79+
is_nothrow_constructible_v<decay_t<_OtherClosure>, _OtherClosure>) {
80+
return __range_adaptor_closure_t(std::__compose(std::forward<_OtherClosure>(__c2), std::forward<_Closure>(__c1)));
81+
}
82+
83+
template <class _Tp>
84+
requires is_class_v<_Tp> && same_as<_Tp, remove_cv_t<_Tp>>
85+
struct __range_adaptor_closure {};
86+
87+
# if _LIBCPP_STD_VER >= 23
88+
template <class _Tp>
89+
requires is_class_v<_Tp> && same_as<_Tp, remove_cv_t<_Tp>>
90+
class range_adaptor_closure : public __range_adaptor_closure<_Tp> {};
91+
# endif // _LIBCPP_STD_VER >= 23
92+
93+
} // namespace ranges
94+
7695
#endif // _LIBCPP_STD_VER >= 20
7796

7897
_LIBCPP_END_NAMESPACE_STD

libcxx/include/ranges

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ namespace std::ranges {
9393
template<class T>
9494
concept viewable_range = see below;
9595
96+
// [range.adaptor.object], range adaptor objects
97+
template<class D>
98+
requires is_class_v<D> && same_as<D, remove_cv_t<D>>
99+
class range_adaptor_closure { }; // Since c++23
100+
96101
// [view.interface], class template view_interface
97102
template<class D>
98103
requires is_class_v<D> && same_as<D, remove_cv_t<D>>
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
10+
11+
// std::ranges::range_adaptor_closure;
12+
13+
#include <ranges>
14+
15+
#include <algorithm>
16+
#include <vector>
17+
18+
#include "test_range.h"
19+
20+
template <class T>
21+
concept CanDeriveFromRangeAdaptorClosure = requires { typename std::ranges::range_adaptor_closure<T>; };
22+
static_assert(!CanDeriveFromRangeAdaptorClosure<int>);
23+
24+
struct Foo {};
25+
static_assert(CanDeriveFromRangeAdaptorClosure<Foo>);
26+
static_assert(!CanDeriveFromRangeAdaptorClosure<Foo&>);
27+
static_assert(!CanDeriveFromRangeAdaptorClosure<const Foo>);
28+
static_assert(!CanDeriveFromRangeAdaptorClosure<volatile Foo>);
29+
static_assert(!CanDeriveFromRangeAdaptorClosure<const volatile Foo&&>);
30+
31+
struct incomplete_t;
32+
static_assert(CanDeriveFromRangeAdaptorClosure<incomplete_t>);
33+
34+
using range_t = std::vector<int>;
35+
36+
template <class T>
37+
concept RangeAdaptorClosure =
38+
CanBePiped<range_t, T&> && CanBePiped<range_t, const T&> && CanBePiped<range_t, T&&> &&
39+
CanBePiped<range_t, const T&&>;
40+
41+
struct callable : std::ranges::range_adaptor_closure<callable> {
42+
static void operator()(const range_t&) {}
43+
};
44+
static_assert(RangeAdaptorClosure<callable>);
45+
46+
// `not_callable_1` doesn't have an `operator()`
47+
struct not_callable_1 : std::ranges::range_adaptor_closure<not_callable_1> {};
48+
static_assert(!RangeAdaptorClosure<not_callable_1>);
49+
50+
// `not_callable_2` doesn't have an `operator()` that accepts a `range` argument
51+
struct not_callable_2 : std::ranges::range_adaptor_closure<not_callable_2> {
52+
static void operator()() {}
53+
};
54+
static_assert(!RangeAdaptorClosure<not_callable_2>);
55+
56+
// `not_derived_from_1` doesn't derive from `std::ranges::range_adaptor_closure`
57+
struct not_derived_from_1 {
58+
static void operator()(const range_t&) {}
59+
};
60+
static_assert(!RangeAdaptorClosure<not_derived_from_1>);
61+
62+
// `not_derived_from_2` doesn't publicly derive from `std::ranges::range_adaptor_closure`
63+
struct not_derived_from_2 : private std::ranges::range_adaptor_closure<not_derived_from_2> {
64+
static void operator()(const range_t&) {}
65+
};
66+
static_assert(!RangeAdaptorClosure<not_derived_from_2>);
67+
68+
// `not_derived_from_3` doesn't derive from the correct specialization of `std::ranges::range_adaptor_closure`
69+
struct not_derived_from_3 : std::ranges::range_adaptor_closure<callable> {
70+
static void operator()(const range_t&) {}
71+
};
72+
static_assert(!RangeAdaptorClosure<not_derived_from_3>);
73+
74+
// `not_derived_from_4` doesn't derive from exactly one specialization of `std::ranges::range_adaptor_closure`
75+
struct not_derived_from_4
76+
: std::ranges::range_adaptor_closure<not_derived_from_4>,
77+
std::ranges::range_adaptor_closure<callable> {
78+
static void operator()(const range_t&) {}
79+
};
80+
static_assert(!RangeAdaptorClosure<not_derived_from_4>);
81+
82+
// `is_range` models `range`
83+
struct is_range : std::ranges::range_adaptor_closure<is_range> {
84+
static void operator()(const range_t&) {}
85+
int* begin() const { return nullptr; }
86+
int* end() const { return nullptr; }
87+
};
88+
static_assert(std::ranges::range<is_range> && std::ranges::range<const is_range>);
89+
static_assert(!RangeAdaptorClosure<is_range>);
90+
91+
// user-defined range adaptor closure object
92+
struct negate_fn : std::ranges::range_adaptor_closure<negate_fn> {
93+
template <std::ranges::range Range>
94+
static constexpr decltype(auto) operator()(Range&& range) {
95+
return std::forward<Range>(range) | std::views::transform([](auto element) { return -element; });
96+
}
97+
};
98+
static_assert(RangeAdaptorClosure<negate_fn>);
99+
constexpr auto negate = negate_fn{};
100+
101+
// user-defined range adaptor closure object
102+
struct plus_1_fn : std::ranges::range_adaptor_closure<plus_1_fn> {
103+
template <std::ranges::range Range>
104+
static constexpr decltype(auto) operator()(Range&& range) {
105+
return std::forward<Range>(range) | std::views::transform([](auto element) { return element + 1; });
106+
}
107+
};
108+
static_assert(RangeAdaptorClosure<plus_1_fn>);
109+
constexpr auto plus_1 = plus_1_fn{};
110+
111+
constexpr bool test() {
112+
const std::vector<int> n{1, 2, 3, 4, 5};
113+
const std::vector<int> n_negate{-1, -2, -3, -4, -5};
114+
115+
assert(std::ranges::equal(n | negate, n_negate));
116+
assert(std::ranges::equal(negate(n), n_negate));
117+
118+
assert(std::ranges::equal(n | negate | negate, n));
119+
assert(std::ranges::equal(n | (negate | negate), n));
120+
assert(std::ranges::equal((n | negate) | negate, n));
121+
assert(std::ranges::equal(negate(n) | negate, n));
122+
assert(std::ranges::equal(negate(n | negate), n));
123+
assert(std::ranges::equal((negate | negate)(n), n));
124+
assert(std::ranges::equal(negate(negate(n)), n));
125+
126+
const std::vector<int> n_plus_1_negate{-2, -3, -4, -5, -6};
127+
assert(std::ranges::equal(n | plus_1 | negate, n_plus_1_negate));
128+
assert(std::ranges::equal(
129+
n | plus_1 | std::views::transform([](auto element) { return element; }) | negate, n_plus_1_negate));
130+
131+
const std::vector<int> n_negate_plus_1{0, -1, -2, -3, -4};
132+
assert(std::ranges::equal(n | negate | plus_1, n_negate_plus_1));
133+
assert(std::ranges::equal(n | std::views::reverse | negate | plus_1 | std::views::reverse, n_negate_plus_1));
134+
return true;
135+
}
136+
137+
int main(int, char**) {
138+
test();
139+
static_assert(test());
140+
141+
return 0;
142+
}

0 commit comments

Comments
 (0)