Skip to content

Commit ee44dd8

Browse files
committed
[libc++] Implement the underlying mechanism for range adaptors
This patch implements the underlying mechanism for range adaptors. It does so based on http://wg21.link/p2387, even though that paper hasn't been adopted yet. In the future, if p2387 is adopted, it would suffice to rename `__bind_back` to `std::bind_back` and `__range_adaptor_closure` to `std::range_adaptor_closure` to implement that paper by the spec. Differential Revision: https://reviews.llvm.org/D107098
1 parent 0353252 commit ee44dd8

File tree

11 files changed

+393
-6
lines changed

11 files changed

+393
-6
lines changed

libcxx/include/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ set(files
227227
__ranges/iota_view.h
228228
__ranges/join_view.h
229229
__ranges/non_propagating_cache.h
230+
__ranges/range_adaptor.h
230231
__ranges/ref_view.h
231232
__ranges/reverse_view.h
232233
__ranges/take_view.h

libcxx/include/__ranges/all.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <__iterator/iterator_traits.h>
1515
#include <__ranges/access.h>
1616
#include <__ranges/concepts.h>
17+
#include <__ranges/range_adaptor.h>
1718
#include <__ranges/ref_view.h>
1819
#include <__ranges/subrange.h>
1920
#include <__utility/__decay_copy.h>
@@ -35,10 +36,10 @@ _LIBCPP_BEGIN_NAMESPACE_STD
3536
namespace ranges::views {
3637

3738
namespace __all {
38-
struct __fn {
39+
struct __fn : __range_adaptor_closure<__fn> {
3940
template<class _Tp>
4041
requires ranges::view<decay_t<_Tp>>
41-
_LIBCPP_HIDE_FROM_ABI
42+
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI
4243
constexpr auto operator()(_Tp&& __t) const
4344
noexcept(noexcept(_VSTD::__decay_copy(_VSTD::forward<_Tp>(__t))))
4445
{
@@ -48,7 +49,7 @@ namespace __all {
4849
template<class _Tp>
4950
requires (!ranges::view<decay_t<_Tp>>) &&
5051
requires (_Tp&& __t) { ranges::ref_view{_VSTD::forward<_Tp>(__t)}; }
51-
_LIBCPP_HIDE_FROM_ABI
52+
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI
5253
constexpr auto operator()(_Tp&& __t) const
5354
noexcept(noexcept(ranges::ref_view{_VSTD::forward<_Tp>(__t)}))
5455
{
@@ -59,7 +60,7 @@ namespace __all {
5960
requires (!ranges::view<decay_t<_Tp>> &&
6061
!requires (_Tp&& __t) { ranges::ref_view{_VSTD::forward<_Tp>(__t)}; } &&
6162
requires (_Tp&& __t) { ranges::subrange{_VSTD::forward<_Tp>(__t)}; })
62-
_LIBCPP_HIDE_FROM_ABI
63+
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI
6364
constexpr auto operator()(_Tp&& __t) const
6465
noexcept(noexcept(ranges::subrange{_VSTD::forward<_Tp>(__t)}))
6566
{
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// -*- C++ -*-
2+
//===----------------------------------------------------------------------===//
3+
//
4+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5+
// See https://llvm.org/LICENSE.txt for license information.
6+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
#ifndef _LIBCPP___RANGES_RANGE_ADAPTOR_H
11+
#define _LIBCPP___RANGES_RANGE_ADAPTOR_H
12+
13+
#include <__config>
14+
#include <__functional/compose.h>
15+
#include <__functional/invoke.h>
16+
#include <__ranges/concepts.h>
17+
#include <__utility/forward.h>
18+
#include <__utility/move.h>
19+
#include <concepts>
20+
#include <type_traits>
21+
22+
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
23+
#pragma GCC system_header
24+
#endif
25+
26+
_LIBCPP_BEGIN_NAMESPACE_STD
27+
28+
#if !defined(_LIBCPP_HAS_NO_RANGES)
29+
30+
// CRTP base that one can derive from in order to be considered a range adaptor closure
31+
// by the library. When deriving from this class, a pipe operator will be provided to
32+
// make the following hold:
33+
// - `x | f` is equivalent to `f(x)`
34+
// - `f1 | f2` is an adaptor closure `g` such that `g(x)` is equivalent to `f2(f1(x))`
35+
template <class _Tp>
36+
struct __range_adaptor_closure;
37+
38+
// Type that wraps an arbitrary function object and makes it into a range adaptor closure,
39+
// i.e. something that can be called via the `x | f` notation.
40+
template <class _Fn>
41+
struct __range_adaptor_closure_t : _Fn, __range_adaptor_closure<__range_adaptor_closure_t<_Fn>> {
42+
constexpr explicit __range_adaptor_closure_t(_Fn&& __f) : _Fn(_VSTD::move(__f)) { }
43+
};
44+
45+
template <class _Tp>
46+
concept _RangeAdaptorClosure = derived_from<remove_cvref_t<_Tp>, __range_adaptor_closure<remove_cvref_t<_Tp>>>;
47+
48+
template <class _Tp>
49+
struct __range_adaptor_closure {
50+
template <ranges::viewable_range _View, _RangeAdaptorClosure _Closure>
51+
requires same_as<_Tp, remove_cvref_t<_Closure>> &&
52+
invocable<_Closure, _View>
53+
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI
54+
friend constexpr decltype(auto) operator|(_View&& __view, _Closure&& __closure)
55+
noexcept(is_nothrow_invocable_v<_Closure, _View>)
56+
{ return _VSTD::invoke(_VSTD::forward<_Closure>(__closure), _VSTD::forward<_View>(__view)); }
57+
58+
template <_RangeAdaptorClosure _Closure, _RangeAdaptorClosure _OtherClosure>
59+
requires same_as<_Tp, remove_cvref_t<_Closure>> &&
60+
constructible_from<decay_t<_Closure>, _Closure> &&
61+
constructible_from<decay_t<_OtherClosure>, _OtherClosure>
62+
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI
63+
friend constexpr auto operator|(_Closure&& __c1, _OtherClosure&& __c2)
64+
noexcept(is_nothrow_constructible_v<decay_t<_Closure>, _Closure> &&
65+
is_nothrow_constructible_v<decay_t<_OtherClosure>, _OtherClosure>)
66+
{ return __range_adaptor_closure_t(_VSTD::__compose(_VSTD::forward<_OtherClosure>(__c2), _VSTD::forward<_Closure>(__c1))); }
67+
};
68+
69+
#endif // !defined(_LIBCPP_HAS_NO_RANGES)
70+
71+
_LIBCPP_END_NAMESPACE_STD
72+
73+
#endif // _LIBCPP___RANGES_RANGE_ADAPTOR_H

libcxx/include/__ranges/transform_view.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#define _LIBCPP___RANGES_TRANSFORM_VIEW_H
1111

1212
#include <__config>
13+
#include <__functional/bind_back.h>
1314
#include <__functional/invoke.h>
1415
#include <__iterator/concepts.h>
1516
#include <__iterator/iter_swap.h>
@@ -20,8 +21,10 @@
2021
#include <__ranges/concepts.h>
2122
#include <__ranges/copyable_box.h>
2223
#include <__ranges/empty.h>
24+
#include <__ranges/range_adaptor.h>
2325
#include <__ranges/size.h>
2426
#include <__ranges/view_interface.h>
27+
#include <__utility/forward.h>
2528
#include <__utility/in_place.h>
2629
#include <__utility/move.h>
2730
#include <concepts>
@@ -401,6 +404,30 @@ class transform_view<_View, _Fn>::__sentinel {
401404
}
402405
};
403406

407+
namespace views {
408+
namespace __transform {
409+
struct __fn {
410+
template<class _Range, class _Fn>
411+
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI
412+
constexpr auto operator()(_Range&& __range, _Fn&& __f) const
413+
noexcept(noexcept(transform_view(_VSTD::forward<_Range>(__range), _VSTD::forward<_Fn>(__f))))
414+
-> decltype( transform_view(_VSTD::forward<_Range>(__range), _VSTD::forward<_Fn>(__f)))
415+
{ return transform_view(_VSTD::forward<_Range>(__range), _VSTD::forward<_Fn>(__f)); }
416+
417+
template<class _Fn>
418+
requires constructible_from<decay_t<_Fn>, _Fn>
419+
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI
420+
constexpr auto operator()(_Fn&& __f) const
421+
noexcept(is_nothrow_constructible_v<decay_t<_Fn>, _Fn>)
422+
{ return __range_adaptor_closure_t(_VSTD::__bind_back(*this, _VSTD::forward<_Fn>(__f))); }
423+
};
424+
}
425+
426+
inline namespace __cpo {
427+
inline constexpr auto transform = __transform::__fn{};
428+
}
429+
} // namespace views
430+
404431
} // namespace ranges
405432

406433
#endif // !defined(_LIBCPP_HAS_NO_RANGES)

libcxx/include/module.modulemap

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,11 @@ module std [system] {
647647

648648
module __ranges {
649649
module access { private header "__ranges/access.h" }
650-
module all { private header "__ranges/all.h" }
650+
module all {
651+
private header "__ranges/all.h"
652+
export functional.__functional.compose
653+
export functional.__functional.perfect_forward
654+
}
651655
module common_view { private header "__ranges/common_view.h" }
652656
module concepts { private header "__ranges/concepts.h" }
653657
module copyable_box { private header "__ranges/copyable_box.h" }
@@ -662,13 +666,18 @@ module std [system] {
662666
module iota_view { private header "__ranges/iota_view.h" }
663667
module join_view { private header "__ranges/join_view.h" }
664668
module non_propagating_cache { private header "__ranges/non_propagating_cache.h" }
669+
module range_adaptor { private header "__ranges/range_adaptor.h" }
665670
module ref_view { private header "__ranges/ref_view.h" }
666671
module reverse_view { private header "__ranges/reverse_view.h" }
667672
module size { private header "__ranges/size.h" }
668673
module single_view { private header "__ranges/single_view.h" }
669674
module subrange { private header "__ranges/subrange.h" }
670675
module take_view { private header "__ranges/take_view.h" }
671-
module transform_view { private header "__ranges/transform_view.h" }
676+
module transform_view {
677+
private header "__ranges/transform_view.h"
678+
export functional.__functional.bind_back
679+
export functional.__functional.perfect_forward
680+
}
672681
module view_interface { private header "__ranges/view_interface.h" }
673682
}
674683
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// -*- C++ -*-
2+
//===----------------------------------------------------------------------===//
3+
//
4+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5+
// See https://llvm.org/LICENSE.txt for license information.
6+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
// REQUIRES: modules-build
11+
12+
// WARNING: This test was generated by 'generate_private_header_tests.py'
13+
// and should not be edited manually.
14+
15+
// expected-error@*:* {{use of private header from outside its module: '__ranges/range_adaptor.h'}}
16+
#include <__ranges/range_adaptor.h>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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
10+
// UNSUPPORTED: libcpp-no-concepts
11+
// UNSUPPORTED: libcpp-has-no-incomplete-ranges
12+
// REQUIRES: libc++
13+
14+
// Test the libc++ extension that std::views::all is marked as [[nodiscard]].
15+
16+
#include <ranges>
17+
18+
void test() {
19+
int range[] = {1, 2, 3};
20+
auto f = [](int i) { return i; };
21+
22+
std::views::all(range); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
23+
range | std::views::all; // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
24+
std::views::transform(f) | std::views::all; // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
25+
std::views::all | std::views::transform(f); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
26+
}

libcxx/test/std/ranges/range.adaptors/range.all/all.pass.cpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
#include <ranges>
1616

1717
#include <cassert>
18+
#include <concepts>
19+
#include <type_traits>
20+
#include <utility>
21+
1822
#include "test_macros.h"
1923
#include "test_iterators.h"
2024

@@ -83,6 +87,11 @@ struct RandomAccessRange {
8387
template<>
8488
inline constexpr bool std::ranges::enable_borrowed_range<RandomAccessRange> = true;
8589

90+
template <class View, class T>
91+
concept CanBePiped = requires (View&& view, T&& t) {
92+
{ std::forward<View>(view) | std::forward<T>(t) };
93+
};
94+
8695
constexpr bool test() {
8796
{
8897
ASSERT_SAME_TYPE(decltype(std::views::all(View<true>())), View<true>);
@@ -142,6 +151,49 @@ constexpr bool test() {
142151
assert(std::ranges::end(subrange) == std::ranges::begin(subrange) + 8);
143152
}
144153

154+
// Check SFINAE friendliness of the call operator
155+
{
156+
static_assert(!std::is_invocable_v<decltype(std::views::all)>);
157+
static_assert(!std::is_invocable_v<decltype(std::views::all), RandomAccessRange, RandomAccessRange>);
158+
}
159+
160+
// Test that std::views::all is a range adaptor
161+
{
162+
// Test `v | views::all`
163+
{
164+
Range range(0);
165+
auto result = range | std::views::all;
166+
ASSERT_SAME_TYPE(decltype(result), std::ranges::ref_view<Range>);
167+
assert(&result.base() == &range);
168+
}
169+
170+
// Test `adaptor | views::all`
171+
{
172+
Range range(0);
173+
auto f = [](int i) { return i; };
174+
auto const partial = std::views::transform(f) | std::views::all;
175+
using Result = std::ranges::transform_view<std::ranges::ref_view<Range>, decltype(f)>;
176+
std::same_as<Result> auto result = partial(range);
177+
assert(&result.base().base() == &range);
178+
}
179+
180+
// Test `views::all | adaptor`
181+
{
182+
Range range(0);
183+
auto f = [](int i) { return i; };
184+
auto const partial = std::views::all | std::views::transform(f);
185+
using Result = std::ranges::transform_view<std::ranges::ref_view<Range>, decltype(f)>;
186+
std::same_as<Result> auto result = partial(range);
187+
assert(&result.base().base() == &range);
188+
}
189+
190+
{
191+
struct NotAView { };
192+
static_assert( CanBePiped<Range&, decltype(std::views::all)>);
193+
static_assert(!CanBePiped<NotAView, decltype(std::views::all)>);
194+
}
195+
}
196+
145197
{
146198
static_assert(std::same_as<decltype(std::views::all), decltype(std::ranges::views::all)>);
147199
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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
10+
// UNSUPPORTED: libcpp-no-concepts
11+
// UNSUPPORTED: libcpp-has-no-incomplete-ranges
12+
// REQUIRES: libc++
13+
14+
// Test the libc++ extension that std::views::transform is marked as [[nodiscard]] to avoid
15+
// the potential for user mistakenly thinking they're calling an algorithm.
16+
17+
#include <ranges>
18+
19+
void test() {
20+
int range[] = {1, 2, 3};
21+
auto f = [](int i) { return i; };
22+
23+
std::views::transform(f); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
24+
std::views::transform(range, f); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
25+
range | std::views::transform(f); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
26+
std::views::transform(f) | std::views::transform(f); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
27+
}

0 commit comments

Comments
 (0)