diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -226,6 +226,7 @@ __ranges/iota_view.h __ranges/join_view.h __ranges/non_propagating_cache.h + __ranges/range_adaptor.h __ranges/ref_view.h __ranges/reverse_view.h __ranges/take_view.h diff --git a/libcxx/include/__ranges/all.h b/libcxx/include/__ranges/all.h --- a/libcxx/include/__ranges/all.h +++ b/libcxx/include/__ranges/all.h @@ -14,6 +14,7 @@ #include <__iterator/iterator_traits.h> #include <__ranges/access.h> #include <__ranges/concepts.h> +#include <__ranges/range_adaptor.h> #include <__ranges/ref_view.h> #include <__ranges/subrange.h> #include <__utility/__decay_copy.h> @@ -35,7 +36,7 @@ namespace ranges::views { namespace __all { - struct __fn { + struct __fn : __range_adaptor_closure<__fn> { template requires ranges::view> _LIBCPP_HIDE_FROM_ABI diff --git a/libcxx/include/__ranges/range_adaptor.h b/libcxx/include/__ranges/range_adaptor.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__ranges/range_adaptor.h @@ -0,0 +1,63 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP___RANGES_RANGE_ADAPTOR_H +#define _LIBCPP___RANGES_RANGE_ADAPTOR_H + +#include <__config> +#include <__functional/compose.h> +#include <__functional/invoke.h> +#include <__ranges/concepts.h> +#include <__utility/forward.h> +#include <__utility/move.h> +#include +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +#pragma GCC system_header +#endif + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if !defined(_LIBCPP_HAS_NO_RANGES) + +// CRTP base that one can derive from in order to be considered a range adaptor closure +// by the library. That allows opting into the `operator|`s provided below. +template +struct __range_adaptor_closure { }; + +template +concept _RangeAdaptorClosure = derived_from, __range_adaptor_closure>>; + +// Type that wraps an arbitrary function object and makes it into a range adaptor closure, +// i.e. something that can be called via the `x | f` notation. +template +struct __range_adaptor_closure_t : _Fn, __range_adaptor_closure<__range_adaptor_closure_t<_Fn>> { + constexpr explicit __range_adaptor_closure_t(_Fn&& __f) : _Fn(_VSTD::move(__f)) { } +}; + +template +_LIBCPP_HIDE_FROM_ABI +constexpr auto operator|(_View&& __view, _Closure&& __closure) +noexcept(noexcept(_VSTD::invoke(_VSTD::forward<_Closure>(__closure), _VSTD::forward<_View>(__view)))) +-> decltype( _VSTD::invoke(_VSTD::forward<_Closure>(__closure), _VSTD::forward<_View>(__view))) +{ return _VSTD::invoke(_VSTD::forward<_Closure>(__closure), _VSTD::forward<_View>(__view)); } + +template <_RangeAdaptorClosure _Closure1, _RangeAdaptorClosure _Closure2> +_LIBCPP_HIDE_FROM_ABI +constexpr auto operator|(_Closure1&& __c1, _Closure2&& __c2) +noexcept(noexcept(__range_adaptor_closure_t(_VSTD::__compose(_VSTD::forward<_Closure2>(__c2), _VSTD::forward<_Closure1>(__c1))))) +-> decltype( __range_adaptor_closure_t(_VSTD::__compose(_VSTD::forward<_Closure2>(__c2), _VSTD::forward<_Closure1>(__c1)))) +{ return __range_adaptor_closure_t(_VSTD::__compose(_VSTD::forward<_Closure2>(__c2), _VSTD::forward<_Closure1>(__c1))); } + +#endif // !defined(_LIBCPP_HAS_NO_RANGES) + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___RANGES_RANGE_ADAPTOR_H diff --git a/libcxx/include/__ranges/transform_view.h b/libcxx/include/__ranges/transform_view.h --- a/libcxx/include/__ranges/transform_view.h +++ b/libcxx/include/__ranges/transform_view.h @@ -10,6 +10,7 @@ #define _LIBCPP___RANGES_TRANSFORM_VIEW_H #include <__config> +#include <__functional/bind_back.h> #include <__functional/invoke.h> #include <__iterator/concepts.h> #include <__iterator/iter_swap.h> @@ -20,8 +21,10 @@ #include <__ranges/concepts.h> #include <__ranges/copyable_box.h> #include <__ranges/empty.h> +#include <__ranges/range_adaptor.h> #include <__ranges/size.h> #include <__ranges/view_interface.h> +#include <__utility/forward.h> #include <__utility/in_place.h> #include <__utility/move.h> #include @@ -401,6 +404,30 @@ } }; +namespace views { +namespace __transform { + struct __fn { + template + _LIBCPP_HIDE_FROM_ABI + constexpr auto operator()(_Range&& __range, _Fn&& __f) const + noexcept(noexcept(transform_view(_VSTD::forward<_Range>(__range), _VSTD::forward<_Fn>(__f)))) + -> decltype( transform_view(_VSTD::forward<_Range>(__range), _VSTD::forward<_Fn>(__f))) + { return transform_view(_VSTD::forward<_Range>(__range), _VSTD::forward<_Fn>(__f)); } + + template + _LIBCPP_HIDE_FROM_ABI + constexpr auto operator()(_Fn&& __f) const + noexcept(noexcept(__range_adaptor_closure_t(_VSTD::__bind_back(*this, _VSTD::forward<_Fn>(__f))))) + -> decltype( __range_adaptor_closure_t(_VSTD::__bind_back(*this, _VSTD::forward<_Fn>(__f)))) + { return __range_adaptor_closure_t(_VSTD::__bind_back(*this, _VSTD::forward<_Fn>(__f))); } + }; +} + +inline namespace __cpo { + inline constexpr auto transform = __transform::__fn{}; +} +} // namespace views + } // namespace ranges #endif // !defined(_LIBCPP_HAS_NO_RANGES) diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap --- a/libcxx/include/module.modulemap +++ b/libcxx/include/module.modulemap @@ -661,13 +661,18 @@ module iota_view { private header "__ranges/iota_view.h" } module join_view { private header "__ranges/join_view.h" } module non_propagating_cache { private header "__ranges/non_propagating_cache.h" } + module range_adaptor { private header "__ranges/range_adaptor.h" } module ref_view { private header "__ranges/ref_view.h" } module reverse_view { private header "__ranges/reverse_view.h" } module size { private header "__ranges/size.h" } module single_view { private header "__ranges/single_view.h" } module subrange { private header "__ranges/subrange.h" } module take_view { private header "__ranges/take_view.h" } - module transform_view { private header "__ranges/transform_view.h" } + module transform_view { + private header "__ranges/transform_view.h" + export functional.__functional.perfect_forward + export functional.__functional.bind_back + } module view_interface { private header "__ranges/view_interface.h" } } } diff --git a/libcxx/test/libcxx/diagnostics/detail.headers/ranges/range_adaptor.module.verify.cpp b/libcxx/test/libcxx/diagnostics/detail.headers/ranges/range_adaptor.module.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/diagnostics/detail.headers/ranges/range_adaptor.module.verify.cpp @@ -0,0 +1,16 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// REQUIRES: modules-build + +// WARNING: This test was generated by 'generate_private_header_tests.py' +// and should not be edited manually. + +// expected-error@*:* {{use of private header from outside its module: '__ranges/range_adaptor.h'}} +#include <__ranges/range_adaptor.h> diff --git a/libcxx/test/std/ranges/range.adaptors/range.all/all.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.all/all.pass.cpp --- a/libcxx/test/std/ranges/range.adaptors/range.all/all.pass.cpp +++ b/libcxx/test/std/ranges/range.adaptors/range.all/all.pass.cpp @@ -15,6 +15,9 @@ #include #include +#include +#include + #include "test_macros.h" #include "test_iterators.h" @@ -83,6 +86,11 @@ template<> inline constexpr bool std::ranges::enable_borrowed_range = true; +template +concept CanBePiped = requires (View&& view, T&& t) { + { std::forward(view) | std::forward(t) }; +}; + constexpr bool test() { { ASSERT_SAME_TYPE(decltype(std::views::all(View())), View); @@ -142,6 +150,29 @@ assert(std::ranges::end(subrange) == std::ranges::begin(subrange) + 8); } + // Check SFINAE friendliness of the call operator + { + static_assert(!std::is_invocable_v); + static_assert(!std::is_invocable_v); + } + + // Test that std::views::all is a range adaptor + { + // Test `v | views::all` + { + Range range(0); + auto result = range | std::views::all; + ASSERT_SAME_TYPE(decltype(result), std::ranges::ref_view); + assert(&result.base() == &range); + } + + { + struct NotAView { }; + static_assert( CanBePiped); + static_assert(!CanBePiped); + } + } + { static_assert(std::same_as); } diff --git a/libcxx/test/std/ranges/range.adaptors/range.transform/adaptor.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.transform/adaptor.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.transform/adaptor.pass.cpp @@ -0,0 +1,139 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// UNSUPPORTED: libcpp-no-concepts +// UNSUPPORTED: libcpp-has-no-incomplete-ranges + +// std::views::transform + +#include +#include +#include +#include + +#include "test_macros.h" +#include "types.h" + +template +concept CanBePiped = requires (View&& view, T&& t) { + { std::forward(view) | std::forward(t) }; +}; + +constexpr bool test() { + int buff[8] = {0, 1, 2, 3, 4, 5, 6, 7}; + + // Test `views::transform(f)(v)` + { + { + auto result = std::views::transform(PlusOne{})(ContiguousView{buff}); + ASSERT_SAME_TYPE(decltype(result), std::ranges::transform_view); + assert(result.begin().base() == buff); + assert(result[0] == 1); + assert(result[1] == 2); + assert(result[2] == 3); + } + { + auto const partial = std::views::transform(PlusOne{}); + auto result = partial(ContiguousView{buff}); + ASSERT_SAME_TYPE(decltype(result), std::ranges::transform_view); + assert(result.begin().base() == buff); + assert(result[0] == 1); + assert(result[1] == 2); + assert(result[2] == 3); + } + } + + // Test `v | views::transform(f)` + { + { + auto result = ContiguousView{buff} | std::views::transform(PlusOne{}); + ASSERT_SAME_TYPE(decltype(result), std::ranges::transform_view); + assert(result.begin().base() == buff); + assert(result[0] == 1); + assert(result[1] == 2); + assert(result[2] == 3); + } + { + auto const partial = std::views::transform(PlusOne{}); + auto result = ContiguousView{buff} | partial; + ASSERT_SAME_TYPE(decltype(result), std::ranges::transform_view); + assert(result.begin().base() == buff); + assert(result[0] == 1); + assert(result[1] == 2); + assert(result[2] == 3); + } + } + + // Test `views::transform(v, f)` + { + auto result = std::views::transform(ContiguousView{buff}, PlusOne{}); + ASSERT_SAME_TYPE(decltype(result), std::ranges::transform_view); + assert(result.begin().base() == buff); + assert(result[0] == 1); + assert(result[1] == 2); + assert(result[2] == 3); + } + + // Test that one can call std::views::transform with arbitrary stuff, as long as we + // don't try to actually complete the call by passing it a range. + // + // That makes no sense and we can't do anything with the result, but it's valid. + { + struct X { }; + auto partial = std::views::transform(X{}); + (void)partial; + } + + // Test `adaptor | views::transform(f)` + { + { + auto result = ContiguousView{buff} | std::views::transform(PlusOne{}) | std::views::transform(TimesTwo{}); + ASSERT_SAME_TYPE(decltype(result), std::ranges::transform_view, TimesTwo>); + assert(result.begin().base().base() == buff); + assert(result[0] == 2); + assert(result[1] == 4); + assert(result[2] == 6); + } + { + auto const partial = std::views::transform(PlusOne{}) | std::views::transform(TimesTwo{}); + auto result = ContiguousView{buff} | partial; + ASSERT_SAME_TYPE(decltype(result), std::ranges::transform_view, TimesTwo>); + assert(result.begin().base().base() == buff); + assert(result[0] == 2); + assert(result[1] == 4); + assert(result[2] == 6); + } + } + + // Test SFINAE friendliness + { + struct NotAView { }; + static_assert(!CanBePiped); + static_assert( CanBePiped); + static_assert(!CanBePiped); + + static_assert(!std::is_invocable_v); + static_assert(!std::is_invocable_v); + static_assert( std::is_invocable_v); + static_assert(!std::is_invocable_v); + } + + { + static_assert(std::is_same_v); + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.transform/types.h b/libcxx/test/std/ranges/range.adaptors/range.transform/types.h --- a/libcxx/test/std/ranges/range.adaptors/range.transform/types.h +++ b/libcxx/test/std/ranges/range.adaptors/range.transform/types.h @@ -129,6 +129,10 @@ constexpr ThreeWayCompIter end() const { return ThreeWayCompIter(globalBuff + 8); } }; +struct TimesTwo { + constexpr int operator()(int x) const { return x * 2; } +}; + struct PlusOneMutable { constexpr int operator()(int x) { return x + 1; } };