[libcxx-commits] [libcxx] ee44dd8 - [libc++] Implement the underlying mechanism for range adaptors

Louis Dionne via libcxx-commits libcxx-commits at lists.llvm.org
Thu Aug 26 11:07:37 PDT 2021


Author: Louis Dionne
Date: 2021-08-26T14:07:21-04:00
New Revision: ee44dd8062a26541808fc0d3fd5c6703e19f6016

URL: https://github.com/llvm/llvm-project/commit/ee44dd8062a26541808fc0d3fd5c6703e19f6016
DIFF: https://github.com/llvm/llvm-project/commit/ee44dd8062a26541808fc0d3fd5c6703e19f6016.diff

LOG: [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

Added: 
    libcxx/include/__ranges/range_adaptor.h
    libcxx/test/libcxx/diagnostics/detail.headers/ranges/range_adaptor.module.verify.cpp
    libcxx/test/std/ranges/range.adaptors/range.all/all.nodiscard.verify.cpp
    libcxx/test/std/ranges/range.adaptors/range.transform/adaptor.nodiscard.verify.cpp
    libcxx/test/std/ranges/range.adaptors/range.transform/adaptor.pass.cpp

Modified: 
    libcxx/include/CMakeLists.txt
    libcxx/include/__ranges/all.h
    libcxx/include/__ranges/transform_view.h
    libcxx/include/module.modulemap
    libcxx/test/std/ranges/range.adaptors/range.all/all.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.transform/types.h

Removed: 
    


################################################################################
diff  --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index a757449f79734..3189651995c0a 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -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

diff  --git a/libcxx/include/__ranges/all.h b/libcxx/include/__ranges/all.h
index f44beaa272722..144ecdfb5cf59 100644
--- 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,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)}))
     {

diff  --git a/libcxx/include/__ranges/range_adaptor.h b/libcxx/include/__ranges/range_adaptor.h
new file mode 100644
index 0000000000000..74aea3187e892
--- /dev/null
+++ b/libcxx/include/__ranges/range_adaptor.h
@@ -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

diff  --git a/libcxx/include/__ranges/transform_view.h b/libcxx/include/__ranges/transform_view.h
index 9aee8fce49584..b892e5a045cac 100644
--- 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 <concepts>
@@ -401,6 +404,30 @@ class transform_view<_View, _Fn>::__sentinel {
   }
 };
 
+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)

diff  --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap
index 98ba8d38c71a3..b6a39a0702d52 100644
--- a/libcxx/include/module.modulemap
+++ b/libcxx/include/module.modulemap
@@ -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"        }
     }
   }

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
index 0000000000000..b1bdc0d1e6874
--- /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.nodiscard.verify.cpp b/libcxx/test/std/ranges/range.adaptors/range.all/all.nodiscard.verify.cpp
new file mode 100644
index 0000000000000..f3981f95e685d
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.all/all.nodiscard.verify.cpp
@@ -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}}
+}

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
index cef873465eb65..508326dbc225d 100644
--- 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,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)>);
   }

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.transform/adaptor.nodiscard.verify.cpp b/libcxx/test/std/ranges/range.adaptors/range.transform/adaptor.nodiscard.verify.cpp
new file mode 100644
index 0000000000000..05eb468559b07
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.transform/adaptor.nodiscard.verify.cpp
@@ -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}}
+}

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
index 0000000000000..7c3b27bb5739f
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.transform/adaptor.pass.cpp
@@ -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;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.transform/types.h b/libcxx/test/std/ranges/range.adaptors/range.transform/types.h
index 159db9b4c97bf..1d535eda5db00 100644
--- 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 @@ 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; }
 };


        


More information about the libcxx-commits mailing list