[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
This commit is contained in:
Louis Dionne 2021-08-11 17:36:35 -04:00
parent 035325275c
commit ee44dd8062
11 changed files with 393 additions and 6 deletions

View File

@ -227,6 +227,7 @@ set(files
__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

View File

@ -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,10 +36,10 @@ _LIBCPP_BEGIN_NAMESPACE_STD
namespace ranges::views {
namespace __all {
struct __fn {
struct __fn : __range_adaptor_closure<__fn> {
template<class _Tp>
requires ranges::view<decay_t<_Tp>>
_LIBCPP_HIDE_FROM_ABI
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI
constexpr auto operator()(_Tp&& __t) const
noexcept(noexcept(_VSTD::__decay_copy(_VSTD::forward<_Tp>(__t))))
{
@ -48,7 +49,7 @@ namespace __all {
template<class _Tp>
requires (!ranges::view<decay_t<_Tp>>) &&
requires (_Tp&& __t) { ranges::ref_view{_VSTD::forward<_Tp>(__t)}; }
_LIBCPP_HIDE_FROM_ABI
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI
constexpr auto operator()(_Tp&& __t) const
noexcept(noexcept(ranges::ref_view{_VSTD::forward<_Tp>(__t)}))
{
@ -59,7 +60,7 @@ namespace __all {
requires (!ranges::view<decay_t<_Tp>> &&
!requires (_Tp&& __t) { ranges::ref_view{_VSTD::forward<_Tp>(__t)}; } &&
requires (_Tp&& __t) { ranges::subrange{_VSTD::forward<_Tp>(__t)}; })
_LIBCPP_HIDE_FROM_ABI
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI
constexpr auto operator()(_Tp&& __t) const
noexcept(noexcept(ranges::subrange{_VSTD::forward<_Tp>(__t)}))
{

View File

@ -0,0 +1,73 @@
// -*- 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 <concepts>
#include <type_traits>
#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. 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>
struct __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 <class _Fn>
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 <class _Tp>
concept _RangeAdaptorClosure = derived_from<remove_cvref_t<_Tp>, __range_adaptor_closure<remove_cvref_t<_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 _VSTD::invoke(_VSTD::forward<_Closure>(__closure), _VSTD::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(_VSTD::__compose(_VSTD::forward<_OtherClosure>(__c2), _VSTD::forward<_Closure>(__c1))); }
};
#endif // !defined(_LIBCPP_HAS_NO_RANGES)
_LIBCPP_END_NAMESPACE_STD
#endif // _LIBCPP___RANGES_RANGE_ADAPTOR_H

View File

@ -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 <concepts>
@ -401,6 +404,30 @@ public:
}
};
namespace views {
namespace __transform {
struct __fn {
template<class _Range, class _Fn>
[[nodiscard]] _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<class _Fn>
requires constructible_from<decay_t<_Fn>, _Fn>
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI
constexpr auto operator()(_Fn&& __f) const
noexcept(is_nothrow_constructible_v<decay_t<_Fn>, _Fn>)
{ 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)

View File

@ -647,7 +647,11 @@ module std [system] {
module __ranges {
module access { private header "__ranges/access.h" }
module all { private header "__ranges/all.h" }
module all {
private header "__ranges/all.h"
export functional.__functional.compose
export functional.__functional.perfect_forward
}
module common_view { private header "__ranges/common_view.h" }
module concepts { private header "__ranges/concepts.h" }
module copyable_box { private header "__ranges/copyable_box.h" }
@ -662,13 +666,18 @@ module std [system] {
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.bind_back
export functional.__functional.perfect_forward
}
module view_interface { private header "__ranges/view_interface.h" }
}
}

View File

@ -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>

View File

@ -0,0 +1,26 @@
//===----------------------------------------------------------------------===//
//
// 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
// REQUIRES: libc++
// Test the libc++ extension that std::views::all is marked as [[nodiscard]].
#include <ranges>
void test() {
int range[] = {1, 2, 3};
auto f = [](int i) { return i; };
std::views::all(range); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
range | std::views::all; // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
std::views::transform(f) | std::views::all; // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
std::views::all | std::views::transform(f); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
}

View File

@ -15,6 +15,10 @@
#include <ranges>
#include <cassert>
#include <concepts>
#include <type_traits>
#include <utility>
#include "test_macros.h"
#include "test_iterators.h"
@ -83,6 +87,11 @@ struct RandomAccessRange {
template<>
inline constexpr bool std::ranges::enable_borrowed_range<RandomAccessRange> = true;
template <class View, class T>
concept CanBePiped = requires (View&& view, T&& t) {
{ std::forward<View>(view) | std::forward<T>(t) };
};
constexpr bool test() {
{
ASSERT_SAME_TYPE(decltype(std::views::all(View<true>())), View<true>);
@ -142,6 +151,49 @@ constexpr bool test() {
assert(std::ranges::end(subrange) == std::ranges::begin(subrange) + 8);
}
// Check SFINAE friendliness of the call operator
{
static_assert(!std::is_invocable_v<decltype(std::views::all)>);
static_assert(!std::is_invocable_v<decltype(std::views::all), RandomAccessRange, RandomAccessRange>);
}
// 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<Range>);
assert(&result.base() == &range);
}
// Test `adaptor | views::all`
{
Range range(0);
auto f = [](int i) { return i; };
auto const partial = std::views::transform(f) | std::views::all;
using Result = std::ranges::transform_view<std::ranges::ref_view<Range>, decltype(f)>;
std::same_as<Result> auto result = partial(range);
assert(&result.base().base() == &range);
}
// Test `views::all | adaptor`
{
Range range(0);
auto f = [](int i) { return i; };
auto const partial = std::views::all | std::views::transform(f);
using Result = std::ranges::transform_view<std::ranges::ref_view<Range>, decltype(f)>;
std::same_as<Result> auto result = partial(range);
assert(&result.base().base() == &range);
}
{
struct NotAView { };
static_assert( CanBePiped<Range&, decltype(std::views::all)>);
static_assert(!CanBePiped<NotAView, decltype(std::views::all)>);
}
}
{
static_assert(std::same_as<decltype(std::views::all), decltype(std::ranges::views::all)>);
}

View File

@ -0,0 +1,27 @@
//===----------------------------------------------------------------------===//
//
// 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
// REQUIRES: libc++
// Test the libc++ extension that std::views::transform is marked as [[nodiscard]] to avoid
// the potential for user mistakenly thinking they're calling an algorithm.
#include <ranges>
void test() {
int range[] = {1, 2, 3};
auto f = [](int i) { return i; };
std::views::transform(f); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
std::views::transform(range, f); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
range | std::views::transform(f); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
std::views::transform(f) | std::views::transform(f); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
}

View File

@ -0,0 +1,151 @@
//===----------------------------------------------------------------------===//
//
// 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 <ranges>
#include <cassert>
#include <concepts>
#include <type_traits>
#include <utility>
#include "test_macros.h"
#include "types.h"
template <class View, class T>
concept CanBePiped = requires (View&& view, T&& t) {
{ std::forward<View>(view) | std::forward<T>(t) };
};
struct NonCopyableFunction {
NonCopyableFunction(NonCopyableFunction const&) = delete;
template <class T>
constexpr T operator()(T x) const { return x; }
};
constexpr bool test() {
int buff[8] = {0, 1, 2, 3, 4, 5, 6, 7};
// Test `views::transform(f)(v)`
{
{
using Result = std::ranges::transform_view<ContiguousView, PlusOne>;
std::same_as<Result> auto result = std::views::transform(PlusOne{})(ContiguousView{buff});
assert(result.begin().base() == buff);
assert(result[0] == 1);
assert(result[1] == 2);
assert(result[2] == 3);
}
{
auto const partial = std::views::transform(PlusOne{});
using Result = std::ranges::transform_view<ContiguousView, PlusOne>;
std::same_as<Result> auto result = partial(ContiguousView{buff});
assert(result.begin().base() == buff);
assert(result[0] == 1);
assert(result[1] == 2);
assert(result[2] == 3);
}
}
// Test `v | views::transform(f)`
{
{
using Result = std::ranges::transform_view<ContiguousView, PlusOne>;
std::same_as<Result> auto result = ContiguousView{buff} | std::views::transform(PlusOne{});
assert(result.begin().base() == buff);
assert(result[0] == 1);
assert(result[1] == 2);
assert(result[2] == 3);
}
{
auto const partial = std::views::transform(PlusOne{});
using Result = std::ranges::transform_view<ContiguousView, PlusOne>;
std::same_as<Result> auto result = ContiguousView{buff} | partial;
assert(result.begin().base() == buff);
assert(result[0] == 1);
assert(result[1] == 2);
assert(result[2] == 3);
}
}
// Test `views::transform(v, f)`
{
using Result = std::ranges::transform_view<ContiguousView, PlusOne>;
std::same_as<Result> auto result = std::views::transform(ContiguousView{buff}, PlusOne{});
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)`
{
{
using Result = std::ranges::transform_view<std::ranges::transform_view<ContiguousView, PlusOne>, TimesTwo>;
std::same_as<Result> auto result = ContiguousView{buff} | std::views::transform(PlusOne{}) | std::views::transform(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{});
using Result = std::ranges::transform_view<std::ranges::transform_view<ContiguousView, PlusOne>, TimesTwo>;
std::same_as<Result> auto result = ContiguousView{buff} | partial;
assert(result.begin().base().base() == buff);
assert(result[0] == 2);
assert(result[1] == 4);
assert(result[2] == 6);
}
}
// Test SFINAE friendliness
{
struct NotAView { };
struct NotInvocable { };
static_assert(!CanBePiped<ContiguousView, decltype(std::views::transform)>);
static_assert( CanBePiped<ContiguousView, decltype(std::views::transform(PlusOne{}))>);
static_assert(!CanBePiped<NotAView, decltype(std::views::transform(PlusOne{}))>);
static_assert(!CanBePiped<ContiguousView, decltype(std::views::transform(NotInvocable{}))>);
static_assert(!std::is_invocable_v<decltype(std::views::transform)>);
static_assert(!std::is_invocable_v<decltype(std::views::transform), PlusOne, ContiguousView>);
static_assert( std::is_invocable_v<decltype(std::views::transform), ContiguousView, PlusOne>);
static_assert(!std::is_invocable_v<decltype(std::views::transform), ContiguousView, PlusOne, PlusOne>);
static_assert(!std::is_invocable_v<decltype(std::views::transform), NonCopyableFunction>);
}
{
static_assert(std::is_same_v<decltype(std::ranges::views::transform), decltype(std::views::transform)>);
}
return true;
}
int main(int, char**) {
test();
static_assert(test());
return 0;
}

View File

@ -129,6 +129,10 @@ struct ThreeWayCompView : std::ranges::view_base {
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; }
};