[PATCH] libstdc++: Support range adaptors with defaultable arguments

Patrick Palka ppalka@redhat.com
Thu May 20 16:07:41 GMT 2021


This adds support for defining range adaptors with defaultable arguments.
No such range adaptors have yet been standardized, but range-v3 has a
couple, e.g. 'unique' and 'sample' (which are approximately implemented
in the added testcase), and it would be good to preemptively support
such adaptors.

In order to make 'unique | unique' (where 'unique' is an adaptor that
takes a single defaultable extra argument) unambiguously mean
composition instead of the partial application 'unique(unique)', we need
to additionally constrain the first operand in the first overload of
_RangeAdaptorClosure::operator| as per [range.adaptor.object]/1, which
says R | C is equivalent to C(R) only if R models viewable_range.
However, for our purposes checking range instead of viewable_range
suffices and is cheaper to check.

Tested on x86_64-pc-linux-gnu, does this look OK for trunk/11?  Existing
adaptors aren't affected by this change.

libstdc++-v3/ChangeLog:

	* include/std/ranges (__adaptor_partial_app_arity_ok): Define.
	(__adaptor_partial_app_viable): Use it.
	(_RangeAdaptorClosure::operator|): In the first overload, swap
	order of template parameters _Self and _Range.  Add a
	range<_Range> constraint to this overload.
	(_RangeAdaptor): Document that _S_arity can also be defined
	as a pair consisting of the minimum and maximum arity.
	* testsuite/std/ranges/adaptors/detail/user_defined.cc: New test.
---
 libstdc++-v3/include/std/ranges               | 27 +++++--
 .../ranges/adaptors/detail/user_defined.cc    | 81 +++++++++++++++++++
 2 files changed, 102 insertions(+), 6 deletions(-)
 create mode 100644 libstdc++-v3/testsuite/std/ranges/adaptors/detail/user_defined.cc

diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges
index 48100e9d7f2..8f691ee41f6 100644
--- a/libstdc++-v3/include/std/ranges
+++ b/libstdc++-v3/include/std/ranges
@@ -742,12 +742,25 @@ namespace views::__adaptor
     concept __adaptor_invocable
       = requires { std::declval<_Adaptor>()(declval<_Args>()...); };
 
+  template<typename _Adaptor>
+    constexpr bool
+    __adaptor_partial_app_arity_ok(int __nargs)
+    {
+      if constexpr (integral<decltype(_Adaptor::_S_arity)>)
+	return 1 + __nargs == _Adaptor::_S_arity;
+      else
+	{
+	  auto [__min, __max] = _Adaptor::_S_arity;
+	  return 1 + __nargs >= __min && 1 + __nargs <= __max;
+	}
+    }
+
   // True if the range adaptor non-closure _Adaptor can be partially applied
   // with _Args.
   template<typename _Adaptor, typename... _Args>
-    concept __adaptor_partial_app_viable = (_Adaptor::_S_arity > 1)
-      && (sizeof...(_Args) == _Adaptor::_S_arity - 1)
-      && (constructible_from<decay_t<_Args>, _Args> && ...);
+    concept __adaptor_partial_app_viable
+      = __adaptor_partial_app_arity_ok<_Adaptor>(sizeof...(_Args))
+	&& (constructible_from<decay_t<_Args>, _Args> && ...);
 
   template<typename _Adaptor, typename... _Args>
     struct _Partial;
@@ -759,7 +772,7 @@ namespace views::__adaptor
   struct _RangeAdaptorClosure
   {
     // range | adaptor is equivalent to adaptor(range).
-    template<typename _Self, typename _Range>
+    template<range _Range, typename _Self>
       requires derived_from<remove_cvref_t<_Self>, _RangeAdaptorClosure>
 	&& __adaptor_invocable<_Self, _Range>
       friend constexpr auto
@@ -778,8 +791,10 @@ namespace views::__adaptor
 
   // The base class of every range adaptor non-closure.
   //
-  // The static data member _Derived::_S_arity must contain the total number of
-  // arguments that the adaptor takes, and the class _Derived must introduce
+  // The static data member _Derived::_S_arity must either be an integer
+  // denoting the total arity of the adaptor, or a tuple consisting of the
+  // minimum and maximum arity of the adaptor (if e.g. the adaptor has
+  // defaultable arguments).  The class _Derived must also introduce
   // _RangeAdaptor::operator() into the class scope via a using-declaration.
   template<typename _Derived>
     struct _RangeAdaptor
diff --git a/libstdc++-v3/testsuite/std/ranges/adaptors/detail/user_defined.cc b/libstdc++-v3/testsuite/std/ranges/adaptors/detail/user_defined.cc
new file mode 100644
index 00000000000..94dabb50db8
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/ranges/adaptors/detail/user_defined.cc
@@ -0,0 +1,81 @@
+// Copyright (C) 2021 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-options "-std=gnu++2a" }
+// { dg-do compile { target c++2a } }
+
+#include <ranges>
+
+using std::views::__adaptor::_RangeAdaptor;
+using std::views::__adaptor::_RangeAdaptorClosure;
+
+// An example of a range adaptor that accepts one defaultable extra argument.
+struct _Unique : _RangeAdaptor<_Unique>, _RangeAdaptorClosure
+{
+  template<std::ranges::viewable_range _Range, typename _Pred = std::ranges::equal_to>
+    std::ranges::empty_view<int>
+    operator()(_Range&&, _Pred = {}) const;
+
+  using _RangeAdaptor<_Unique>::operator();
+  static constexpr std::pair<int, int> _S_arity = {1,2};
+};
+
+inline constexpr _Unique unique;
+
+void
+test01()
+{
+  extern int r[42];
+  auto p = std::ranges::equal_to{};
+  r | unique;
+  r | (unique | unique);
+  r | (unique(p) | unique(p));
+  r | (unique(p) | unique);
+  r | (unique | unique(p));
+  unique(r, p);
+  unique(p)(r);
+}
+
+struct default_rng { };
+
+// An example of a range adaptor that accepts two extra arguments, one of which
+// is defaultable.
+struct _Sample : _RangeAdaptor<_Sample>
+{
+  template<std::ranges::viewable_range _Range, typename _Rng = default_rng>
+    std::ranges::empty_view<int>
+    operator()(_Range&&, int, _Rng = {}) const;
+
+  using _RangeAdaptor<_Sample>::operator();
+  static constexpr std::pair<int, int> _S_arity = {2,3};
+};
+
+inline constexpr _Sample sample;
+
+void
+test02()
+{
+  extern int r[42];
+  sample(r, 5);
+  sample(r, 5, default_rng{});
+  sample(5)(r);
+  sample(5, default_rng{})(r);
+  r | sample(5);
+  r | sample(5, default_rng{});
+  r | (sample(5) | sample(5));
+  r | (sample(5, default_rng{}) | sample(5, default_rng{}));
+}
-- 
2.32.0.rc0



More information about the Libstdc++ mailing list