Skip to content

[libc++][ranges] P2387R3: Pipe support for user-defined range adaptors #89148

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 23, 2024
Merged
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
1 change: 1 addition & 0 deletions libcxx/docs/ReleaseNotes/19.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Implemented Papers
- P2302R4 - ``std::ranges::contains``
- P1659R3 - ``std::ranges::starts_with`` and ``std::ranges::ends_with``
- P3029R1 - Better ``mdspan``'s CTAD
- P2387R3 - Pipe support for user-defined range adaptors

Improvements and New Features
-----------------------------
Expand Down
1 change: 0 additions & 1 deletion libcxx/docs/Status/Cxx23.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ Paper Status
.. [#note-P0533R9] P0533R9: ``isfinite``, ``isinf``, ``isnan`` and ``isnormal`` are implemented.
.. [#note-P1413R3] P1413R3: ``std::aligned_storage_t`` and ``std::aligned_union_t`` are marked deprecated, but
clang doesn't issue a diagnostic for deprecated using template declarations.
.. [#note-P2387R3] P2387R3: ``bind_back`` only
.. [#note-P2520R0] P2520R0: Libc++ implemented this paper as a DR in C++20 as well.
.. [#note-P2711R1] P2711R1: ``join_with_view`` hasn't been done yet since this type isn't implemented yet.
.. [#note-P2770R0] P2770R0: ``join_with_view`` hasn't been done yet since this type isn't implemented yet.
Expand Down
2 changes: 1 addition & 1 deletion libcxx/docs/Status/Cxx23Papers.csv
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"`P1413R3 <https://wg21.link/P1413R3>`__","LWG","Deprecate ``std::aligned_storage`` and ``std::aligned_union``","February 2022","|Complete| [#note-P1413R3]_",""
"`P2255R2 <https://wg21.link/P2255R2>`__","LWG","A type trait to detect reference binding to temporary","February 2022","",""
"`P2273R3 <https://wg21.link/P2273R3>`__","LWG","Making ``std::unique_ptr`` constexpr","February 2022","|Complete|","16.0"
"`P2387R3 <https://wg21.link/P2387R3>`__","LWG","Pipe support for user-defined range adaptors","February 2022","|Partial| [#note-P2387R3]_","","|ranges|"
"`P2387R3 <https://wg21.link/P2387R3>`__","LWG","Pipe support for user-defined range adaptors","February 2022","|Complete|","19.0","|ranges|"
"`P2440R1 <https://wg21.link/P2440R1>`__","LWG","``ranges::iota``, ``ranges::shift_left`` and ``ranges::shift_right``","February 2022","","","|ranges|"
"`P2441R2 <https://wg21.link/P2441R2>`__","LWG","``views::join_with``","February 2022","|In Progress|","","|ranges|"
"`P2442R1 <https://wg21.link/P2442R1>`__","LWG","Windowing range adaptors: ``views::chunk`` and ``views::slide``","February 2022","","","|ranges|"
Expand Down
2 changes: 1 addition & 1 deletion libcxx/docs/Status/RangesMajorFeatures.csv
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Standard,Name,Assignee,CL,Status
C++23,`ranges::to <https://wg21.link/P1206R7>`_,Konstantin Varlamov,`D142335 <https://reviews.llvm.org/D142335>`_,Complete
C++23,`Pipe support for user-defined range adaptors <https://wg21.link/P2387R3>`_,Unassigned,No patch yet,Not started
C++23,`Pipe support for user-defined range adaptors <https://wg21.link/P2387R3>`_,"Louis Dionne, Jakub Mazurkiewicz, and Xiaoyang Liu",Various,Complete
C++23,`Formatting Ranges <https://wg21.link/P2286R8>`_,Mark de Wever,Various,Complete
C++20,`Stashing stashing iterators for proper flattening <https://wg21.link/P2770R0>`_,Jakub Mazurkiewicz,Various,In progress
53 changes: 36 additions & 17 deletions libcxx/include/__ranges/range_adaptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <__functional/invoke.h>
#include <__ranges/concepts.h>
#include <__type_traits/decay.h>
#include <__type_traits/is_class.h>
#include <__type_traits/is_nothrow_constructible.h>
#include <__type_traits/remove_cvref.h>
#include <__utility/forward.h>
Expand All @@ -35,12 +36,15 @@ _LIBCPP_BEGIN_NAMESPACE_STD

#if _LIBCPP_STD_VER >= 20

namespace ranges {

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

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

template <class _Tp>
concept _RangeAdaptorClosure = derived_from<remove_cvref_t<_Tp>, __range_adaptor_closure<remove_cvref_t<_Tp>>>;
_Tp __derived_from_range_adaptor_closure(__range_adaptor_closure<_Tp>*);

template <class _Tp>
struct __range_adaptor_closure {
template <ranges::viewable_range _View, _RangeAdaptorClosure _Closure>
requires same_as<_Tp, remove_cvref_t<_Closure>> && invocable<_Closure, _View>
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr decltype(auto)
operator|(_View&& __view, _Closure&& __closure) noexcept(is_nothrow_invocable_v<_Closure, _View>) {
return std::invoke(std::forward<_Closure>(__closure), std::forward<_View>(__view));
}

template <_RangeAdaptorClosure _Closure, _RangeAdaptorClosure _OtherClosure>
requires same_as<_Tp, remove_cvref_t<_Closure>> && constructible_from<decay_t<_Closure>, _Closure> &&
constructible_from<decay_t<_OtherClosure>, _OtherClosure>
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator|(_Closure&& __c1, _OtherClosure&& __c2) noexcept(
is_nothrow_constructible_v<decay_t<_Closure>, _Closure> &&
is_nothrow_constructible_v<decay_t<_OtherClosure>, _OtherClosure>) {
return __range_adaptor_closure_t(std::__compose(std::forward<_OtherClosure>(__c2), std::forward<_Closure>(__c1)));
}
concept _RangeAdaptorClosure = !ranges::range<remove_cvref_t<_Tp>> && requires {
// Ensure that `remove_cvref_t<_Tp>` is derived from `__range_adaptor_closure<remove_cvref_t<_Tp>>` and isn't derived
// from `__range_adaptor_closure<U>` for any other type `U`.
{ ranges::__derived_from_range_adaptor_closure((remove_cvref_t<_Tp>*)nullptr) } -> same_as<remove_cvref_t<_Tp>>;
};

template <ranges::range _Range, _RangeAdaptorClosure _Closure>
requires invocable<_Closure, _Range>
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr decltype(auto)
operator|(_Range&& __range, _Closure&& __closure) noexcept(is_nothrow_invocable_v<_Closure, _Range>) {
return std::invoke(std::forward<_Closure>(__closure), std::forward<_Range>(__range));
}

template <_RangeAdaptorClosure _Closure, _RangeAdaptorClosure _OtherClosure>
requires constructible_from<decay_t<_Closure>, _Closure> && constructible_from<decay_t<_OtherClosure>, _OtherClosure>
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator|(_Closure&& __c1, _OtherClosure&& __c2) noexcept(
is_nothrow_constructible_v<decay_t<_Closure>, _Closure> &&
is_nothrow_constructible_v<decay_t<_OtherClosure>, _OtherClosure>) {
return __range_adaptor_closure_t(std::__compose(std::forward<_OtherClosure>(__c2), std::forward<_Closure>(__c1)));
}

template <class _Tp>
requires is_class_v<_Tp> && same_as<_Tp, remove_cv_t<_Tp>>
struct __range_adaptor_closure {};

# if _LIBCPP_STD_VER >= 23
template <class _Tp>
requires is_class_v<_Tp> && same_as<_Tp, remove_cv_t<_Tp>>
class range_adaptor_closure : public __range_adaptor_closure<_Tp> {};
# endif // _LIBCPP_STD_VER >= 23

} // namespace ranges

#endif // _LIBCPP_STD_VER >= 20

_LIBCPP_END_NAMESPACE_STD
Expand Down
5 changes: 5 additions & 0 deletions libcxx/include/ranges
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ namespace std::ranges {
template<class T>
concept viewable_range = see below;

// [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 { }; // Since c++23

// [view.interface], class template view_interface
template<class D>
requires is_class_v<D> && same_as<D, remove_cv_t<D>>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20

// std::ranges::range_adaptor_closure;

#include <ranges>

#include <algorithm>
#include <vector>

#include "test_range.h"

template <class T>
concept CanDeriveFromRangeAdaptorClosure = requires { typename std::ranges::range_adaptor_closure<T>; };
static_assert(!CanDeriveFromRangeAdaptorClosure<int>);

struct Foo {};
static_assert(CanDeriveFromRangeAdaptorClosure<Foo>);
static_assert(!CanDeriveFromRangeAdaptorClosure<Foo&>);
static_assert(!CanDeriveFromRangeAdaptorClosure<const Foo>);
static_assert(!CanDeriveFromRangeAdaptorClosure<volatile Foo>);
static_assert(!CanDeriveFromRangeAdaptorClosure<const volatile Foo&&>);

struct incomplete_t;
static_assert(CanDeriveFromRangeAdaptorClosure<incomplete_t>);

using range_t = std::vector<int>;

template <class T>
concept RangeAdaptorClosure =
CanBePiped<range_t, T&> && CanBePiped<range_t, const T&> && CanBePiped<range_t, T&&> &&
CanBePiped<range_t, const T&&>;

struct callable : std::ranges::range_adaptor_closure<callable> {
static void operator()(const range_t&) {}
};
static_assert(RangeAdaptorClosure<callable>);

// `not_callable_1` doesn't have an `operator()`
struct not_callable_1 : std::ranges::range_adaptor_closure<not_callable_1> {};
static_assert(!RangeAdaptorClosure<not_callable_1>);

// `not_callable_2` doesn't have an `operator()` that accepts a `range` argument
struct not_callable_2 : std::ranges::range_adaptor_closure<not_callable_2> {
static void operator()() {}
};
static_assert(!RangeAdaptorClosure<not_callable_2>);

// `not_derived_from_1` doesn't derive from `std::ranges::range_adaptor_closure`
struct not_derived_from_1 {
static void operator()(const range_t&) {}
};
static_assert(!RangeAdaptorClosure<not_derived_from_1>);

// `not_derived_from_2` doesn't publicly derive from `std::ranges::range_adaptor_closure`
struct not_derived_from_2 : private std::ranges::range_adaptor_closure<not_derived_from_2> {
static void operator()(const range_t&) {}
};
static_assert(!RangeAdaptorClosure<not_derived_from_2>);

// `not_derived_from_3` doesn't derive from the correct specialization of `std::ranges::range_adaptor_closure`
struct not_derived_from_3 : std::ranges::range_adaptor_closure<callable> {
static void operator()(const range_t&) {}
};
static_assert(!RangeAdaptorClosure<not_derived_from_3>);

// `not_derived_from_4` doesn't derive from exactly one specialization of `std::ranges::range_adaptor_closure`
struct not_derived_from_4
: std::ranges::range_adaptor_closure<not_derived_from_4>,
std::ranges::range_adaptor_closure<callable> {
static void operator()(const range_t&) {}
};
static_assert(!RangeAdaptorClosure<not_derived_from_4>);

// `is_range` models `range`
struct is_range : std::ranges::range_adaptor_closure<is_range> {
static void operator()(const range_t&) {}
int* begin() const { return nullptr; }
int* end() const { return nullptr; }
};
static_assert(std::ranges::range<is_range> && std::ranges::range<const is_range>);
static_assert(!RangeAdaptorClosure<is_range>);

// user-defined range adaptor closure object
struct negate_fn : std::ranges::range_adaptor_closure<negate_fn> {
template <std::ranges::range Range>
static constexpr decltype(auto) operator()(Range&& range) {
return std::forward<Range>(range) | std::views::transform([](auto element) { return -element; });
}
};
static_assert(RangeAdaptorClosure<negate_fn>);
constexpr auto negate = negate_fn{};

// user-defined range adaptor closure object
struct plus_1_fn : std::ranges::range_adaptor_closure<plus_1_fn> {
template <std::ranges::range Range>
static constexpr decltype(auto) operator()(Range&& range) {
return std::forward<Range>(range) | std::views::transform([](auto element) { return element + 1; });
}
};
static_assert(RangeAdaptorClosure<plus_1_fn>);
constexpr auto plus_1 = plus_1_fn{};

constexpr bool test() {
const std::vector<int> n{1, 2, 3, 4, 5};
const std::vector<int> n_negate{-1, -2, -3, -4, -5};

assert(std::ranges::equal(n | negate, n_negate));
assert(std::ranges::equal(negate(n), n_negate));

assert(std::ranges::equal(n | negate | negate, n));
assert(std::ranges::equal(n | (negate | negate), n));
assert(std::ranges::equal((n | negate) | negate, n));
assert(std::ranges::equal(negate(n) | negate, n));
assert(std::ranges::equal(negate(n | negate), n));
assert(std::ranges::equal((negate | negate)(n), n));
assert(std::ranges::equal(negate(negate(n)), n));

const std::vector<int> n_plus_1_negate{-2, -3, -4, -5, -6};
assert(std::ranges::equal(n | plus_1 | negate, n_plus_1_negate));
assert(std::ranges::equal(
n | plus_1 | std::views::transform([](auto element) { return element; }) | negate, n_plus_1_negate));

const std::vector<int> n_negate_plus_1{0, -1, -2, -3, -4};
assert(std::ranges::equal(n | negate | plus_1, n_negate_plus_1));
assert(std::ranges::equal(n | std::views::reverse | negate | plus_1 | std::views::reverse, n_negate_plus_1));
return true;
}

int main(int, char**) {
test();
static_assert(test());

return 0;
}
Loading