diff --git a/libcxx/docs/ReleaseNotes/19.rst b/libcxx/docs/ReleaseNotes/19.rst index b466b4cd8140cb..8724f321a9d117 100644 --- a/libcxx/docs/ReleaseNotes/19.rst +++ b/libcxx/docs/ReleaseNotes/19.rst @@ -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 ----------------------------- diff --git a/libcxx/docs/Status/Cxx23.rst b/libcxx/docs/Status/Cxx23.rst index b19ff4fdc0f79e..23d30c8128d71e 100644 --- a/libcxx/docs/Status/Cxx23.rst +++ b/libcxx/docs/Status/Cxx23.rst @@ -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. diff --git a/libcxx/docs/Status/Cxx23Papers.csv b/libcxx/docs/Status/Cxx23Papers.csv index 065db97a0b0b15..f75dd288304b27 100644 --- a/libcxx/docs/Status/Cxx23Papers.csv +++ b/libcxx/docs/Status/Cxx23Papers.csv @@ -45,7 +45,7 @@ "`P1413R3 `__","LWG","Deprecate ``std::aligned_storage`` and ``std::aligned_union``","February 2022","|Complete| [#note-P1413R3]_","" "`P2255R2 `__","LWG","A type trait to detect reference binding to temporary","February 2022","","" "`P2273R3 `__","LWG","Making ``std::unique_ptr`` constexpr","February 2022","|Complete|","16.0" -"`P2387R3 `__","LWG","Pipe support for user-defined range adaptors","February 2022","|Partial| [#note-P2387R3]_","","|ranges|" +"`P2387R3 `__","LWG","Pipe support for user-defined range adaptors","February 2022","|Complete|","19.0","|ranges|" "`P2440R1 `__","LWG","``ranges::iota``, ``ranges::shift_left`` and ``ranges::shift_right``","February 2022","","","|ranges|" "`P2441R2 `__","LWG","``views::join_with``","February 2022","|In Progress|","","|ranges|" "`P2442R1 `__","LWG","Windowing range adaptors: ``views::chunk`` and ``views::slide``","February 2022","","","|ranges|" diff --git a/libcxx/docs/Status/RangesMajorFeatures.csv b/libcxx/docs/Status/RangesMajorFeatures.csv index c0bec8d924e8a9..d00fbce9edf489 100644 --- a/libcxx/docs/Status/RangesMajorFeatures.csv +++ b/libcxx/docs/Status/RangesMajorFeatures.csv @@ -1,5 +1,5 @@ Standard,Name,Assignee,CL,Status C++23,`ranges::to `_,Konstantin Varlamov,`D142335 `_,Complete -C++23,`Pipe support for user-defined range adaptors `_,Unassigned,No patch yet,Not started +C++23,`Pipe support for user-defined range adaptors `_,"Louis Dionne, Jakub Mazurkiewicz, and Xiaoyang Liu",Various,Complete C++23,`Formatting Ranges `_,Mark de Wever,Various,Complete C++20,`Stashing stashing iterators for proper flattening `_,Jakub Mazurkiewicz,Various,In progress diff --git a/libcxx/include/__ranges/range_adaptor.h b/libcxx/include/__ranges/range_adaptor.h index 726b7eda019ee3..2da246f24e1d2f 100644 --- a/libcxx/include/__ranges/range_adaptor.h +++ b/libcxx/include/__ranges/range_adaptor.h @@ -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> @@ -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 + 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, @@ -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 -concept _RangeAdaptorClosure = derived_from, __range_adaptor_closure>>; +_Tp __derived_from_range_adaptor_closure(__range_adaptor_closure<_Tp>*); template -struct __range_adaptor_closure { - template - 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, _Closure> && - constructible_from, _OtherClosure> - [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator|(_Closure&& __c1, _OtherClosure&& __c2) noexcept( - is_nothrow_constructible_v, _Closure> && - is_nothrow_constructible_v, _OtherClosure>) { - return __range_adaptor_closure_t(std::__compose(std::forward<_OtherClosure>(__c2), std::forward<_Closure>(__c1))); - } +concept _RangeAdaptorClosure = !ranges::range> && requires { + // Ensure that `remove_cvref_t<_Tp>` is derived from `__range_adaptor_closure>` and isn't derived + // from `__range_adaptor_closure` for any other type `U`. + { ranges::__derived_from_range_adaptor_closure((remove_cvref_t<_Tp>*)nullptr) } -> same_as>; }; +template + 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, _Closure> && constructible_from, _OtherClosure> +[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator|(_Closure&& __c1, _OtherClosure&& __c2) noexcept( + is_nothrow_constructible_v, _Closure> && + is_nothrow_constructible_v, _OtherClosure>) { + return __range_adaptor_closure_t(std::__compose(std::forward<_OtherClosure>(__c2), std::forward<_Closure>(__c1))); +} + +template + requires is_class_v<_Tp> && same_as<_Tp, remove_cv_t<_Tp>> +struct __range_adaptor_closure {}; + +# if _LIBCPP_STD_VER >= 23 +template + 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 diff --git a/libcxx/include/ranges b/libcxx/include/ranges index 167d2137eaf454..07a525ed8641fd 100644 --- a/libcxx/include/ranges +++ b/libcxx/include/ranges @@ -93,6 +93,11 @@ namespace std::ranges { template concept viewable_range = see below; + // [range.adaptor.object], range adaptor objects + template + requires is_class_v && same_as> + class range_adaptor_closure { }; // Since c++23 + // [view.interface], class template view_interface template requires is_class_v && same_as> diff --git a/libcxx/test/std/ranges/range.adaptors/range.adaptor.object/range_adaptor_closure.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.adaptor.object/range_adaptor_closure.pass.cpp new file mode 100644 index 00000000000000..9d1eb124345813 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.adaptor.object/range_adaptor_closure.pass.cpp @@ -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 + +#include +#include + +#include "test_range.h" + +template +concept CanDeriveFromRangeAdaptorClosure = requires { typename std::ranges::range_adaptor_closure; }; +static_assert(!CanDeriveFromRangeAdaptorClosure); + +struct Foo {}; +static_assert(CanDeriveFromRangeAdaptorClosure); +static_assert(!CanDeriveFromRangeAdaptorClosure); +static_assert(!CanDeriveFromRangeAdaptorClosure); +static_assert(!CanDeriveFromRangeAdaptorClosure); +static_assert(!CanDeriveFromRangeAdaptorClosure); + +struct incomplete_t; +static_assert(CanDeriveFromRangeAdaptorClosure); + +using range_t = std::vector; + +template +concept RangeAdaptorClosure = + CanBePiped && CanBePiped && CanBePiped && + CanBePiped; + +struct callable : std::ranges::range_adaptor_closure { + static void operator()(const range_t&) {} +}; +static_assert(RangeAdaptorClosure); + +// `not_callable_1` doesn't have an `operator()` +struct not_callable_1 : std::ranges::range_adaptor_closure {}; +static_assert(!RangeAdaptorClosure); + +// `not_callable_2` doesn't have an `operator()` that accepts a `range` argument +struct not_callable_2 : std::ranges::range_adaptor_closure { + static void operator()() {} +}; +static_assert(!RangeAdaptorClosure); + +// `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_2` doesn't publicly derive from `std::ranges::range_adaptor_closure` +struct not_derived_from_2 : private std::ranges::range_adaptor_closure { + static void operator()(const range_t&) {} +}; +static_assert(!RangeAdaptorClosure); + +// `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 { + static void operator()(const range_t&) {} +}; +static_assert(!RangeAdaptorClosure); + +// `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, + std::ranges::range_adaptor_closure { + static void operator()(const range_t&) {} +}; +static_assert(!RangeAdaptorClosure); + +// `is_range` models `range` +struct is_range : std::ranges::range_adaptor_closure { + static void operator()(const range_t&) {} + int* begin() const { return nullptr; } + int* end() const { return nullptr; } +}; +static_assert(std::ranges::range && std::ranges::range); +static_assert(!RangeAdaptorClosure); + +// user-defined range adaptor closure object +struct negate_fn : std::ranges::range_adaptor_closure { + template + static constexpr decltype(auto) operator()(Range&& range) { + return std::forward(range) | std::views::transform([](auto element) { return -element; }); + } +}; +static_assert(RangeAdaptorClosure); +constexpr auto negate = negate_fn{}; + +// user-defined range adaptor closure object +struct plus_1_fn : std::ranges::range_adaptor_closure { + template + static constexpr decltype(auto) operator()(Range&& range) { + return std::forward(range) | std::views::transform([](auto element) { return element + 1; }); + } +}; +static_assert(RangeAdaptorClosure); +constexpr auto plus_1 = plus_1_fn{}; + +constexpr bool test() { + const std::vector n{1, 2, 3, 4, 5}; + const std::vector 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 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 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; +}