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