On Fri, 7 Mar 2025 at 15:59, Patrick Palka <ppa...@redhat.com> wrote:

> On Fri, 7 Mar 2025, Jonathan Wakely wrote:
>
> >
> >
> > On Fri, 7 Mar 2025 at 15:05, Tomasz Kamiński wrote:
> >       Add missing move_constructible && regular_invocable constrains on
> functor type,
> >       for invocations of `views::zip_transform` without range arguments.
> >
> >       libstdc++-v3/ChangeLog:
> >
> >               * include/std/ranges (_ZipTransform::operator()):
> >               Add `move_constructible` and `regular_invocable`
> constraints
> >               * testsuite/std/ranges/zip_transform/1.cc: New tests
> >       ---
> >        Tested on x86_64-linux. OK for trunk?
> >
> >
> > I think the server hook will reject the commit message. Does `git
> gcc-verify` say it's OK?
> >
> > The summary line has [PR111138] but that PR number is not repeated in
> the ChangeLog part.
> >
> > There should be "<TAB>PR libstdc++/111138" either before the
> "libstdc++-v3/ChangeLog:" line, or before the "* include/std/ranges" line.
> >
> > This policy is documented at
> > https://gcc.gnu.org/contribute.html#patches
> >
> > "If your patch relates a bug in the compiler for which there is an
> existing PR number the bug number should be stated. Use the short-form
> variant [PRnnnnn] without the Bugzilla component identifier and with
> > no space between 'PR' and the number. The body of the commit message
> should still contain the full form (PR <component>/nnnnn) within the body
> of the commit message so that Bugzilla will correctly notice the
> > commit."
> >
> >
> >        libstdc++-v3/include/std/ranges                |  3 ++-
> >        .../testsuite/std/ranges/zip_transform/1.cc    | 18
> ++++++++++++++++++
> >        2 files changed, 20 insertions(+), 1 deletion(-)
> >
> >       diff --git a/libstdc++-v3/include/std/ranges
> b/libstdc++-v3/include/std/ranges
> >       index e21f5284b46..33e9926b89f 100644
> >       --- a/libstdc++-v3/include/std/ranges
> >       +++ b/libstdc++-v3/include/std/ranges
> >       @@ -5332,7 +5332,8 @@ namespace views::__adaptor
> >            struct _ZipTransform
> >            {
> >              template<typename _Fp, typename... _Ts>
> >       -       requires (sizeof...(_Ts) == 0) ||
> __detail::__can_zip_transform_view<_Fp, _Ts...>
> >       +       requires (sizeof...(_Ts) == 0) &&
> move_constructible<decay_t<_Fp>> && regular_invocable<decay_t<_Fp>&>
> >
> >
> > I would prefer parentheses here so I don't have to think about the
> precedence.
> >
> > I think it's ((sizeof...(T) == 0) && move_cons && invoc) ||
> can_zip_xform, right?
>
> Pedantically I guess it should be
>
>   requires (sizeof...(T) == 0 && move_cons && invoc)
>     || (sizeof...(T) != 0 && can_zip_xform)
>
> We don't want/need to check can_zip_xform if we have no range arguments
> and an unsuitable functor type.
>

Ah yes.

can_zip_xform will check sizeof...(T) > 0 because that's in the
transform_view constraints, but only after checking move_constructible<F>.



>
> >
> > Is this still missing a check that decay_t<invoke_result_t<FD&>> is an
> object type?
> >
> > Maybe we want to create a helper concept which checks decay_t<_Fp>, e.g.
> add this to __detail:
> >
> > template<typename _Fd>
> >   concept __can_xform_empty // TODO: better name?
> >     = move_constructible<_Fd> && regular_invocable<_Fd&>
> >       && is_object_v<decay_t<invoke_result_t<_Fd&>>>;
> >
> > And then constrain _ZipTransform::operator() with:
> >
> >       template<typename _Fp, typename... _Ts>
> > requires (sizeof...(_Ts) == 0 &&
> __detail::__can_xform_empty<decay_t<_Fp>>)
> >  || __detail::__can_zip_transform_view<_Fp, _Ts...>
> >
> > Or ... and maybe this is heresy ... we could overload operator()
> >
> > template<typename _Fp, typename... _Ts>
> > requires __detail::__can_zip_transform_view<_Fp, _Ts...>
> > constexpr auto
> > operator() [[nodiscard]] (_Fp&& __f, _Ts&&... __ts) const
> > {
> >   return zip_transform_view(std::forward<_Fp>(__f),
> std::forward<_Ts>(__ts)...);
> > }
> >
> > template<typename _Fp>
> > requires __detail::__can_xform_empty<decay_t<_Fp>>
> > constexpr auto
> > operator() [[nodiscard]] (_Fp&& __f) const
> > {
> >   return views::empty<decay_t<invoke_result_t<decay_t<_Fp>&>>>;
> > }
> >
> > It's simpler for me to understand this way, but we would have to pay the
> cost of overload resolution. So probably not a good idea.
> >
> >
> >
> >       +         || __detail::__can_zip_transform_view<_Fp, _Ts...>
> >               constexpr auto
> >               operator() [[nodiscard]] (_Fp&& __f, _Ts&&... __ts) const
> >               {
> >       diff --git a/libstdc++-v3/testsuite/std/ranges/zip_transform/1.cc
> b/libstdc++-v3/testsuite/std/ranges/zip_transform/1.cc
> >       index 20abdcba0f8..67839261cc7 100644
> >       --- a/libstdc++-v3/testsuite/std/ranges/zip_transform/1.cc
> >       +++ b/libstdc++-v3/testsuite/std/ranges/zip_transform/1.cc
> >       @@ -9,6 +9,20 @@
> >        namespace ranges = std::ranges;
> >        namespace views = std::views;
> >
> >       +template<typename T>
> >       +concept can_zip_transform = requires (T t) {
> >       +  views::zip_transform(std::forward<T>(t));
> >       +};
> >       +
> >       +static_assert(!can_zip_transform<int>);
> >       +
> >       +struct NonMovable {
> >       +  NonMovable(NonMovable&&) = delete;
> >       +};
> >       +
> >       +static_assert(!can_zip_transform<NonMovable>);
> >       +static_assert(!can_zip_transform<NonMovable&>);
> >       +
> >        constexpr bool
> >        test01()
> >        {
> >       @@ -46,6 +60,10 @@ test01()
> >          VERIFY( ranges::size(z3) == 3 );
> >          VERIFY( ranges::equal(z3, (int[]){3, 6, 9}) );
> >
> >       +  auto z4 = views::zip_transform([] () { return 1; });
> >       +  VERIFY( ranges::size(z4) == 0 );
> >       +  static_assert(
> std::same_as<ranges::range_value_t<decltype(z4)>, int> );
> >       +
> >          return true;
> >        }
> >
> >       --
> >       2.48.1
> >
> >
> >

Reply via email to