On Fri, 28 Feb 2025 at 21:47, Patrick Palka <ppa...@redhat.com> wrote:
>
> On Fri, 28 Feb 2025, Jonathan Wakely wrote:
>
> > On 28/02/25 16:27 -0500, Patrick Palka wrote:
> > > On Thu, 27 Feb 2025, Jonathan Wakely wrote:
> > >
> > > > The specification for std::ranges::iter_move apparently requires us to
> > > > handle types which do not satisfy std::indirectly_readable, for example
> > > > with overloaded operator* which behaves differently for different value
> > > > categories.
> > > >
> > > > libstdc++-v3/ChangeLog:
> > > >
> > > >   PR libstdc++/106612
> > > >   * include/bits/iterator_concepts.h (_IterMove::__iter_ref_t):
> > > >   New alias template.
> > > >   (_IterMove::__result): Use __iter_ref_t instead of
> > > >   std::iter_reference_t.
> > > >   (_IterMove::__type): Remove incorrect __dereferenceable
> > > >   constraint.
> > > >   (_IterMove::operator()): Likewise. Add correct constraints. Use
> > > >   __iter_ref_t instead of std::iter_reference_t. Forward parameter
> > > >   as correct value category.
> > > >   (iter_swap): Add comments.
> > > >   * testsuite/24_iterators/customization_points/iter_move.cc: Test
> > > >   that iter_move is found by ADL and that rvalue arguments are
> > > >   handled correctly.
> > > > ---
> > > >
> > > > Tested x86_64-linux.
> > > >
> > > > I think the spec is silly to require this, but here we are.
> > > >
> > > >  libstdc++-v3/include/bits/iterator_concepts.h | 33 +++++--
> > > >  .../customization_points/iter_move.cc         | 95 +++++++++++++++++++
> > > >  2 files changed, 119 insertions(+), 9 deletions(-)
> > > >
> > > > diff --git a/libstdc++-v3/include/bits/iterator_concepts.h
> > > > b/libstdc++-v3/include/bits/iterator_concepts.h
> > > > index 4265c475273..555af3bdb38 100644
> > > > --- a/libstdc++-v3/include/bits/iterator_concepts.h
> > > > +++ b/libstdc++-v3/include/bits/iterator_concepts.h
> > > > @@ -103,32 +103,42 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > >    namespace ranges
> > > >    {
> > > >      /// @cond undocumented
> > > > +    // Implementation of std::ranges::iter_move, [iterator.cust.move].
> > > >      namespace __imove
> > > >      {
> > > >        void iter_move() = delete;
> > > >
> > > > +      // Satisfied if _Tp is a class or enumeration type and iter_move
> > > > +      // can be found by argument-dependent lookup.
> > > >        template<typename _Tp>
> > > >   concept __adl_imove
> > > >     = (std::__detail::__class_or_enum<remove_reference_t<_Tp>>)
> > > > -   && requires(_Tp&& __t) { iter_move(static_cast<_Tp&&>(__t)); };
> > > > +       && requires(_Tp&& __t) { iter_move(static_cast<_Tp&&>(__t)); };
> > > >
> > > >        struct _IterMove
> > > >        {
> > > >        private:
> > > > + // The type returned by dereferencing a value of type _Tp.
> > > > + // Unlike iter_reference_t this preserves the value category of _Tp.
> > > > + template<typename _Tp> requires requires { *std::declval<_Tp>(); }
> > >
> > > IIUC this requires-clause is redundant since it's checking a subset
> > > of the alias definition (and alias templates are transparent so if the
> > > decltype is invalid it'll induce an error in the immediate context)?
> >
> > Good point, I've removed the constraint.
> >
> > > > +   using __iter_ref_t = decltype(*std::declval<_Tp>());
> > > > +
> > > >   template<typename _Tp>
> > > >     struct __result
> > > > -   { using type = iter_reference_t<_Tp>; };
> > > > +   { using type = __iter_ref_t<_Tp>; };
> > > >
> > > > + // Use iter_move(E) if that works.
> > > >   template<typename _Tp>
> > > >     requires __adl_imove<_Tp>
> > > >     struct __result<_Tp>
> > > >     { using type = decltype(iter_move(std::declval<_Tp>())); };
> > > >
> > > > + // Otherwise, if *E if an lvalue, use std::move(*E).
> > > >   template<typename _Tp>
> > > >     requires (!__adl_imove<_Tp>)
> > > > -   && is_lvalue_reference_v<iter_reference_t<_Tp>>
> > > > +     && is_lvalue_reference_v<__iter_ref_t<_Tp>>
> > > >     struct __result<_Tp>
> > > > -   { using type = remove_reference_t<iter_reference_t<_Tp>>&&; };
> > > > +   { using type = remove_reference_t<__iter_ref_t<_Tp>>&&; };
> > > >
> > > >   template<typename _Tp>
> > > >     static constexpr bool
> > > > @@ -142,10 +152,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > >
> > > >        public:
> > > >   // The result type of iter_move(std::declval<_Tp>())
> > > > - template<std::__detail::__dereferenceable _Tp>
> > > > + template<typename _Tp>
> > > >     using __type = typename __result<_Tp>::type;
> > > >
> > > > - template<std::__detail::__dereferenceable _Tp>
> > > > + template<typename _Tp>
> > > > +   requires __adl_imove<_Tp> || requires { *std::declval<_Tp>(); }
> > >
> > > Maybe requires { typename __iter_ref_t<_Tp>; } instead to make it more
> > > obvious that the __iter_ref_t<_Tp> in the function body is always valid?
> >
> > Yes, that makes the constraint more obviously match what it does.
> >
> > > >     [[nodiscard]]
> > > >     constexpr __type<_Tp>
> > > >     operator()(_Tp&& __e) const
> > > > @@ -153,10 +164,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > >     {
> > > >       if constexpr (__adl_imove<_Tp>)
> > > >         return iter_move(static_cast<_Tp&&>(__e));
> > > > -     else if constexpr (is_lvalue_reference_v<iter_reference_t<_Tp>>)
> > >
> > > With the new constraints it's not clear that iter_reference_t<_Tp> is
> > > always valid here, say, if _Tp has an operator* that returns void?
> >
> > Using iter_reference_t here was a bug, which is why it's been replaced
> > with using __iter_ref_t:
> >
> > > > -       return static_cast<__type<_Tp>>(*__e);
> > > > +     else if constexpr (is_lvalue_reference_v<__iter_ref_t<_Tp>>)
> > > > +       return std::move(*static_cast<_Tp&&>(__e));
> > > >       else
> > > > -       return *__e;
> > > > +       return *static_cast<_Tp&&>(__e);
> > > >     }
> > > >        };
> > > >      } // namespace __imove
> >
> > New version attached, re-testing now...
>
> LGTM

Thanks, pushed now (and the other two you reviewed).

I'll backport all three after letting them rest on trunk for a while.

Reply via email to