On Fri, 28 Feb 2025, 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)?
> 
> > +     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?
> 
> >       [[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?

Oops, just realized I was reviewing a _removed_ line!  LGTM then aside
from the first two small comments.

> 
> > -         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
> > @@ -167,6 +178,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >      }
> >    } // namespace ranges
> >  
> > +  /// The result type of ranges::iter_move(std::declval<_Tp&>())
> >    template<__detail::__dereferenceable _Tp>
> >      requires 
> > __detail::__can_reference<ranges::__imove::_IterMove::__type<_Tp&>>
> >      using iter_rvalue_reference_t = 
> > ranges::__imove::_IterMove::__type<_Tp&>;
> > @@ -873,11 +885,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >  namespace ranges
> >  {
> >    /// @cond undocumented
> > +  // Implementation of std::ranges::iter_swap, [iterator.cust.swap].
> >    namespace __iswap
> >    {
> >      template<typename _It1, typename _It2>
> >        void iter_swap(_It1, _It2) = delete;
> >  
> > +    // Satisfied if _Tp and _Up are class or enumeration types and 
> > iter_swap
> > +    // can be found by argument-dependent lookup.
> >      template<typename _Tp, typename _Up>
> >        concept __adl_iswap
> >     = (std::__detail::__class_or_enum<remove_reference_t<_Tp>>
> > diff --git 
> > a/libstdc++-v3/testsuite/24_iterators/customization_points/iter_move.cc 
> > b/libstdc++-v3/testsuite/24_iterators/customization_points/iter_move.cc
> > index c5def5ac577..341bd5b98d7 100644
> > --- a/libstdc++-v3/testsuite/24_iterators/customization_points/iter_move.cc
> > +++ b/libstdc++-v3/testsuite/24_iterators/customization_points/iter_move.cc
> > @@ -63,8 +63,103 @@ test01()
> >    VERIFY( test_X(3, 4) );
> >  }
> >  
> > +template<typename T>
> > +using rval_ref = std::iter_rvalue_reference_t<T>;
> > +
> > +static_assert(std::same_as<rval_ref<int*>, int&&>);
> > +static_assert(std::same_as<rval_ref<const int*>, const int&&>);
> > +static_assert(std::same_as<rval_ref<std::move_iterator<int*>>, int&&>);
> > +
> > +template<typename T>
> > +concept iter_movable = requires { 
> > std::ranges::iter_move(std::declval<T>()); };
> > +
> > +struct Iter
> > +{
> > +  friend int& iter_move(Iter&) { static int i = 1; return i; }
> > +  friend long iter_move(Iter&&) { return 2; }
> > +  const short& operator*() const & { static short s = 3; return s; }
> > +  friend float operator*(const Iter&&) { return 4.0f; }
> > +};
> > +
> > +void
> > +test_adl()
> > +{
> > +  Iter it;
> > +  const Iter& cit = it;
> > +
> > +  VERIFY( std::ranges::iter_move(it) == 1 );
> > +  VERIFY( std::ranges::iter_move(std::move(it)) == 2 );
> > +  VERIFY( std::ranges::iter_move(cit) == 3 );
> > +  VERIFY( std::ranges::iter_move(std::move(cit)) == 4.0f );
> > +
> > +  // The return type should be unchanged for ADL iter_move:
> > +  static_assert(std::same_as<decltype(std::ranges::iter_move(it)), int&>);
> > +  
> > static_assert(std::same_as<decltype(std::ranges::iter_move(std::move(it))),
> > +                        long>);
> > +  // When ADL iter_move is not used, return type should be an rvalue:
> > +  static_assert(std::same_as<decltype(std::ranges::iter_move(cit)),
> > +                        const short&&>);
> > +  
> > static_assert(std::same_as<decltype(std::ranges::iter_move(std::move(cit))),
> > +                        float>);
> > +
> > +  // std::iter_rvalue_reference_t always considers the argument as lvalue.
> > +  static_assert(std::same_as<rval_ref<Iter>, int&>);
> > +  static_assert(std::same_as<rval_ref<Iter&>, int&>);
> > +  static_assert(std::same_as<rval_ref<const Iter>, const short&&>);
> > +  static_assert(std::same_as<rval_ref<const Iter&>, const short&&>);
> > +}
> > +
> > +void
> > +test_pr106612()
> > +{
> > +  // Bug 106612 ranges::iter_move does not consider iterator's value 
> > categories
> > +
> > +  struct I
> > +  {
> > +    int i{};
> > +    int& operator*() & { return i; }
> > +    int operator*() const & { return i; }
> > +    void operator*() && = delete;
> > +  };
> > +
> > +  static_assert( iter_movable<I&> );
> > +  static_assert( iter_movable<I const&> );
> > +  static_assert( ! iter_movable<I> );
> > +  static_assert( std::same_as<std::iter_rvalue_reference_t<I>, int&&> );
> > +  static_assert( std::same_as<std::iter_rvalue_reference_t<const I>, int> 
> > );
> > +
> > +  struct I2
> > +  {
> > +    int i{};
> > +    int& operator*() & { return i; }
> > +    int operator*() const & { return i; }
> > +    void operator*() &&;
> > +  };
> > +
> > +  static_assert( iter_movable<I2&> );
> > +  static_assert( iter_movable<I2 const&> );
> > +  static_assert( iter_movable<I2> );
> > +  static_assert( std::is_void_v<decltype(std::ranges::iter_move(I2{}))> );
> > +  static_assert( std::same_as<std::iter_rvalue_reference_t<I2>, int&&> );
> > +  static_assert( std::same_as<std::iter_rvalue_reference_t<I2 const>, int> 
> > );
> > +
> > +  enum E { e };
> > +  enum F { f };
> > +
> > +  struct I3
> > +  {
> > +    E operator*() const & { return e; }
> > +    F operator*() && { return f; }
> > +  };
> > +
> > +  static_assert( iter_movable<I3&> );
> > +  static_assert( iter_movable<I3> );
> > +  static_assert( std::same_as<decltype(std::ranges::iter_move(I3{})), F> );
> > +}
> > +
> >  int
> >  main()
> >  {
> >    test01();
> > +  test_adl();
> >  }
> > -- 
> > 2.48.1
> > 
> > 
> 

Reply via email to