libstdc++: Implement P3137R3 views::to_input for C++26

Message ID 20250310222713.3045246-1-ppalka@redhat.com
State Committed
Headers
Series libstdc++: Implement P3137R3 views::to_input for C++26 |

Commit Message

Patrick Palka March 10, 2025, 10:27 p.m. UTC
  Tested on x86_64-pc-linux-gnu, does this look OK for trunk?

-- >8 --

libstdc++-v3/ChangeLog:

	* include/bits/version.def (ranges_to_input): Define.
	* include/bits/version.h: Regenerate.
	* include/std/ranges (ranges::to_input_view): Define for C++26.
	(views::__detail::__can_to_input): Likewise.
	(views::_ToInput, views::to_input): Likewise.
	* testsuite/std/ranges/adaptors/to_input/1.cc: New test.
---
 libstdc++-v3/include/bits/version.def         |   8 +
 libstdc++-v3/include/bits/version.h           |  10 ++
 libstdc++-v3/include/std/ranges               | 170 ++++++++++++++++++
 .../std/ranges/adaptors/to_input/1.cc         |  58 ++++++
 4 files changed, 246 insertions(+)
 create mode 100644 libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
  

Comments

Tomasz Kaminski March 11, 2025, 7:15 a.m. UTC | #1
On Mon, Mar 10, 2025 at 11:28 PM Patrick Palka <ppalka@redhat.com> wrote:

> Tested on x86_64-pc-linux-gnu, does this look OK for trunk?
>
> -- >8 --
>
> libstdc++-v3/ChangeLog:
>
>         * include/bits/version.def (ranges_to_input): Define.
>         * include/bits/version.h: Regenerate.
>         * include/std/ranges (ranges::to_input_view): Define for C++26.
>         (views::__detail::__can_to_input): Likewise.
>         (views::_ToInput, views::to_input): Likewise.
>         * testsuite/std/ranges/adaptors/to_input/1.cc: New test.
> ---
>  libstdc++-v3/include/bits/version.def         |   8 +
>  libstdc++-v3/include/bits/version.h           |  10 ++
>  libstdc++-v3/include/std/ranges               | 170 ++++++++++++++++++
>  .../std/ranges/adaptors/to_input/1.cc         |  58 ++++++
>  4 files changed, 246 insertions(+)
>  create mode 100644
> libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
>
> diff --git a/libstdc++-v3/include/bits/version.def
> b/libstdc++-v3/include/bits/version.def
> index 2af5a54bff2..c2b5283df89 100644
> --- a/libstdc++-v3/include/bits/version.def
> +++ b/libstdc++-v3/include/bits/version.def
> @@ -1910,6 +1910,14 @@ ftms = {
>    };
>  };
>
> +ftms = {
> +  name = ranges_to_input;
> +  values = {
> +    v = 202502;
> +    cxxmin = 26;
> +  };
> +};
> +
>  ftms = {
>    name = to_string;
>    values = {
> diff --git a/libstdc++-v3/include/bits/version.h
> b/libstdc++-v3/include/bits/version.h
> index 9833023cfdc..775c8642139 100644
> --- a/libstdc++-v3/include/bits/version.h
> +++ b/libstdc++-v3/include/bits/version.h
> @@ -2120,6 +2120,16 @@
>  #endif /* !defined(__cpp_lib_text_encoding) &&
> defined(__glibcxx_want_text_encoding) */
>  #undef __glibcxx_want_text_encoding
>
> +#if !defined(__cpp_lib_ranges_to_input)
> +# if (__cplusplus >  202302L)
> +#  define __glibcxx_ranges_to_input 202502L
> +#  if defined(__glibcxx_want_all) ||
> defined(__glibcxx_want_ranges_to_input)
> +#   define __cpp_lib_ranges_to_input 202502L
> +#  endif
> +# endif
> +#endif /* !defined(__cpp_lib_ranges_to_input) &&
> defined(__glibcxx_want_ranges_to_input) */
> +#undef __glibcxx_want_ranges_to_input
> +
>  #if !defined(__cpp_lib_to_string)
>  # if (__cplusplus >  202302L) && _GLIBCXX_HOSTED && (__glibcxx_to_chars)
>  #  define __glibcxx_to_string 202306L
> diff --git a/libstdc++-v3/include/std/ranges
> b/libstdc++-v3/include/std/ranges
> index e21f5284b46..dd97d276ef0 100644
> --- a/libstdc++-v3/include/std/ranges
> +++ b/libstdc++-v3/include/std/ranges
> @@ -69,6 +69,7 @@
>  #define __glibcxx_want_ranges_slide
>  #define __glibcxx_want_ranges_stride
>  #define __glibcxx_want_ranges_to_container
> +#define __glibcxx_want_ranges_to_input
>  #define __glibcxx_want_ranges_zip
>  #include <bits/version.h>
>
> @@ -10390,6 +10391,175 @@ namespace ranges
>  } // namespace ranges
>  #endif // __cpp_lib_ranges_cache_latest
>
> +#if __cpp_lib_ranges_to_input // C++ >= 26
> +namespace ranges
> +{
> +  template<input_range _Vp>
> +    requires view<_Vp>
> +  class to_input_view : public view_interface<to_input_view<_Vp>>
> +  {
> +    _Vp _M_base = _Vp();
> +
> +    template<bool _Const>
> +    class _Iterator;
> +
> +  public:
> +    to_input_view() requires default_initializable<_Vp> = default;
> +
> +    constexpr explicit
> +    to_input_view(_Vp __base)
> +    : _M_base(std::move(__base))
> +    { }
> +
> +    constexpr _Vp
> +    base() const & requires copy_constructible<_Vp>
> +    { return _M_base; }
> +
> +    constexpr _Vp
> +    base() &&
> +    { return std::move(_M_base); }
> +
> +    constexpr auto
> +    begin() requires (!__detail::__simple_view<_Vp>)
> +    { return _Iterator<false>(ranges::begin(_M_base)); }
> +
> +    constexpr auto
> +    begin() const requires range<const _Vp>
> +    { return _Iterator<true>(ranges::begin(_M_base)); }
> +
> +    constexpr auto
> +    end() requires (!__detail::__simple_view<_Vp>)
> +    { return ranges::end(_M_base); }
> +
> +    constexpr auto
> +    end() const requires range<const _Vp>
> +    { return ranges::end(_M_base); }
> +
> +    constexpr auto
> +    size() requires sized_range<_Vp>
> +    { return ranges::size(_M_base); }
> +
> +    constexpr auto
> +    size() const requires sized_range<const _Vp>
> +    { return ranges::size(_M_base); }
> +  };
> +
> +  template<typename _Range>
> +    to_input_view(_Range&&) -> to_input_view<views::all_t<_Range>>;
> +
> +  template<input_range _Vp>
> +    requires view<_Vp>
> +  template<bool _Const>
> +  class to_input_view<_Vp>::_Iterator
> +  {
> +    using _Base = __maybe_const_t<_Const, _Vp>;
> +
> +    iterator_t<_Base> _M_current = iterator_t<_Base>();
> +
> +    constexpr explicit
> +    _Iterator(iterator_t<_Base> __current)
> +    : _M_current(std::move(__current))
> +    { }
> +
> +    friend to_input_view;
> +    friend _Iterator<!_Const>;
> +
> +  public:
> +    using difference_type = range_difference_t<_Base>;
> +    using value_type = range_value_t<_Base>;
> +    using iterator_concept = input_iterator_tag;
> +
> +    _Iterator() requires default_initializable<iterator_t<_Base>> =
> default;
> +
> +    _Iterator(_Iterator&&) = default;
> +    _Iterator& operator=(_Iterator&&) = default;
> +
> +    constexpr
> +    _Iterator(_Iterator<!_Const> __i)
> +      requires _Const && convertible_to<iterator_t<_Vp>,
> iterator_t<_Base>>
> +    : _M_current(std::move(__i._M_current))
> +    { }
> +
> +    constexpr iterator_t<_Base>
> +    base() &&
> +    { return std::move(_M_current); }
> +
> +    constexpr const iterator_t<_Base>&
> +    base() const & noexcept
> +    { return _M_current; }
> +
> +    constexpr decltype(auto)
> +    operator*() const
> +    { return *_M_current; }
> +
> +    constexpr _Iterator&
> +    operator++()
> +    {
> +      ++_M_current;
> +      return *this;
> +    }
> +
> +    constexpr void
> +    operator++(int)
> +    { ++*this; }
> +
> +    friend constexpr bool
> +    operator==(const _Iterator& __x, const sentinel_t<_Base>& __y)
> +    { return __x._M_current == __y; }
> +
> +    friend constexpr difference_type
> +    operator-(const sentinel_t<_Base>& __y, const _Iterator& __x)
> +      requires sized_sentinel_for<sentinel_t<_Base>, iterator_t<_Base>>
> +    { return __y - __x._M_current; }
> +
> +    friend constexpr difference_type
> +    operator-(const _Iterator& __x, const sentinel_t<_Base>& __y)
> +      requires sized_sentinel_for<sentinel_t<_Base>, iterator_t<_Base>>
> +    { return __x._M_current - __y; }
> +
> +    friend constexpr range_rvalue_reference_t<_Base>
> +    iter_move(const _Iterator& __i)
> +      noexcept(noexcept(ranges::iter_move(__i._M_current)))
> +    { return ranges::iter_move(__i._M_current); }
> +
> +    friend constexpr void
> +    iter_swap(const _Iterator& __x, const _Iterator& __y)
> +      noexcept(noexcept(ranges::iter_swap(__x._M_current,
> __y._M_current)))
> +      requires indirectly_swappable<iterator_t<_Base>>
> +    { ranges::iter_swap(__x._M_current, __y._M_current); }
> +  };
> +
> +  namespace views
> +  {
> +    namespace __detail
> +    {
> +      template<typename _Tp>
> +       concept __can_to_input = requires {
> to_input_view(std::declval<_Tp>()); };
> +    }
> +
> +    struct _ToInput : __adaptor::_RangeAdaptorClosure<_ToInput>
> +    {
> +      template<viewable_range _Range>
> +       requires __detail::__can_to_input<_Range>
> +       constexpr auto
> +       operator() [[nodiscard]] (_Range&& __r) const
> +       {
> +         if constexpr (input_range<_Range>
> +                       && !common_range<_Range>
> +                       && !forward_range<_Range>)
> +           return views::all(__r);
> +         else
> +           return to_input_view(std::forward<_Range>(__r));
> +       }
> +
> +      static constexpr bool _S_has_simple_call_op = true;
> +    };
> +
> +    inline constexpr _ToInput to_input;
> +  }
> +} // namespace ranges
> +#endif // __cpp_lib_ranges_to_input
> +
>  _GLIBCXX_END_NAMESPACE_VERSION
>  } // namespace std
>  #endif // library concepts
> diff --git a/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
> b/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
> new file mode 100644
> index 00000000000..1e43281adb4
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
> @@ -0,0 +1,58 @@
> +// { dg-do run { target c++26 } }
> +
> +#include <ranges>
> +
> +#if __cpp_lib_ranges_to_input != 202502L
> +# error "Feature-test macro __cpp_lib_ranges_to_input has wrong value in
> <ranges>"
> +#endif
> +
> +#include <algorithm>
> +#include <vector>
> +#include <testsuite_hooks.h>
> +#include <testsuite_iterators.h>
> +
> +namespace ranges = std::ranges;
> +namespace views = std::views;
> +
> +void
> +test01()
> +{
> +  std::vector<int> r{1,2,3};
> +  auto v = r | views::to_input;
> +  using type = decltype(v);
> +  static_assert( ranges::input_range<type> &&
> !ranges::forward_range<type> );
> +
> +  VERIFY( ranges::equal(v.base(), r) );
> +  VERIFY( v.size() == r.size() );
> +  VERIFY( v.end() == r.end() );
> +  auto it = v.begin();
> +  VERIFY( it != r.end() );
> +  *it = 42;
> +  ++it;
> +  *it = 43;
> +  it++;
> +  ranges::iter_swap(v.begin(), it);
>
As far as I can tell this is not invoking the iter_swap friend function,
and instead defaulting to swap by 3 iter_moves.
We cannot copy the iterator, but you could replace v.begin(), with
views::to_input(v).begin() to call the overload, and
have the same effect.

> +  VERIFY( ranges::equal(r, (int[]){3,43,42}) );
> +  *it = ranges::iter_move(it);
> +  VERIFY( it == r.begin() + 2 );
> +  VERIFY( r.end() - it == 1 );
> +  VERIFY( it - r.end() == -1 );
> +}
> +
> +void
> +test02()
> +{
> +  int x[] = {1,2,3};
> +  __gnu_test::test_input_range<int> rx(x);
> +  static_assert( !ranges::common_range<decltype(rx)> );
> +  auto v = rx | views::to_input;
> +  using type = decltype(v);
> +  using type = ranges::ref_view<decltype(rx)>;
>
I would prefer a more direct representation of this check, as
static_assert(std::is_same_v<decltype(views::all(rx)), decltype())>);

> +}
> +
> +int
> +main()
> +{
> +  test01();
> +  test02();
> +}
> --
> 2.49.0.rc1.37.ge969bc8759
>
>
  
Tomasz Kaminski March 11, 2025, 9:33 a.m. UTC | #2
On Mon, Mar 10, 2025 at 11:28 PM Patrick Palka <ppalka@redhat.com> wrote:

> Tested on x86_64-pc-linux-gnu, does this look OK for trunk?
>
> -- >8 --
>
> libstdc++-v3/ChangeLog:
>
>         * include/bits/version.def (ranges_to_input): Define.
>         * include/bits/version.h: Regenerate.
>         * include/std/ranges (ranges::to_input_view): Define for C++26.
>         (views::__detail::__can_to_input): Likewise.
>         (views::_ToInput, views::to_input): Likewise.
>         * testsuite/std/ranges/adaptors/to_input/1.cc: New test.
> ---
>  libstdc++-v3/include/bits/version.def         |   8 +
>  libstdc++-v3/include/bits/version.h           |  10 ++
>  libstdc++-v3/include/std/ranges               | 170 ++++++++++++++++++
>  .../std/ranges/adaptors/to_input/1.cc         |  58 ++++++
>  4 files changed, 246 insertions(+)
>  create mode 100644
> libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
>
> diff --git a/libstdc++-v3/include/bits/version.def
> b/libstdc++-v3/include/bits/version.def
> index 2af5a54bff2..c2b5283df89 100644
> --- a/libstdc++-v3/include/bits/version.def
> +++ b/libstdc++-v3/include/bits/version.def
> @@ -1910,6 +1910,14 @@ ftms = {
>    };
>  };
>
> +ftms = {
> +  name = ranges_to_input;
> +  values = {
> +    v = 202502;
> +    cxxmin = 26;
> +  };
> +};
> +
>  ftms = {
>    name = to_string;
>    values = {
> diff --git a/libstdc++-v3/include/bits/version.h
> b/libstdc++-v3/include/bits/version.h
> index 9833023cfdc..775c8642139 100644
> --- a/libstdc++-v3/include/bits/version.h
> +++ b/libstdc++-v3/include/bits/version.h
> @@ -2120,6 +2120,16 @@
>  #endif /* !defined(__cpp_lib_text_encoding) &&
> defined(__glibcxx_want_text_encoding) */
>  #undef __glibcxx_want_text_encoding
>
> +#if !defined(__cpp_lib_ranges_to_input)
> +# if (__cplusplus >  202302L)
> +#  define __glibcxx_ranges_to_input 202502L
> +#  if defined(__glibcxx_want_all) ||
> defined(__glibcxx_want_ranges_to_input)
> +#   define __cpp_lib_ranges_to_input 202502L
> +#  endif
> +# endif
> +#endif /* !defined(__cpp_lib_ranges_to_input) &&
> defined(__glibcxx_want_ranges_to_input) */
> +#undef __glibcxx_want_ranges_to_input
> +
>  #if !defined(__cpp_lib_to_string)
>  # if (__cplusplus >  202302L) && _GLIBCXX_HOSTED && (__glibcxx_to_chars)
>  #  define __glibcxx_to_string 202306L
> diff --git a/libstdc++-v3/include/std/ranges
> b/libstdc++-v3/include/std/ranges
> index e21f5284b46..dd97d276ef0 100644
> --- a/libstdc++-v3/include/std/ranges
> +++ b/libstdc++-v3/include/std/ranges
> @@ -69,6 +69,7 @@
>  #define __glibcxx_want_ranges_slide
>  #define __glibcxx_want_ranges_stride
>  #define __glibcxx_want_ranges_to_container
> +#define __glibcxx_want_ranges_to_input
>  #define __glibcxx_want_ranges_zip
>  #include <bits/version.h>
>
> @@ -10390,6 +10391,175 @@ namespace ranges
>  } // namespace ranges
>  #endif // __cpp_lib_ranges_cache_latest
>
> +#if __cpp_lib_ranges_to_input // C++ >= 26
> +namespace ranges
> +{
> +  template<input_range _Vp>
> +    requires view<_Vp>
> +  class to_input_view : public view_interface<to_input_view<_Vp>>
> +  {
> +    _Vp _M_base = _Vp();
> +
> +    template<bool _Const>
> +    class _Iterator;
> +
> +  public:
> +    to_input_view() requires default_initializable<_Vp> = default;
> +
> +    constexpr explicit
> +    to_input_view(_Vp __base)
> +    : _M_base(std::move(__base))
> +    { }
> +
> +    constexpr _Vp
> +    base() const & requires copy_constructible<_Vp>
> +    { return _M_base; }
> +
> +    constexpr _Vp
> +    base() &&
> +    { return std::move(_M_base); }
> +
> +    constexpr auto
> +    begin() requires (!__detail::__simple_view<_Vp>)
> +    { return _Iterator<false>(ranges::begin(_M_base)); }
> +
> +    constexpr auto
> +    begin() const requires range<const _Vp>
> +    { return _Iterator<true>(ranges::begin(_M_base)); }
> +
> +    constexpr auto
> +    end() requires (!__detail::__simple_view<_Vp>)
> +    { return ranges::end(_M_base); }
> +
> +    constexpr auto
> +    end() const requires range<const _Vp>
> +    { return ranges::end(_M_base); }
> +
> +    constexpr auto
> +    size() requires sized_range<_Vp>
> +    { return ranges::size(_M_base); }
> +
> +    constexpr auto
> +    size() const requires sized_range<const _Vp>
> +    { return ranges::size(_M_base); }
> +  };
> +
> +  template<typename _Range>
> +    to_input_view(_Range&&) -> to_input_view<views::all_t<_Range>>;
> +
> +  template<input_range _Vp>
> +    requires view<_Vp>
> +  template<bool _Const>
> +  class to_input_view<_Vp>::_Iterator
> +  {
> +    using _Base = __maybe_const_t<_Const, _Vp>;
> +
> +    iterator_t<_Base> _M_current = iterator_t<_Base>();
> +
> +    constexpr explicit
> +    _Iterator(iterator_t<_Base> __current)
> +    : _M_current(std::move(__current))
> +    { }
> +
> +    friend to_input_view;
> +    friend _Iterator<!_Const>;
> +
> +  public:
> +    using difference_type = range_difference_t<_Base>;
> +    using value_type = range_value_t<_Base>;
> +    using iterator_concept = input_iterator_tag;
> +
> +    _Iterator() requires default_initializable<iterator_t<_Base>> =
> default;
> +
> +    _Iterator(_Iterator&&) = default;
> +    _Iterator& operator=(_Iterator&&) = default;
> +
> +    constexpr
> +    _Iterator(_Iterator<!_Const> __i)
> +      requires _Const && convertible_to<iterator_t<_Vp>,
> iterator_t<_Base>>
> +    : _M_current(std::move(__i._M_current))
> +    { }
> +
> +    constexpr iterator_t<_Base>
> +    base() &&
> +    { return std::move(_M_current); }
> +
> +    constexpr const iterator_t<_Base>&
> +    base() const & noexcept
> +    { return _M_current; }
> +
> +    constexpr decltype(auto)
> +    operator*() const
> +    { return *_M_current; }
> +
> +    constexpr _Iterator&
> +    operator++()
> +    {
> +      ++_M_current;
> +      return *this;
> +    }
> +
> +    constexpr void
> +    operator++(int)
> +    { ++*this; }
> +
> +    friend constexpr bool
> +    operator==(const _Iterator& __x, const sentinel_t<_Base>& __y)
> +    { return __x._M_current == __y; }
> +
> +    friend constexpr difference_type
> +    operator-(const sentinel_t<_Base>& __y, const _Iterator& __x)
> +      requires sized_sentinel_for<sentinel_t<_Base>, iterator_t<_Base>>
> +    { return __y - __x._M_current; }
> +
> +    friend constexpr difference_type
> +    operator-(const _Iterator& __x, const sentinel_t<_Base>& __y)
> +      requires sized_sentinel_for<sentinel_t<_Base>, iterator_t<_Base>>
> +    { return __x._M_current - __y; }
> +
> +    friend constexpr range_rvalue_reference_t<_Base>
> +    iter_move(const _Iterator& __i)
> +      noexcept(noexcept(ranges::iter_move(__i._M_current)))
> +    { return ranges::iter_move(__i._M_current); }
> +
> +    friend constexpr void
> +    iter_swap(const _Iterator& __x, const _Iterator& __y)
> +      noexcept(noexcept(ranges::iter_swap(__x._M_current,
> __y._M_current)))
> +      requires indirectly_swappable<iterator_t<_Base>>
> +    { ranges::iter_swap(__x._M_current, __y._M_current); }
> +  };
> +
> +  namespace views
> +  {
> +    namespace __detail
> +    {
> +      template<typename _Tp>
> +       concept __can_to_input = requires {
> to_input_view(std::declval<_Tp>()); };
> +    }
> +
> +    struct _ToInput : __adaptor::_RangeAdaptorClosure<_ToInput>
> +    {
> +      template<viewable_range _Range>
> +       requires __detail::__can_to_input<_Range>
> +       constexpr auto
> +       operator() [[nodiscard]] (_Range&& __r) const
> +       {
> +         if constexpr (input_range<_Range>

This branch does not create an `to_input_view`, so we do not require it to
be well formed (__can_to_input).
The only requirement imposed by constraints on that view type is
`input_range`, so this is not observable.
Maybe it would be better to remove this repeated check?

> +                       && !common_range<_Range>
> +                       && !forward_range<_Range>)
> +           return views::all(__r);
> +         else
> +           return to_input_view(std::forward<_Range>(__r));
> +       }
> +
> +      static constexpr bool _S_has_simple_call_op = true;
> +    };
> +
> +    inline constexpr _ToInput to_input;
> +  }
> +} // namespace ranges
> +#endif // __cpp_lib_ranges_to_input
> +
>  _GLIBCXX_END_NAMESPACE_VERSION
>  } // namespace std
>  #endif // library concepts
> diff --git a/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
> b/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
> new file mode 100644
> index 00000000000..1e43281adb4
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
> @@ -0,0 +1,58 @@
> +// { dg-do run { target c++26 } }
> +
> +#include <ranges>
> +
> +#if __cpp_lib_ranges_to_input != 202502L
> +# error "Feature-test macro __cpp_lib_ranges_to_input has wrong value in
> <ranges>"
> +#endif
> +
> +#include <algorithm>
> +#include <vector>
> +#include <testsuite_hooks.h>
> +#include <testsuite_iterators.h>
> +
> +namespace ranges = std::ranges;
> +namespace views = std::views;
> +
> +void
> +test01()
> +{
> +  std::vector<int> r{1,2,3};
> +  auto v = r | views::to_input;
> +  using type = decltype(v);
> +  static_assert( ranges::input_range<type> &&
> !ranges::forward_range<type> );
> +
> +  VERIFY( ranges::equal(v.base(), r) );
> +  VERIFY( v.size() == r.size() );
> +  VERIFY( v.end() == r.end() );
> +  auto it = v.begin();
> +  VERIFY( it != r.end() );
> +  *it = 42;
> +  ++it;
> +  *it = 43;
> +  it++;
> +  ranges::iter_swap(v.begin(), it);
> +  VERIFY( ranges::equal(r, (int[]){3,43,42}) );
> +  *it = ranges::iter_move(it);
> +  VERIFY( it == r.begin() + 2 );
> +  VERIFY( r.end() - it == 1 );
> +  VERIFY( it - r.end() == -1 );
> +}
> +
> +void
> +test02()
> +{
> +  int x[] = {1,2,3};
> +  __gnu_test::test_input_range<int> rx(x);
> +  static_assert( !ranges::common_range<decltype(rx)> );
> +  auto v = rx | views::to_input;
> +  using type = decltype(v);
> +  using type = ranges::ref_view<decltype(rx)>;
> +}
> +
> +int
> +main()
> +{
> +  test01();
> +  test02();
> +}
> --
> 2.49.0.rc1.37.ge969bc8759
>
>
  
Patrick Palka March 11, 2025, 4:04 p.m. UTC | #3
On Tue, 11 Mar 2025, Tomasz Kaminski wrote:

> 
> 
> On Mon, Mar 10, 2025 at 11:28 PM Patrick Palka <ppalka@redhat.com> wrote:
>       Tested on x86_64-pc-linux-gnu, does this look OK for trunk?
> 
>       -- >8 --
> 
>       libstdc++-v3/ChangeLog:
> 
>               * include/bits/version.def (ranges_to_input): Define.
>               * include/bits/version.h: Regenerate.
>               * include/std/ranges (ranges::to_input_view): Define for C++26.
>               (views::__detail::__can_to_input): Likewise.
>               (views::_ToInput, views::to_input): Likewise.
>               * testsuite/std/ranges/adaptors/to_input/1.cc: New test.
>       ---
>        libstdc++-v3/include/bits/version.def         |   8 +
>        libstdc++-v3/include/bits/version.h           |  10 ++
>        libstdc++-v3/include/std/ranges               | 170 ++++++++++++++++++
>        .../std/ranges/adaptors/to_input/1.cc         |  58 ++++++
>        4 files changed, 246 insertions(+)
>        create mode 100644 libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
> 
>       diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
>       index 2af5a54bff2..c2b5283df89 100644
>       --- a/libstdc++-v3/include/bits/version.def
>       +++ b/libstdc++-v3/include/bits/version.def
>       @@ -1910,6 +1910,14 @@ ftms = {
>          };
>        };
> 
>       +ftms = {
>       +  name = ranges_to_input;
>       +  values = {
>       +    v = 202502;
>       +    cxxmin = 26;
>       +  };
>       +};
>       +
>        ftms = {
>          name = to_string;
>          values = {
>       diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
>       index 9833023cfdc..775c8642139 100644
>       --- a/libstdc++-v3/include/bits/version.h
>       +++ b/libstdc++-v3/include/bits/version.h
>       @@ -2120,6 +2120,16 @@
>        #endif /* !defined(__cpp_lib_text_encoding) && defined(__glibcxx_want_text_encoding) */
>        #undef __glibcxx_want_text_encoding
> 
>       +#if !defined(__cpp_lib_ranges_to_input)
>       +# if (__cplusplus >  202302L)
>       +#  define __glibcxx_ranges_to_input 202502L
>       +#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_ranges_to_input)
>       +#   define __cpp_lib_ranges_to_input 202502L
>       +#  endif
>       +# endif
>       +#endif /* !defined(__cpp_lib_ranges_to_input) && defined(__glibcxx_want_ranges_to_input) */
>       +#undef __glibcxx_want_ranges_to_input
>       +
>        #if !defined(__cpp_lib_to_string)
>        # if (__cplusplus >  202302L) && _GLIBCXX_HOSTED && (__glibcxx_to_chars)
>        #  define __glibcxx_to_string 202306L
>       diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges
>       index e21f5284b46..dd97d276ef0 100644
>       --- a/libstdc++-v3/include/std/ranges
>       +++ b/libstdc++-v3/include/std/ranges
>       @@ -69,6 +69,7 @@
>        #define __glibcxx_want_ranges_slide
>        #define __glibcxx_want_ranges_stride
>        #define __glibcxx_want_ranges_to_container
>       +#define __glibcxx_want_ranges_to_input
>        #define __glibcxx_want_ranges_zip
>        #include <bits/version.h>
> 
>       @@ -10390,6 +10391,175 @@ namespace ranges
>        } // namespace ranges
>        #endif // __cpp_lib_ranges_cache_latest
> 
>       +#if __cpp_lib_ranges_to_input // C++ >= 26
>       +namespace ranges
>       +{
>       +  template<input_range _Vp>
>       +    requires view<_Vp>
>       +  class to_input_view : public view_interface<to_input_view<_Vp>>
>       +  {
>       +    _Vp _M_base = _Vp();
>       +
>       +    template<bool _Const>
>       +    class _Iterator;
>       +
>       +  public:
>       +    to_input_view() requires default_initializable<_Vp> = default;
>       +
>       +    constexpr explicit
>       +    to_input_view(_Vp __base)
>       +    : _M_base(std::move(__base))
>       +    { }
>       +
>       +    constexpr _Vp
>       +    base() const & requires copy_constructible<_Vp>
>       +    { return _M_base; }
>       +
>       +    constexpr _Vp
>       +    base() &&
>       +    { return std::move(_M_base); }
>       +
>       +    constexpr auto
>       +    begin() requires (!__detail::__simple_view<_Vp>)
>       +    { return _Iterator<false>(ranges::begin(_M_base)); }
>       +
>       +    constexpr auto
>       +    begin() const requires range<const _Vp>
>       +    { return _Iterator<true>(ranges::begin(_M_base)); }
>       +
>       +    constexpr auto
>       +    end() requires (!__detail::__simple_view<_Vp>)
>       +    { return ranges::end(_M_base); }
>       +
>       +    constexpr auto
>       +    end() const requires range<const _Vp>
>       +    { return ranges::end(_M_base); }
>       +
>       +    constexpr auto
>       +    size() requires sized_range<_Vp>
>       +    { return ranges::size(_M_base); }
>       +
>       +    constexpr auto
>       +    size() const requires sized_range<const _Vp>
>       +    { return ranges::size(_M_base); }
>       +  };
>       +
>       +  template<typename _Range>
>       +    to_input_view(_Range&&) -> to_input_view<views::all_t<_Range>>;
>       +
>       +  template<input_range _Vp>
>       +    requires view<_Vp>
>       +  template<bool _Const>
>       +  class to_input_view<_Vp>::_Iterator
>       +  {
>       +    using _Base = __maybe_const_t<_Const, _Vp>;
>       +
>       +    iterator_t<_Base> _M_current = iterator_t<_Base>();
>       +
>       +    constexpr explicit
>       +    _Iterator(iterator_t<_Base> __current)
>       +    : _M_current(std::move(__current))
>       +    { }
>       +
>       +    friend to_input_view;
>       +    friend _Iterator<!_Const>;
>       +
>       +  public:
>       +    using difference_type = range_difference_t<_Base>;
>       +    using value_type = range_value_t<_Base>;
>       +    using iterator_concept = input_iterator_tag;
>       +
>       +    _Iterator() requires default_initializable<iterator_t<_Base>> = default;
>       +
>       +    _Iterator(_Iterator&&) = default;
>       +    _Iterator& operator=(_Iterator&&) = default;
>       +
>       +    constexpr
>       +    _Iterator(_Iterator<!_Const> __i)
>       +      requires _Const && convertible_to<iterator_t<_Vp>, iterator_t<_Base>>
>       +    : _M_current(std::move(__i._M_current))
>       +    { }
>       +
>       +    constexpr iterator_t<_Base>
>       +    base() &&
>       +    { return std::move(_M_current); }
>       +
>       +    constexpr const iterator_t<_Base>&
>       +    base() const & noexcept
>       +    { return _M_current; }
>       +
>       +    constexpr decltype(auto)
>       +    operator*() const
>       +    { return *_M_current; }
>       +
>       +    constexpr _Iterator&
>       +    operator++()
>       +    {
>       +      ++_M_current;
>       +      return *this;
>       +    }
>       +
>       +    constexpr void
>       +    operator++(int)
>       +    { ++*this; }
>       +
>       +    friend constexpr bool
>       +    operator==(const _Iterator& __x, const sentinel_t<_Base>& __y)
>       +    { return __x._M_current == __y; }
>       +
>       +    friend constexpr difference_type
>       +    operator-(const sentinel_t<_Base>& __y, const _Iterator& __x)
>       +      requires sized_sentinel_for<sentinel_t<_Base>, iterator_t<_Base>>
>       +    { return __y - __x._M_current; }
>       +
>       +    friend constexpr difference_type
>       +    operator-(const _Iterator& __x, const sentinel_t<_Base>& __y)
>       +      requires sized_sentinel_for<sentinel_t<_Base>, iterator_t<_Base>>
>       +    { return __x._M_current - __y; }
>       +
>       +    friend constexpr range_rvalue_reference_t<_Base>
>       +    iter_move(const _Iterator& __i)
>       +      noexcept(noexcept(ranges::iter_move(__i._M_current)))
>       +    { return ranges::iter_move(__i._M_current); }
>       +
>       +    friend constexpr void
>       +    iter_swap(const _Iterator& __x, const _Iterator& __y)
>       +      noexcept(noexcept(ranges::iter_swap(__x._M_current, __y._M_current)))
>       +      requires indirectly_swappable<iterator_t<_Base>>
>       +    { ranges::iter_swap(__x._M_current, __y._M_current); }
>       +  };
>       +
>       +  namespace views
>       +  {
>       +    namespace __detail
>       +    {
>       +      template<typename _Tp>
>       +       concept __can_to_input = requires { to_input_view(std::declval<_Tp>()); };
>       +    }
>       +
>       +    struct _ToInput : __adaptor::_RangeAdaptorClosure<_ToInput>
>       +    {
>       +      template<viewable_range _Range>
>       +       requires __detail::__can_to_input<_Range>
>       +       constexpr auto
>       +       operator() [[nodiscard]] (_Range&& __r) const
>       +       {
>       +         if constexpr (input_range<_Range>
>       +                       && !common_range<_Range>
>       +                       && !forward_range<_Range>)
>       +           return views::all(__r);
>       +         else
>       +           return to_input_view(std::forward<_Range>(__r));
>       +       }
>       +
>       +      static constexpr bool _S_has_simple_call_op = true;
>       +    };
>       +
>       +    inline constexpr _ToInput to_input;
>       +  }
>       +} // namespace ranges
>       +#endif // __cpp_lib_ranges_to_input
>       +
>        _GLIBCXX_END_NAMESPACE_VERSION
>        } // namespace std
>        #endif // library concepts
>       diff --git a/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc b/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
>       new file mode 100644
>       index 00000000000..1e43281adb4
>       --- /dev/null
>       +++ b/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
>       @@ -0,0 +1,58 @@
>       +// { dg-do run { target c++26 } }
>       +
>       +#include <ranges>
>       +
>       +#if __cpp_lib_ranges_to_input != 202502L
>       +# error "Feature-test macro __cpp_lib_ranges_to_input has wrong value in <ranges>"
>       +#endif
>       +
>       +#include <algorithm>
>       +#include <vector>
>       +#include <testsuite_hooks.h>
>       +#include <testsuite_iterators.h>
>       +
>       +namespace ranges = std::ranges;
>       +namespace views = std::views;
>       +
>       +void
>       +test01()
>       +{
>       +  std::vector<int> r{1,2,3};
>       +  auto v = r | views::to_input;
>       +  using type = decltype(v);
>       +  static_assert( ranges::input_range<type> && !ranges::forward_range<type> );
>       +
>       +  VERIFY( ranges::equal(v.base(), r) );
>       +  VERIFY( v.size() == r.size() );
>       +  VERIFY( v.end() == r.end() );
>       +  auto it = v.begin();
>       +  VERIFY( it != r.end() );
>       +  *it = 42;
>       +  ++it;
>       +  *it = 43;
>       +  it++;
>       +  ranges::iter_swap(v.begin(), it);
> 
> As far as I can tell this is not invoking the iter_swap friend function, and instead defaulting to swap by 3 iter_moves.
> We cannot copy the iterator, but you could replace v.begin(), with views::to_input(v).begin() to call the overload, and
> have the same effect.

v is the to_input_view, not the vector, so both arguments are
to_input_view iterators and I'd expect the friend function gets
used here?  Looks like it is, demonstrated by a static_assert(false)
added to its body:

In file included from src/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc:3:
/scratchpad/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/ranges: In instantiation of ‘constexpr void std::ranges::iter_swap(const to_input_view<ref_view<std::__debug::vector<int> > >::_Iterator<true>&, const to_input_view<ref_view<std::__debug::vector<int> > >::_Iterator<true>&) requires  indirectly_swappable<decltype(std::ranges::__access::__begin((declval<typename std::__conditional<_Const>::type<const _Vp, _Vp>&>)())), decltype(std::ranges::__access::__begin((declval<typename std::__conditional<_Const>::type<const _Vp, _Vp>&>)()))>’:
/scratchpad/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/iterator_concepts.h:949:15:   required from ‘constexpr void std::ranges::__iswap::_IterSwap::operator()(_Tp&&, _Up&&) const [with _Tp = std::ranges::to_input_view<std::ranges::ref_view<std::__debug::vector<int> > >::_Iterator<true>; _Up = std::ranges::to_input_view<std::ranges::ref_view<std::__debug::vector<int> > >::_Iterator<true>&]’
  949 |             iter_swap(static_cast<_Tp&&>(__e1), static_cast<_Up&&>(__e2));
      |             ~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc:34:20:   required from here
   34 |   ranges::iter_swap(v.begin(), it);
      |   ~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~
/scratchpad/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/ranges:10529:21: error: static assertion failed
10529 |     { static_assert(false); ranges::iter_swap(__x._M_current, __y._M_current); }
      |                     ^~~~~
/scratchpad/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/ranges:10529:21: note: ‘false’ evaluates to false

>       +  VERIFY( ranges::equal(r, (int[]){3,43,42}) );
>       +  *it = ranges::iter_move(it);
>       +  VERIFY( it == r.begin() + 2 );
>       +  VERIFY( r.end() - it == 1 );
>       +  VERIFY( it - r.end() == -1 );
>       +}
>       +
>       +void
>       +test02()
>       +{
>       +  int x[] = {1,2,3};
>       +  __gnu_test::test_input_range<int> rx(x);
>       +  static_assert( !ranges::common_range<decltype(rx)> );
>       +  auto v = rx | views::to_input;
>       +  using type = decltype(v);
>       +  using type = ranges::ref_view<decltype(rx)>;
> 
> I would prefer a more direct representation of this check, as static_assert(std::is_same_v<decltype(views::all(rx)), decltype())>);

Sounds good.  The 'using' technique doesn't add much here since the type
names are short and we don't subsequently use the alias anywhere.

>       +}
>       +
>       +int
>       +main()
>       +{
>       +  test01();
>       +  test02();
>       +}
>       --
>       2.49.0.rc1.37.ge969bc8759
> 
> 
>
  
Tomasz Kaminski March 11, 2025, 4:25 p.m. UTC | #4
On Tue, Mar 11, 2025 at 5:04 PM Patrick Palka <ppalka@redhat.com> wrote:

> On Tue, 11 Mar 2025, Tomasz Kaminski wrote:
>
> >
> >
> > On Mon, Mar 10, 2025 at 11:28 PM Patrick Palka <ppalka@redhat.com>
> wrote:
> >       Tested on x86_64-pc-linux-gnu, does this look OK for trunk?
> >
> >       -- >8 --
> >
> >       libstdc++-v3/ChangeLog:
> >
> >               * include/bits/version.def (ranges_to_input): Define.
> >               * include/bits/version.h: Regenerate.
> >               * include/std/ranges (ranges::to_input_view): Define for
> C++26.
> >               (views::__detail::__can_to_input): Likewise.
> >               (views::_ToInput, views::to_input): Likewise.
> >               * testsuite/std/ranges/adaptors/to_input/1.cc: New test.
> >       ---
> >        libstdc++-v3/include/bits/version.def         |   8 +
> >        libstdc++-v3/include/bits/version.h           |  10 ++
> >        libstdc++-v3/include/std/ranges               | 170
> ++++++++++++++++++
> >        .../std/ranges/adaptors/to_input/1.cc         |  58 ++++++
> >        4 files changed, 246 insertions(+)
> >        create mode 100644
> libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
> >
> >       diff --git a/libstdc++-v3/include/bits/version.def
> b/libstdc++-v3/include/bits/version.def
> >       index 2af5a54bff2..c2b5283df89 100644
> >       --- a/libstdc++-v3/include/bits/version.def
> >       +++ b/libstdc++-v3/include/bits/version.def
> >       @@ -1910,6 +1910,14 @@ ftms = {
> >          };
> >        };
> >
> >       +ftms = {
> >       +  name = ranges_to_input;
> >       +  values = {
> >       +    v = 202502;
> >       +    cxxmin = 26;
> >       +  };
> >       +};
> >       +
> >        ftms = {
> >          name = to_string;
> >          values = {
> >       diff --git a/libstdc++-v3/include/bits/version.h
> b/libstdc++-v3/include/bits/version.h
> >       index 9833023cfdc..775c8642139 100644
> >       --- a/libstdc++-v3/include/bits/version.h
> >       +++ b/libstdc++-v3/include/bits/version.h
> >       @@ -2120,6 +2120,16 @@
> >        #endif /* !defined(__cpp_lib_text_encoding) &&
> defined(__glibcxx_want_text_encoding) */
> >        #undef __glibcxx_want_text_encoding
> >
> >       +#if !defined(__cpp_lib_ranges_to_input)
> >       +# if (__cplusplus >  202302L)
> >       +#  define __glibcxx_ranges_to_input 202502L
> >       +#  if defined(__glibcxx_want_all) ||
> defined(__glibcxx_want_ranges_to_input)
> >       +#   define __cpp_lib_ranges_to_input 202502L
> >       +#  endif
> >       +# endif
> >       +#endif /* !defined(__cpp_lib_ranges_to_input) &&
> defined(__glibcxx_want_ranges_to_input) */
> >       +#undef __glibcxx_want_ranges_to_input
> >       +
> >        #if !defined(__cpp_lib_to_string)
> >        # if (__cplusplus >  202302L) && _GLIBCXX_HOSTED &&
> (__glibcxx_to_chars)
> >        #  define __glibcxx_to_string 202306L
> >       diff --git a/libstdc++-v3/include/std/ranges
> b/libstdc++-v3/include/std/ranges
> >       index e21f5284b46..dd97d276ef0 100644
> >       --- a/libstdc++-v3/include/std/ranges
> >       +++ b/libstdc++-v3/include/std/ranges
> >       @@ -69,6 +69,7 @@
> >        #define __glibcxx_want_ranges_slide
> >        #define __glibcxx_want_ranges_stride
> >        #define __glibcxx_want_ranges_to_container
> >       +#define __glibcxx_want_ranges_to_input
> >        #define __glibcxx_want_ranges_zip
> >        #include <bits/version.h>
> >
> >       @@ -10390,6 +10391,175 @@ namespace ranges
> >        } // namespace ranges
> >        #endif // __cpp_lib_ranges_cache_latest
> >
> >       +#if __cpp_lib_ranges_to_input // C++ >= 26
> >       +namespace ranges
> >       +{
> >       +  template<input_range _Vp>
> >       +    requires view<_Vp>
> >       +  class to_input_view : public view_interface<to_input_view<_Vp>>
> >       +  {
> >       +    _Vp _M_base = _Vp();
> >       +
> >       +    template<bool _Const>
> >       +    class _Iterator;
> >       +
> >       +  public:
> >       +    to_input_view() requires default_initializable<_Vp> = default;
> >       +
> >       +    constexpr explicit
> >       +    to_input_view(_Vp __base)
> >       +    : _M_base(std::move(__base))
> >       +    { }
> >       +
> >       +    constexpr _Vp
> >       +    base() const & requires copy_constructible<_Vp>
> >       +    { return _M_base; }
> >       +
> >       +    constexpr _Vp
> >       +    base() &&
> >       +    { return std::move(_M_base); }
> >       +
> >       +    constexpr auto
> >       +    begin() requires (!__detail::__simple_view<_Vp>)
> >       +    { return _Iterator<false>(ranges::begin(_M_base)); }
> >       +
> >       +    constexpr auto
> >       +    begin() const requires range<const _Vp>
> >       +    { return _Iterator<true>(ranges::begin(_M_base)); }
> >       +
> >       +    constexpr auto
> >       +    end() requires (!__detail::__simple_view<_Vp>)
> >       +    { return ranges::end(_M_base); }
> >       +
> >       +    constexpr auto
> >       +    end() const requires range<const _Vp>
> >       +    { return ranges::end(_M_base); }
> >       +
> >       +    constexpr auto
> >       +    size() requires sized_range<_Vp>
> >       +    { return ranges::size(_M_base); }
> >       +
> >       +    constexpr auto
> >       +    size() const requires sized_range<const _Vp>
> >       +    { return ranges::size(_M_base); }
> >       +  };
> >       +
> >       +  template<typename _Range>
> >       +    to_input_view(_Range&&) ->
> to_input_view<views::all_t<_Range>>;
> >       +
> >       +  template<input_range _Vp>
> >       +    requires view<_Vp>
> >       +  template<bool _Const>
> >       +  class to_input_view<_Vp>::_Iterator
> >       +  {
> >       +    using _Base = __maybe_const_t<_Const, _Vp>;
> >       +
> >       +    iterator_t<_Base> _M_current = iterator_t<_Base>();
> >       +
> >       +    constexpr explicit
> >       +    _Iterator(iterator_t<_Base> __current)
> >       +    : _M_current(std::move(__current))
> >       +    { }
> >       +
> >       +    friend to_input_view;
> >       +    friend _Iterator<!_Const>;
> >       +
> >       +  public:
> >       +    using difference_type = range_difference_t<_Base>;
> >       +    using value_type = range_value_t<_Base>;
> >       +    using iterator_concept = input_iterator_tag;
> >       +
> >       +    _Iterator() requires default_initializable<iterator_t<_Base>>
> = default;
> >       +
> >       +    _Iterator(_Iterator&&) = default;
> >       +    _Iterator& operator=(_Iterator&&) = default;
> >       +
> >       +    constexpr
> >       +    _Iterator(_Iterator<!_Const> __i)
> >       +      requires _Const && convertible_to<iterator_t<_Vp>,
> iterator_t<_Base>>
> >       +    : _M_current(std::move(__i._M_current))
> >       +    { }
> >       +
> >       +    constexpr iterator_t<_Base>
> >       +    base() &&
> >       +    { return std::move(_M_current); }
> >       +
> >       +    constexpr const iterator_t<_Base>&
> >       +    base() const & noexcept
> >       +    { return _M_current; }
> >       +
> >       +    constexpr decltype(auto)
> >       +    operator*() const
> >       +    { return *_M_current; }
> >       +
> >       +    constexpr _Iterator&
> >       +    operator++()
> >       +    {
> >       +      ++_M_current;
> >       +      return *this;
> >       +    }
> >       +
> >       +    constexpr void
> >       +    operator++(int)
> >       +    { ++*this; }
> >       +
> >       +    friend constexpr bool
> >       +    operator==(const _Iterator& __x, const sentinel_t<_Base>& __y)
> >       +    { return __x._M_current == __y; }
> >       +
> >       +    friend constexpr difference_type
> >       +    operator-(const sentinel_t<_Base>& __y, const _Iterator& __x)
> >       +      requires sized_sentinel_for<sentinel_t<_Base>,
> iterator_t<_Base>>
> >       +    { return __y - __x._M_current; }
> >       +
> >       +    friend constexpr difference_type
> >       +    operator-(const _Iterator& __x, const sentinel_t<_Base>& __y)
> >       +      requires sized_sentinel_for<sentinel_t<_Base>,
> iterator_t<_Base>>
> >       +    { return __x._M_current - __y; }
> >       +
> >       +    friend constexpr range_rvalue_reference_t<_Base>
> >       +    iter_move(const _Iterator& __i)
> >       +      noexcept(noexcept(ranges::iter_move(__i._M_current)))
> >       +    { return ranges::iter_move(__i._M_current); }
> >       +
> >       +    friend constexpr void
> >       +    iter_swap(const _Iterator& __x, const _Iterator& __y)
> >       +      noexcept(noexcept(ranges::iter_swap(__x._M_current,
> __y._M_current)))
> >       +      requires indirectly_swappable<iterator_t<_Base>>
> >       +    { ranges::iter_swap(__x._M_current, __y._M_current); }
> >       +  };
> >       +
> >       +  namespace views
> >       +  {
> >       +    namespace __detail
> >       +    {
> >       +      template<typename _Tp>
> >       +       concept __can_to_input = requires {
> to_input_view(std::declval<_Tp>()); };
> >       +    }
> >       +
> >       +    struct _ToInput : __adaptor::_RangeAdaptorClosure<_ToInput>
> >       +    {
> >       +      template<viewable_range _Range>
> >       +       requires __detail::__can_to_input<_Range>
> >       +       constexpr auto
> >       +       operator() [[nodiscard]] (_Range&& __r) const
> >       +       {
> >       +         if constexpr (input_range<_Range>
> >       +                       && !common_range<_Range>
> >       +                       && !forward_range<_Range>)
> >       +           return views::all(__r);
> >       +         else
> >       +           return to_input_view(std::forward<_Range>(__r));
> >       +       }
> >       +
> >       +      static constexpr bool _S_has_simple_call_op = true;
> >       +    };
> >       +
> >       +    inline constexpr _ToInput to_input;
> >       +  }
> >       +} // namespace ranges
> >       +#endif // __cpp_lib_ranges_to_input
> >       +
> >        _GLIBCXX_END_NAMESPACE_VERSION
> >        } // namespace std
> >        #endif // library concepts
> >       diff --git
> a/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
> b/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
> >       new file mode 100644
> >       index 00000000000..1e43281adb4
> >       --- /dev/null
> >       +++ b/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
> >       @@ -0,0 +1,58 @@
> >       +// { dg-do run { target c++26 } }
> >       +
> >       +#include <ranges>
> >       +
> >       +#if __cpp_lib_ranges_to_input != 202502L
> >       +# error "Feature-test macro __cpp_lib_ranges_to_input has wrong
> value in <ranges>"
> >       +#endif
> >       +
> >       +#include <algorithm>
> >       +#include <vector>
> >       +#include <testsuite_hooks.h>
> >       +#include <testsuite_iterators.h>
> >       +
> >       +namespace ranges = std::ranges;
> >       +namespace views = std::views;
> >       +
> >       +void
> >       +test01()
> >       +{
> >       +  std::vector<int> r{1,2,3};
> >       +  auto v = r | views::to_input;
> >       +  using type = decltype(v);
> >       +  static_assert( ranges::input_range<type> &&
> !ranges::forward_range<type> );
> >       +
> >       +  VERIFY( ranges::equal(v.base(), r) );
> >       +  VERIFY( v.size() == r.size() );
> >       +  VERIFY( v.end() == r.end() );
> >       +  auto it = v.begin();
> >       +  VERIFY( it != r.end() );
> >       +  *it = 42;
> >       +  ++it;
> >       +  *it = 43;
> >       +  it++;
> >       +  ranges::iter_swap(v.begin(), it);
> >
> > As far as I can tell this is not invoking the iter_swap friend function,
> and instead defaulting to swap by 3 iter_moves.
> > We cannot copy the iterator, but you could replace v.begin(), with
> views::to_input(v).begin() to call the overload, and
> > have the same effect.
>
> v is the to_input_view, not the vector, so both arguments are
> to_input_view iterators and I'd expect the friend function gets
> used here?  Looks like it is, demonstrated by a static_assert(false)
> added to its body:
>
Indeed, I got confused by the name (v being for vector), and the friend
overload is called.
We are also calling begin on input_range twice, which is not guaranteed to
work in general,
but seem to be well-defined in this case.

>
> In file included from
> src/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc:3:
> /scratchpad/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/ranges: In
> instantiation of ‘constexpr void std::ranges::iter_swap(const
> to_input_view<ref_view<std::__debug::vector<int> > >::_Iterator<true>&,
> const to_input_view<ref_view<std::__debug::vector<int> >
> >::_Iterator<true>&) requires
> indirectly_swappable<decltype(std::ranges::__access::__begin((declval<typename
> std::__conditional<_Const>::type<const _Vp, _Vp>&>)())),
> decltype(std::ranges::__access::__begin((declval<typename
> std::__conditional<_Const>::type<const _Vp, _Vp>&>)()))>’:
> /scratchpad/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/iterator_concepts.h:949:15:
>  required from ‘constexpr void
> std::ranges::__iswap::_IterSwap::operator()(_Tp&&, _Up&&) const [with _Tp =
> std::ranges::to_input_view<std::ranges::ref_view<std::__debug::vector<int>
> > >::_Iterator<true>; _Up =
> std::ranges::to_input_view<std::ranges::ref_view<std::__debug::vector<int>
> > >::_Iterator<true>&]’
>   949 |             iter_swap(static_cast<_Tp&&>(__e1),
> static_cast<_Up&&>(__e2));
>       |
>  ~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> src/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc:34:20:
>  required from here
>    34 |   ranges::iter_swap(v.begin(), it);
>       |   ~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~
> /scratchpad/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/ranges:10529:21:
> error: static assertion failed
> 10529 |     { static_assert(false); ranges::iter_swap(__x._M_current,
> __y._M_current); }
>       |                     ^~~~~
> /scratchpad/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/ranges:10529:21:
> note: ‘false’ evaluates to false
>
> >       +  VERIFY( ranges::equal(r, (int[]){3,43,42}) );
> >       +  *it = ranges::iter_move(it);
> >       +  VERIFY( it == r.begin() + 2 );
> >       +  VERIFY( r.end() - it == 1 );
> >       +  VERIFY( it - r.end() == -1 );
> >       +}
> >       +
> >       +void
> >       +test02()
> >       +{
> >       +  int x[] = {1,2,3};
> >       +  __gnu_test::test_input_range<int> rx(x);
> >       +  static_assert( !ranges::common_range<decltype(rx)> );
> >       +  auto v = rx | views::to_input;
> >       +  using type = decltype(v);
> >       +  using type = ranges::ref_view<decltype(rx)>;
> >
> > I would prefer a more direct representation of this check, as
> static_assert(std::is_same_v<decltype(views::all(rx)), decltype())>);
>
> Sounds good.  The 'using' technique doesn't add much here since the type
> names are short and we don't subsequently use the alias anywhere.
>
> >       +}
> >       +
> >       +int
> >       +main()
> >       +{
> >       +  test01();
> >       +  test02();
> >       +}
> >       --
> >       2.49.0.rc1.37.ge969bc8759
> >
> >
> >
  
Patrick Palka March 11, 2025, 4:39 p.m. UTC | #5
On Tue, 11 Mar 2025, Tomasz Kaminski wrote:

> 
> 
> On Mon, Mar 10, 2025 at 11:28 PM Patrick Palka <ppalka@redhat.com> wrote:
>       Tested on x86_64-pc-linux-gnu, does this look OK for trunk?
> 
>       -- >8 --
> 
>       libstdc++-v3/ChangeLog:
> 
>               * include/bits/version.def (ranges_to_input): Define.
>               * include/bits/version.h: Regenerate.
>               * include/std/ranges (ranges::to_input_view): Define for C++26.
>               (views::__detail::__can_to_input): Likewise.
>               (views::_ToInput, views::to_input): Likewise.
>               * testsuite/std/ranges/adaptors/to_input/1.cc: New test.
>       ---
>        libstdc++-v3/include/bits/version.def         |   8 +
>        libstdc++-v3/include/bits/version.h           |  10 ++
>        libstdc++-v3/include/std/ranges               | 170 ++++++++++++++++++
>        .../std/ranges/adaptors/to_input/1.cc         |  58 ++++++
>        4 files changed, 246 insertions(+)
>        create mode 100644 libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
> 
>       diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
>       index 2af5a54bff2..c2b5283df89 100644
>       --- a/libstdc++-v3/include/bits/version.def
>       +++ b/libstdc++-v3/include/bits/version.def
>       @@ -1910,6 +1910,14 @@ ftms = {
>          };
>        };
> 
>       +ftms = {
>       +  name = ranges_to_input;
>       +  values = {
>       +    v = 202502;
>       +    cxxmin = 26;
>       +  };
>       +};
>       +
>        ftms = {
>          name = to_string;
>          values = {
>       diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
>       index 9833023cfdc..775c8642139 100644
>       --- a/libstdc++-v3/include/bits/version.h
>       +++ b/libstdc++-v3/include/bits/version.h
>       @@ -2120,6 +2120,16 @@
>        #endif /* !defined(__cpp_lib_text_encoding) && defined(__glibcxx_want_text_encoding) */
>        #undef __glibcxx_want_text_encoding
> 
>       +#if !defined(__cpp_lib_ranges_to_input)
>       +# if (__cplusplus >  202302L)
>       +#  define __glibcxx_ranges_to_input 202502L
>       +#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_ranges_to_input)
>       +#   define __cpp_lib_ranges_to_input 202502L
>       +#  endif
>       +# endif
>       +#endif /* !defined(__cpp_lib_ranges_to_input) && defined(__glibcxx_want_ranges_to_input) */
>       +#undef __glibcxx_want_ranges_to_input
>       +
>        #if !defined(__cpp_lib_to_string)
>        # if (__cplusplus >  202302L) && _GLIBCXX_HOSTED && (__glibcxx_to_chars)
>        #  define __glibcxx_to_string 202306L
>       diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges
>       index e21f5284b46..dd97d276ef0 100644
>       --- a/libstdc++-v3/include/std/ranges
>       +++ b/libstdc++-v3/include/std/ranges
>       @@ -69,6 +69,7 @@
>        #define __glibcxx_want_ranges_slide
>        #define __glibcxx_want_ranges_stride
>        #define __glibcxx_want_ranges_to_container
>       +#define __glibcxx_want_ranges_to_input
>        #define __glibcxx_want_ranges_zip
>        #include <bits/version.h>
> 
>       @@ -10390,6 +10391,175 @@ namespace ranges
>        } // namespace ranges
>        #endif // __cpp_lib_ranges_cache_latest
> 
>       +#if __cpp_lib_ranges_to_input // C++ >= 26
>       +namespace ranges
>       +{
>       +  template<input_range _Vp>
>       +    requires view<_Vp>
>       +  class to_input_view : public view_interface<to_input_view<_Vp>>
>       +  {
>       +    _Vp _M_base = _Vp();
>       +
>       +    template<bool _Const>
>       +    class _Iterator;
>       +
>       +  public:
>       +    to_input_view() requires default_initializable<_Vp> = default;
>       +
>       +    constexpr explicit
>       +    to_input_view(_Vp __base)
>       +    : _M_base(std::move(__base))
>       +    { }
>       +
>       +    constexpr _Vp
>       +    base() const & requires copy_constructible<_Vp>
>       +    { return _M_base; }
>       +
>       +    constexpr _Vp
>       +    base() &&
>       +    { return std::move(_M_base); }
>       +
>       +    constexpr auto
>       +    begin() requires (!__detail::__simple_view<_Vp>)
>       +    { return _Iterator<false>(ranges::begin(_M_base)); }
>       +
>       +    constexpr auto
>       +    begin() const requires range<const _Vp>
>       +    { return _Iterator<true>(ranges::begin(_M_base)); }
>       +
>       +    constexpr auto
>       +    end() requires (!__detail::__simple_view<_Vp>)
>       +    { return ranges::end(_M_base); }
>       +
>       +    constexpr auto
>       +    end() const requires range<const _Vp>
>       +    { return ranges::end(_M_base); }
>       +
>       +    constexpr auto
>       +    size() requires sized_range<_Vp>
>       +    { return ranges::size(_M_base); }
>       +
>       +    constexpr auto
>       +    size() const requires sized_range<const _Vp>
>       +    { return ranges::size(_M_base); }
>       +  };
>       +
>       +  template<typename _Range>
>       +    to_input_view(_Range&&) -> to_input_view<views::all_t<_Range>>;
>       +
>       +  template<input_range _Vp>
>       +    requires view<_Vp>
>       +  template<bool _Const>
>       +  class to_input_view<_Vp>::_Iterator
>       +  {
>       +    using _Base = __maybe_const_t<_Const, _Vp>;
>       +
>       +    iterator_t<_Base> _M_current = iterator_t<_Base>();
>       +
>       +    constexpr explicit
>       +    _Iterator(iterator_t<_Base> __current)
>       +    : _M_current(std::move(__current))
>       +    { }
>       +
>       +    friend to_input_view;
>       +    friend _Iterator<!_Const>;
>       +
>       +  public:
>       +    using difference_type = range_difference_t<_Base>;
>       +    using value_type = range_value_t<_Base>;
>       +    using iterator_concept = input_iterator_tag;
>       +
>       +    _Iterator() requires default_initializable<iterator_t<_Base>> = default;
>       +
>       +    _Iterator(_Iterator&&) = default;
>       +    _Iterator& operator=(_Iterator&&) = default;
>       +
>       +    constexpr
>       +    _Iterator(_Iterator<!_Const> __i)
>       +      requires _Const && convertible_to<iterator_t<_Vp>, iterator_t<_Base>>
>       +    : _M_current(std::move(__i._M_current))
>       +    { }
>       +
>       +    constexpr iterator_t<_Base>
>       +    base() &&
>       +    { return std::move(_M_current); }
>       +
>       +    constexpr const iterator_t<_Base>&
>       +    base() const & noexcept
>       +    { return _M_current; }
>       +
>       +    constexpr decltype(auto)
>       +    operator*() const
>       +    { return *_M_current; }
>       +
>       +    constexpr _Iterator&
>       +    operator++()
>       +    {
>       +      ++_M_current;
>       +      return *this;
>       +    }
>       +
>       +    constexpr void
>       +    operator++(int)
>       +    { ++*this; }
>       +
>       +    friend constexpr bool
>       +    operator==(const _Iterator& __x, const sentinel_t<_Base>& __y)
>       +    { return __x._M_current == __y; }
>       +
>       +    friend constexpr difference_type
>       +    operator-(const sentinel_t<_Base>& __y, const _Iterator& __x)
>       +      requires sized_sentinel_for<sentinel_t<_Base>, iterator_t<_Base>>
>       +    { return __y - __x._M_current; }
>       +
>       +    friend constexpr difference_type
>       +    operator-(const _Iterator& __x, const sentinel_t<_Base>& __y)
>       +      requires sized_sentinel_for<sentinel_t<_Base>, iterator_t<_Base>>
>       +    { return __x._M_current - __y; }
>       +
>       +    friend constexpr range_rvalue_reference_t<_Base>
>       +    iter_move(const _Iterator& __i)
>       +      noexcept(noexcept(ranges::iter_move(__i._M_current)))
>       +    { return ranges::iter_move(__i._M_current); }
>       +
>       +    friend constexpr void
>       +    iter_swap(const _Iterator& __x, const _Iterator& __y)
>       +      noexcept(noexcept(ranges::iter_swap(__x._M_current, __y._M_current)))
>       +      requires indirectly_swappable<iterator_t<_Base>>
>       +    { ranges::iter_swap(__x._M_current, __y._M_current); }
>       +  };
>       +
>       +  namespace views
>       +  {
>       +    namespace __detail
>       +    {
>       +      template<typename _Tp>
>       +       concept __can_to_input = requires { to_input_view(std::declval<_Tp>()); };
>       +    }
>       +
>       +    struct _ToInput : __adaptor::_RangeAdaptorClosure<_ToInput>
>       +    {
>       +      template<viewable_range _Range>
>       +       requires __detail::__can_to_input<_Range>
>       +       constexpr auto
>       +       operator() [[nodiscard]] (_Range&& __r) const
>       +       {
>       +         if constexpr (input_range<_Range>
> 
> This branch does not create an `to_input_view`, so we do not require it to be well formed (__can_to_input).

FWIW I see this branch as a compile time shortcut for reducing template
instantiation rather than logic that extends the set of types accepted
by this adaptor, which is why it's not reflected in the constraints.

Another example of this is in views::take/drop, which contain a lot of
shortcuts to avoid returning (and instantiating) a take/drop_view and
instead reuse the underlying range type, but it's ultimately constrained
by the corresponding take/drop_view expression being well-formed.

> The only requirement imposed by constraints on that view type is `input_range`, so this is not observable.
> Maybe it would be better to remove this repeated check?

I think I prefer mirroring the wording here for sake of maintainability,
so that (in general) if the wording gets changed it's easier to see what
needs changed in the implementation.  Note the repeated check should be
very cheap compile-time wise since the front end caches constraints
aggressively.

>       +                       && !common_range<_Range>
>       +                       && !forward_range<_Range>)
>       +           return views::all(__r);
>       +         else
>       +           return to_input_view(std::forward<_Range>(__r));
>       +       }
>       +
>       +      static constexpr bool _S_has_simple_call_op = true;
>       +    };
>       +
>       +    inline constexpr _ToInput to_input;
>       +  }
>       +} // namespace ranges
>       +#endif // __cpp_lib_ranges_to_input
>       +
>        _GLIBCXX_END_NAMESPACE_VERSION
>        } // namespace std
>        #endif // library concepts
>       diff --git a/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc b/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
>       new file mode 100644
>       index 00000000000..1e43281adb4
>       --- /dev/null
>       +++ b/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
>       @@ -0,0 +1,58 @@
>       +// { dg-do run { target c++26 } }
>       +
>       +#include <ranges>
>       +
>       +#if __cpp_lib_ranges_to_input != 202502L
>       +# error "Feature-test macro __cpp_lib_ranges_to_input has wrong value in <ranges>"
>       +#endif
>       +
>       +#include <algorithm>
>       +#include <vector>
>       +#include <testsuite_hooks.h>
>       +#include <testsuite_iterators.h>
>       +
>       +namespace ranges = std::ranges;
>       +namespace views = std::views;
>       +
>       +void
>       +test01()
>       +{
>       +  std::vector<int> r{1,2,3};
>       +  auto v = r | views::to_input;
>       +  using type = decltype(v);
>       +  static_assert( ranges::input_range<type> && !ranges::forward_range<type> );
>       +
>       +  VERIFY( ranges::equal(v.base(), r) );
>       +  VERIFY( v.size() == r.size() );
>       +  VERIFY( v.end() == r.end() );
>       +  auto it = v.begin();
>       +  VERIFY( it != r.end() );
>       +  *it = 42;
>       +  ++it;
>       +  *it = 43;
>       +  it++;
>       +  ranges::iter_swap(v.begin(), it);
>       +  VERIFY( ranges::equal(r, (int[]){3,43,42}) );
>       +  *it = ranges::iter_move(it);
>       +  VERIFY( it == r.begin() + 2 );
>       +  VERIFY( r.end() - it == 1 );
>       +  VERIFY( it - r.end() == -1 );
>       +}
>       +
>       +void
>       +test02()
>       +{
>       +  int x[] = {1,2,3};
>       +  __gnu_test::test_input_range<int> rx(x);
>       +  static_assert( !ranges::common_range<decltype(rx)> );
>       +  auto v = rx | views::to_input;
>       +  using type = decltype(v);
>       +  using type = ranges::ref_view<decltype(rx)>;
>       +}
>       +
>       +int
>       +main()
>       +{
>       +  test01();
>       +  test02();
>       +}
>       --
>       2.49.0.rc1.37.ge969bc8759
> 
> 
>
  
Patrick Palka March 11, 2025, 4:54 p.m. UTC | #6
On Tue, 11 Mar 2025, Tomasz Kaminski wrote:

> 
> 
> On Tue, Mar 11, 2025 at 5:04 PM Patrick Palka <ppalka@redhat.com> wrote:
>       On Tue, 11 Mar 2025, Tomasz Kaminski wrote:
> 
>       >
>       >
>       > On Mon, Mar 10, 2025 at 11:28 PM Patrick Palka <ppalka@redhat.com> wrote:
>       >       Tested on x86_64-pc-linux-gnu, does this look OK for trunk?
>       >
>       >       -- >8 --
>       >
>       >       libstdc++-v3/ChangeLog:
>       >
>       >               * include/bits/version.def (ranges_to_input): Define.
>       >               * include/bits/version.h: Regenerate.
>       >               * include/std/ranges (ranges::to_input_view): Define for C++26.
>       >               (views::__detail::__can_to_input): Likewise.
>       >               (views::_ToInput, views::to_input): Likewise.
>       >               * testsuite/std/ranges/adaptors/to_input/1.cc: New test.
>       >       ---
>       >        libstdc++-v3/include/bits/version.def         |   8 +
>       >        libstdc++-v3/include/bits/version.h           |  10 ++
>       >        libstdc++-v3/include/std/ranges               | 170 ++++++++++++++++++
>       >        .../std/ranges/adaptors/to_input/1.cc         |  58 ++++++
>       >        4 files changed, 246 insertions(+)
>       >        create mode 100644 libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
>       >
>       >       diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
>       >       index 2af5a54bff2..c2b5283df89 100644
>       >       --- a/libstdc++-v3/include/bits/version.def
>       >       +++ b/libstdc++-v3/include/bits/version.def
>       >       @@ -1910,6 +1910,14 @@ ftms = {
>       >          };
>       >        };
>       >
>       >       +ftms = {
>       >       +  name = ranges_to_input;
>       >       +  values = {
>       >       +    v = 202502;
>       >       +    cxxmin = 26;
>       >       +  };
>       >       +};
>       >       +
>       >        ftms = {
>       >          name = to_string;
>       >          values = {
>       >       diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
>       >       index 9833023cfdc..775c8642139 100644
>       >       --- a/libstdc++-v3/include/bits/version.h
>       >       +++ b/libstdc++-v3/include/bits/version.h
>       >       @@ -2120,6 +2120,16 @@
>       >        #endif /* !defined(__cpp_lib_text_encoding) && defined(__glibcxx_want_text_encoding) */
>       >        #undef __glibcxx_want_text_encoding
>       >
>       >       +#if !defined(__cpp_lib_ranges_to_input)
>       >       +# if (__cplusplus >  202302L)
>       >       +#  define __glibcxx_ranges_to_input 202502L
>       >       +#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_ranges_to_input)
>       >       +#   define __cpp_lib_ranges_to_input 202502L
>       >       +#  endif
>       >       +# endif
>       >       +#endif /* !defined(__cpp_lib_ranges_to_input) && defined(__glibcxx_want_ranges_to_input) */
>       >       +#undef __glibcxx_want_ranges_to_input
>       >       +
>       >        #if !defined(__cpp_lib_to_string)
>       >        # if (__cplusplus >  202302L) && _GLIBCXX_HOSTED && (__glibcxx_to_chars)
>       >        #  define __glibcxx_to_string 202306L
>       >       diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges
>       >       index e21f5284b46..dd97d276ef0 100644
>       >       --- a/libstdc++-v3/include/std/ranges
>       >       +++ b/libstdc++-v3/include/std/ranges
>       >       @@ -69,6 +69,7 @@
>       >        #define __glibcxx_want_ranges_slide
>       >        #define __glibcxx_want_ranges_stride
>       >        #define __glibcxx_want_ranges_to_container
>       >       +#define __glibcxx_want_ranges_to_input
>       >        #define __glibcxx_want_ranges_zip
>       >        #include <bits/version.h>
>       >
>       >       @@ -10390,6 +10391,175 @@ namespace ranges
>       >        } // namespace ranges
>       >        #endif // __cpp_lib_ranges_cache_latest
>       >
>       >       +#if __cpp_lib_ranges_to_input // C++ >= 26
>       >       +namespace ranges
>       >       +{
>       >       +  template<input_range _Vp>
>       >       +    requires view<_Vp>
>       >       +  class to_input_view : public view_interface<to_input_view<_Vp>>
>       >       +  {
>       >       +    _Vp _M_base = _Vp();
>       >       +
>       >       +    template<bool _Const>
>       >       +    class _Iterator;
>       >       +
>       >       +  public:
>       >       +    to_input_view() requires default_initializable<_Vp> = default;
>       >       +
>       >       +    constexpr explicit
>       >       +    to_input_view(_Vp __base)
>       >       +    : _M_base(std::move(__base))
>       >       +    { }
>       >       +
>       >       +    constexpr _Vp
>       >       +    base() const & requires copy_constructible<_Vp>
>       >       +    { return _M_base; }
>       >       +
>       >       +    constexpr _Vp
>       >       +    base() &&
>       >       +    { return std::move(_M_base); }
>       >       +
>       >       +    constexpr auto
>       >       +    begin() requires (!__detail::__simple_view<_Vp>)
>       >       +    { return _Iterator<false>(ranges::begin(_M_base)); }
>       >       +
>       >       +    constexpr auto
>       >       +    begin() const requires range<const _Vp>
>       >       +    { return _Iterator<true>(ranges::begin(_M_base)); }
>       >       +
>       >       +    constexpr auto
>       >       +    end() requires (!__detail::__simple_view<_Vp>)
>       >       +    { return ranges::end(_M_base); }
>       >       +
>       >       +    constexpr auto
>       >       +    end() const requires range<const _Vp>
>       >       +    { return ranges::end(_M_base); }
>       >       +
>       >       +    constexpr auto
>       >       +    size() requires sized_range<_Vp>
>       >       +    { return ranges::size(_M_base); }
>       >       +
>       >       +    constexpr auto
>       >       +    size() const requires sized_range<const _Vp>
>       >       +    { return ranges::size(_M_base); }
>       >       +  };
>       >       +
>       >       +  template<typename _Range>
>       >       +    to_input_view(_Range&&) -> to_input_view<views::all_t<_Range>>;
>       >       +
>       >       +  template<input_range _Vp>
>       >       +    requires view<_Vp>
>       >       +  template<bool _Const>
>       >       +  class to_input_view<_Vp>::_Iterator
>       >       +  {
>       >       +    using _Base = __maybe_const_t<_Const, _Vp>;
>       >       +
>       >       +    iterator_t<_Base> _M_current = iterator_t<_Base>();
>       >       +
>       >       +    constexpr explicit
>       >       +    _Iterator(iterator_t<_Base> __current)
>       >       +    : _M_current(std::move(__current))
>       >       +    { }
>       >       +
>       >       +    friend to_input_view;
>       >       +    friend _Iterator<!_Const>;
>       >       +
>       >       +  public:
>       >       +    using difference_type = range_difference_t<_Base>;
>       >       +    using value_type = range_value_t<_Base>;
>       >       +    using iterator_concept = input_iterator_tag;
>       >       +
>       >       +    _Iterator() requires default_initializable<iterator_t<_Base>> = default;
>       >       +
>       >       +    _Iterator(_Iterator&&) = default;
>       >       +    _Iterator& operator=(_Iterator&&) = default;
>       >       +
>       >       +    constexpr
>       >       +    _Iterator(_Iterator<!_Const> __i)
>       >       +      requires _Const && convertible_to<iterator_t<_Vp>, iterator_t<_Base>>
>       >       +    : _M_current(std::move(__i._M_current))
>       >       +    { }
>       >       +
>       >       +    constexpr iterator_t<_Base>
>       >       +    base() &&
>       >       +    { return std::move(_M_current); }
>       >       +
>       >       +    constexpr const iterator_t<_Base>&
>       >       +    base() const & noexcept
>       >       +    { return _M_current; }
>       >       +
>       >       +    constexpr decltype(auto)
>       >       +    operator*() const
>       >       +    { return *_M_current; }
>       >       +
>       >       +    constexpr _Iterator&
>       >       +    operator++()
>       >       +    {
>       >       +      ++_M_current;
>       >       +      return *this;
>       >       +    }
>       >       +
>       >       +    constexpr void
>       >       +    operator++(int)
>       >       +    { ++*this; }
>       >       +
>       >       +    friend constexpr bool
>       >       +    operator==(const _Iterator& __x, const sentinel_t<_Base>& __y)
>       >       +    { return __x._M_current == __y; }
>       >       +
>       >       +    friend constexpr difference_type
>       >       +    operator-(const sentinel_t<_Base>& __y, const _Iterator& __x)
>       >       +      requires sized_sentinel_for<sentinel_t<_Base>, iterator_t<_Base>>
>       >       +    { return __y - __x._M_current; }
>       >       +
>       >       +    friend constexpr difference_type
>       >       +    operator-(const _Iterator& __x, const sentinel_t<_Base>& __y)
>       >       +      requires sized_sentinel_for<sentinel_t<_Base>, iterator_t<_Base>>
>       >       +    { return __x._M_current - __y; }
>       >       +
>       >       +    friend constexpr range_rvalue_reference_t<_Base>
>       >       +    iter_move(const _Iterator& __i)
>       >       +      noexcept(noexcept(ranges::iter_move(__i._M_current)))
>       >       +    { return ranges::iter_move(__i._M_current); }
>       >       +
>       >       +    friend constexpr void
>       >       +    iter_swap(const _Iterator& __x, const _Iterator& __y)
>       >       +      noexcept(noexcept(ranges::iter_swap(__x._M_current, __y._M_current)))
>       >       +      requires indirectly_swappable<iterator_t<_Base>>
>       >       +    { ranges::iter_swap(__x._M_current, __y._M_current); }
>       >       +  };
>       >       +
>       >       +  namespace views
>       >       +  {
>       >       +    namespace __detail
>       >       +    {
>       >       +      template<typename _Tp>
>       >       +       concept __can_to_input = requires { to_input_view(std::declval<_Tp>()); };
>       >       +    }
>       >       +
>       >       +    struct _ToInput : __adaptor::_RangeAdaptorClosure<_ToInput>
>       >       +    {
>       >       +      template<viewable_range _Range>
>       >       +       requires __detail::__can_to_input<_Range>
>       >       +       constexpr auto
>       >       +       operator() [[nodiscard]] (_Range&& __r) const
>       >       +       {
>       >       +         if constexpr (input_range<_Range>
>       >       +                       && !common_range<_Range>
>       >       +                       && !forward_range<_Range>)
>       >       +           return views::all(__r);
>       >       +         else
>       >       +           return to_input_view(std::forward<_Range>(__r));
>       >       +       }
>       >       +
>       >       +      static constexpr bool _S_has_simple_call_op = true;
>       >       +    };
>       >       +
>       >       +    inline constexpr _ToInput to_input;
>       >       +  }
>       >       +} // namespace ranges
>       >       +#endif // __cpp_lib_ranges_to_input
>       >       +
>       >        _GLIBCXX_END_NAMESPACE_VERSION
>       >        } // namespace std
>       >        #endif // library concepts
>       >       diff --git a/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc b/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
>       >       new file mode 100644
>       >       index 00000000000..1e43281adb4
>       >       --- /dev/null
>       >       +++ b/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
>       >       @@ -0,0 +1,58 @@
>       >       +// { dg-do run { target c++26 } }
>       >       +
>       >       +#include <ranges>
>       >       +
>       >       +#if __cpp_lib_ranges_to_input != 202502L
>       >       +# error "Feature-test macro __cpp_lib_ranges_to_input has wrong value in <ranges>"
>       >       +#endif
>       >       +
>       >       +#include <algorithm>
>       >       +#include <vector>
>       >       +#include <testsuite_hooks.h>
>       >       +#include <testsuite_iterators.h>
>       >       +
>       >       +namespace ranges = std::ranges;
>       >       +namespace views = std::views;
>       >       +
>       >       +void
>       >       +test01()
>       >       +{
>       >       +  std::vector<int> r{1,2,3};
>       >       +  auto v = r | views::to_input;
>       >       +  using type = decltype(v);
>       >       +  static_assert( ranges::input_range<type> && !ranges::forward_range<type> );
>       >       +
>       >       +  VERIFY( ranges::equal(v.base(), r) );
>       >       +  VERIFY( v.size() == r.size() );
>       >       +  VERIFY( v.end() == r.end() );
>       >       +  auto it = v.begin();
>       >       +  VERIFY( it != r.end() );
>       >       +  *it = 42;
>       >       +  ++it;
>       >       +  *it = 43;
>       >       +  it++;
>       >       +  ranges::iter_swap(v.begin(), it);
>       >
>       > As far as I can tell this is not invoking the iter_swap friend function, and instead defaulting to swap by 3 iter_moves.
>       > We cannot copy the iterator, but you could replace v.begin(), with views::to_input(v).begin() to call the overload, and
>       > have the same effect.
> 
>       v is the to_input_view, not the vector, so both arguments are
>       to_input_view iterators and I'd expect the friend function gets
>       used here?  Looks like it is, demonstrated by a static_assert(false)
>       added to its body:
> 
> Indeed, I got confused by the name (v being for vector), and the friend overload is called.

The range adaptor tests tend to use 'v' as the name of the adapted view
FWIW.

> We are also calling begin on input_range twice, which is not guaranteed to work in general,
> but seem to be well-defined in this case.

Hmm good point -- while the to_input_view iterator is always move-only,
it's not always single pass, the latter property is inherited from the
underlying range.

Here's an updated patch that uses std::same_as in the testcase, and
slightly extends the testcase to verify views::to_input is idempotent.
I also added a missing forward in the views::all branch of to_input.

-- >8 --

libstdc++-v3/ChangeLog:

	* include/bits/version.def (ranges_to_input): Define.
	* include/bits/version.h: Regenerate.
	* include/std/ranges (ranges::to_input_view): Define for C++26.
	(views::__detail::__can_to_input): Likewise.
	(views::_ToInput, views::to_input): Likewise.
	* testsuite/std/ranges/adaptors/to_input/1.cc: New test.
---
 libstdc++-v3/include/bits/version.def         |   8 +
 libstdc++-v3/include/bits/version.h           |  10 ++
 libstdc++-v3/include/std/ranges               | 170 ++++++++++++++++++
 .../std/ranges/adaptors/to_input/1.cc         |  59 ++++++
 4 files changed, 247 insertions(+)
 create mode 100644 libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc

diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
index 2af5a54bff2..c2b5283df89 100644
--- a/libstdc++-v3/include/bits/version.def
+++ b/libstdc++-v3/include/bits/version.def
@@ -1910,6 +1910,14 @@ ftms = {
   };
 };
 
+ftms = {
+  name = ranges_to_input;
+  values = {
+    v = 202502;
+    cxxmin = 26;
+  };
+};
+
 ftms = {
   name = to_string;
   values = {
diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
index 9833023cfdc..775c8642139 100644
--- a/libstdc++-v3/include/bits/version.h
+++ b/libstdc++-v3/include/bits/version.h
@@ -2120,6 +2120,16 @@
 #endif /* !defined(__cpp_lib_text_encoding) && defined(__glibcxx_want_text_encoding) */
 #undef __glibcxx_want_text_encoding
 
+#if !defined(__cpp_lib_ranges_to_input)
+# if (__cplusplus >  202302L)
+#  define __glibcxx_ranges_to_input 202502L
+#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_ranges_to_input)
+#   define __cpp_lib_ranges_to_input 202502L
+#  endif
+# endif
+#endif /* !defined(__cpp_lib_ranges_to_input) && defined(__glibcxx_want_ranges_to_input) */
+#undef __glibcxx_want_ranges_to_input
+
 #if !defined(__cpp_lib_to_string)
 # if (__cplusplus >  202302L) && _GLIBCXX_HOSTED && (__glibcxx_to_chars)
 #  define __glibcxx_to_string 202306L
diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges
index e21f5284b46..c2a2d6f4e05 100644
--- a/libstdc++-v3/include/std/ranges
+++ b/libstdc++-v3/include/std/ranges
@@ -69,6 +69,7 @@
 #define __glibcxx_want_ranges_slide
 #define __glibcxx_want_ranges_stride
 #define __glibcxx_want_ranges_to_container
+#define __glibcxx_want_ranges_to_input
 #define __glibcxx_want_ranges_zip
 #include <bits/version.h>
 
@@ -10390,6 +10391,175 @@ namespace ranges
 } // namespace ranges
 #endif // __cpp_lib_ranges_cache_latest
 
+#if __cpp_lib_ranges_to_input // C++ >= 26
+namespace ranges
+{
+  template<input_range _Vp>
+    requires view<_Vp>
+  class to_input_view : public view_interface<to_input_view<_Vp>>
+  {
+    _Vp _M_base = _Vp();
+
+    template<bool _Const>
+    class _Iterator;
+
+  public:
+    to_input_view() requires default_initializable<_Vp> = default;
+
+    constexpr explicit
+    to_input_view(_Vp __base)
+    : _M_base(std::move(__base))
+    { }
+
+    constexpr _Vp
+    base() const & requires copy_constructible<_Vp>
+    { return _M_base; }
+
+    constexpr _Vp
+    base() &&
+    { return std::move(_M_base); }
+
+    constexpr auto
+    begin() requires (!__detail::__simple_view<_Vp>)
+    { return _Iterator<false>(ranges::begin(_M_base)); }
+
+    constexpr auto
+    begin() const requires range<const _Vp>
+    { return _Iterator<true>(ranges::begin(_M_base)); }
+
+    constexpr auto
+    end() requires (!__detail::__simple_view<_Vp>)
+    { return ranges::end(_M_base); }
+
+    constexpr auto
+    end() const requires range<const _Vp>
+    { return ranges::end(_M_base); }
+
+    constexpr auto
+    size() requires sized_range<_Vp>
+    { return ranges::size(_M_base); }
+
+    constexpr auto
+    size() const requires sized_range<const _Vp>
+    { return ranges::size(_M_base); }
+  };
+
+  template<typename _Range>
+    to_input_view(_Range&&) -> to_input_view<views::all_t<_Range>>;
+
+  template<input_range _Vp>
+    requires view<_Vp>
+  template<bool _Const>
+  class to_input_view<_Vp>::_Iterator
+  {
+    using _Base = __maybe_const_t<_Const, _Vp>;
+
+    iterator_t<_Base> _M_current = iterator_t<_Base>();
+
+    constexpr explicit
+    _Iterator(iterator_t<_Base> __current)
+    : _M_current(std::move(__current))
+    { }
+
+    friend to_input_view;
+    friend _Iterator<!_Const>;
+
+  public:
+    using difference_type = range_difference_t<_Base>;
+    using value_type = range_value_t<_Base>;
+    using iterator_concept = input_iterator_tag;
+
+    _Iterator() requires default_initializable<iterator_t<_Base>> = default;
+
+    _Iterator(_Iterator&&) = default;
+    _Iterator& operator=(_Iterator&&) = default;
+
+    constexpr
+    _Iterator(_Iterator<!_Const> __i)
+      requires _Const && convertible_to<iterator_t<_Vp>, iterator_t<_Base>>
+    : _M_current(std::move(__i._M_current))
+    { }
+
+    constexpr iterator_t<_Base>
+    base() &&
+    { return std::move(_M_current); }
+
+    constexpr const iterator_t<_Base>&
+    base() const & noexcept
+    { return _M_current; }
+
+    constexpr decltype(auto)
+    operator*() const
+    { return *_M_current; }
+
+    constexpr _Iterator&
+    operator++()
+    {
+      ++_M_current;
+      return *this;
+    }
+
+    constexpr void
+    operator++(int)
+    { ++*this; }
+
+    friend constexpr bool
+    operator==(const _Iterator& __x, const sentinel_t<_Base>& __y)
+    { return __x._M_current == __y; }
+
+    friend constexpr difference_type
+    operator-(const sentinel_t<_Base>& __y, const _Iterator& __x)
+      requires sized_sentinel_for<sentinel_t<_Base>, iterator_t<_Base>>
+    { return __y - __x._M_current; }
+
+    friend constexpr difference_type
+    operator-(const _Iterator& __x, const sentinel_t<_Base>& __y)
+      requires sized_sentinel_for<sentinel_t<_Base>, iterator_t<_Base>>
+    { return __x._M_current - __y; }
+
+    friend constexpr range_rvalue_reference_t<_Base>
+    iter_move(const _Iterator& __i)
+      noexcept(noexcept(ranges::iter_move(__i._M_current)))
+    { return ranges::iter_move(__i._M_current); }
+
+    friend constexpr void
+    iter_swap(const _Iterator& __x, const _Iterator& __y)
+      noexcept(noexcept(ranges::iter_swap(__x._M_current, __y._M_current)))
+      requires indirectly_swappable<iterator_t<_Base>>
+    { ranges::iter_swap(__x._M_current, __y._M_current); }
+  };
+
+  namespace views
+  {
+    namespace __detail
+    {
+      template<typename _Tp>
+	concept __can_to_input = requires { to_input_view(std::declval<_Tp>()); };
+    }
+
+    struct _ToInput : __adaptor::_RangeAdaptorClosure<_ToInput>
+    {
+      template<viewable_range _Range>
+	requires __detail::__can_to_input<_Range>
+	constexpr auto
+	operator() [[nodiscard]] (_Range&& __r) const
+	{
+	  if constexpr (input_range<_Range>
+			&& !common_range<_Range>
+			&& !forward_range<_Range>)
+	    return views::all(std::forward<_Range>(__r));
+	  else
+	    return to_input_view(std::forward<_Range>(__r));
+	}
+
+      static constexpr bool _S_has_simple_call_op = true;
+    };
+
+    inline constexpr _ToInput to_input;
+  }
+} // namespace ranges
+#endif // __cpp_lib_ranges_to_input
+
 _GLIBCXX_END_NAMESPACE_VERSION
 } // namespace std
 #endif // library concepts
diff --git a/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc b/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
new file mode 100644
index 00000000000..cde368a2f64
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
@@ -0,0 +1,59 @@
+// { dg-do run { target c++26 } }
+
+#include <ranges>
+
+#if __cpp_lib_ranges_to_input != 202502L
+# error "Feature-test macro __cpp_lib_ranges_to_input has wrong value in <ranges>"
+#endif
+
+#include <algorithm>
+#include <vector>
+#include <testsuite_hooks.h>
+#include <testsuite_iterators.h>
+
+namespace ranges = std::ranges;
+namespace views = std::views;
+
+void
+test01()
+{
+  std::vector<int> r{1,2,3};
+  auto v = r | views::to_input;
+  using type = decltype(v);
+  static_assert( ranges::input_range<type> && !ranges::forward_range<type> );
+
+  VERIFY( ranges::equal(v.base(), r) );
+  VERIFY( v.size() == r.size() );
+  VERIFY( v.end() == r.end() );
+  auto it = v.begin();
+  VERIFY( it != r.end() );
+  *it = 42;
+  ++it;
+  *it = 43;
+  it++;
+  ranges::iter_swap(v.begin(), it);
+  VERIFY( ranges::equal(r, (int[]){3,43,42}) );
+  *it = ranges::iter_move(it);
+  VERIFY( it == r.begin() + 2 );
+  VERIFY( r.end() - it == 1 );
+  VERIFY( it - r.end() == -1 );
+}
+
+void
+test02()
+{
+  int x[] = {1,2,3};
+  __gnu_test::test_input_range<int> rx(x);
+  static_assert( !ranges::common_range<decltype(rx)> );
+  auto v = rx | views::to_input;
+  static_assert( std::same_as<decltype(v), decltype(views::all(rx))> );
+  static_assert( std::same_as<decltype(x | views::to_input),
+			      decltype(x | views::to_input | views::to_input)> );
+}
+
+int
+main()
+{
+  test01();
+  test02();
+}
  
Tomasz Kaminski March 11, 2025, 5:03 p.m. UTC | #7
On Tue, Mar 11, 2025 at 5:54 PM Patrick Palka <ppalka@redhat.com> wrote:

> On Tue, 11 Mar 2025, Tomasz Kaminski wrote:
>
> >
> >
> > On Tue, Mar 11, 2025 at 5:04 PM Patrick Palka <ppalka@redhat.com> wrote:
> >       On Tue, 11 Mar 2025, Tomasz Kaminski wrote:
> >
> >       >
> >       >
> >       > On Mon, Mar 10, 2025 at 11:28 PM Patrick Palka <
> ppalka@redhat.com> wrote:
> >       >       Tested on x86_64-pc-linux-gnu, does this look OK for trunk?
> >       >
> >       >       -- >8 --
> >       >
> >       >       libstdc++-v3/ChangeLog:
> >       >
> >       >               * include/bits/version.def (ranges_to_input):
> Define.
> >       >               * include/bits/version.h: Regenerate.
> >       >               * include/std/ranges (ranges::to_input_view):
> Define for C++26.
> >       >               (views::__detail::__can_to_input): Likewise.
> >       >               (views::_ToInput, views::to_input): Likewise.
> >       >               * testsuite/std/ranges/adaptors/to_input/1.cc: New
> test.
> >       >       ---
> >       >        libstdc++-v3/include/bits/version.def         |   8 +
> >       >        libstdc++-v3/include/bits/version.h           |  10 ++
> >       >        libstdc++-v3/include/std/ranges               | 170
> ++++++++++++++++++
> >       >        .../std/ranges/adaptors/to_input/1.cc         |  58 ++++++
> >       >        4 files changed, 246 insertions(+)
> >       >        create mode 100644
> libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
> >       >
> >       >       diff --git a/libstdc++-v3/include/bits/version.def
> b/libstdc++-v3/include/bits/version.def
> >       >       index 2af5a54bff2..c2b5283df89 100644
> >       >       --- a/libstdc++-v3/include/bits/version.def
> >       >       +++ b/libstdc++-v3/include/bits/version.def
> >       >       @@ -1910,6 +1910,14 @@ ftms = {
> >       >          };
> >       >        };
> >       >
> >       >       +ftms = {
> >       >       +  name = ranges_to_input;
> >       >       +  values = {
> >       >       +    v = 202502;
> >       >       +    cxxmin = 26;
> >       >       +  };
> >       >       +};
> >       >       +
> >       >        ftms = {
> >       >          name = to_string;
> >       >          values = {
> >       >       diff --git a/libstdc++-v3/include/bits/version.h
> b/libstdc++-v3/include/bits/version.h
> >       >       index 9833023cfdc..775c8642139 100644
> >       >       --- a/libstdc++-v3/include/bits/version.h
> >       >       +++ b/libstdc++-v3/include/bits/version.h
> >       >       @@ -2120,6 +2120,16 @@
> >       >        #endif /* !defined(__cpp_lib_text_encoding) &&
> defined(__glibcxx_want_text_encoding) */
> >       >        #undef __glibcxx_want_text_encoding
> >       >
> >       >       +#if !defined(__cpp_lib_ranges_to_input)
> >       >       +# if (__cplusplus >  202302L)
> >       >       +#  define __glibcxx_ranges_to_input 202502L
> >       >       +#  if defined(__glibcxx_want_all) ||
> defined(__glibcxx_want_ranges_to_input)
> >       >       +#   define __cpp_lib_ranges_to_input 202502L
> >       >       +#  endif
> >       >       +# endif
> >       >       +#endif /* !defined(__cpp_lib_ranges_to_input) &&
> defined(__glibcxx_want_ranges_to_input) */
> >       >       +#undef __glibcxx_want_ranges_to_input
> >       >       +
> >       >        #if !defined(__cpp_lib_to_string)
> >       >        # if (__cplusplus >  202302L) && _GLIBCXX_HOSTED &&
> (__glibcxx_to_chars)
> >       >        #  define __glibcxx_to_string 202306L
> >       >       diff --git a/libstdc++-v3/include/std/ranges
> b/libstdc++-v3/include/std/ranges
> >       >       index e21f5284b46..dd97d276ef0 100644
> >       >       --- a/libstdc++-v3/include/std/ranges
> >       >       +++ b/libstdc++-v3/include/std/ranges
> >       >       @@ -69,6 +69,7 @@
> >       >        #define __glibcxx_want_ranges_slide
> >       >        #define __glibcxx_want_ranges_stride
> >       >        #define __glibcxx_want_ranges_to_container
> >       >       +#define __glibcxx_want_ranges_to_input
> >       >        #define __glibcxx_want_ranges_zip
> >       >        #include <bits/version.h>
> >       >
> >       >       @@ -10390,6 +10391,175 @@ namespace ranges
> >       >        } // namespace ranges
> >       >        #endif // __cpp_lib_ranges_cache_latest
> >       >
> >       >       +#if __cpp_lib_ranges_to_input // C++ >= 26
> >       >       +namespace ranges
> >       >       +{
> >       >       +  template<input_range _Vp>
> >       >       +    requires view<_Vp>
> >       >       +  class to_input_view : public
> view_interface<to_input_view<_Vp>>
> >       >       +  {
> >       >       +    _Vp _M_base = _Vp();
> >       >       +
> >       >       +    template<bool _Const>
> >       >       +    class _Iterator;
> >       >       +
> >       >       +  public:
> >       >       +    to_input_view() requires default_initializable<_Vp> =
> default;
> >       >       +
> >       >       +    constexpr explicit
> >       >       +    to_input_view(_Vp __base)
> >       >       +    : _M_base(std::move(__base))
> >       >       +    { }
> >       >       +
> >       >       +    constexpr _Vp
> >       >       +    base() const & requires copy_constructible<_Vp>
> >       >       +    { return _M_base; }
> >       >       +
> >       >       +    constexpr _Vp
> >       >       +    base() &&
> >       >       +    { return std::move(_M_base); }
> >       >       +
> >       >       +    constexpr auto
> >       >       +    begin() requires (!__detail::__simple_view<_Vp>)
> >       >       +    { return _Iterator<false>(ranges::begin(_M_base)); }
> >       >       +
> >       >       +    constexpr auto
> >       >       +    begin() const requires range<const _Vp>
> >       >       +    { return _Iterator<true>(ranges::begin(_M_base)); }
> >       >       +
> >       >       +    constexpr auto
> >       >       +    end() requires (!__detail::__simple_view<_Vp>)
> >       >       +    { return ranges::end(_M_base); }
> >       >       +
> >       >       +    constexpr auto
> >       >       +    end() const requires range<const _Vp>
> >       >       +    { return ranges::end(_M_base); }
> >       >       +
> >       >       +    constexpr auto
> >       >       +    size() requires sized_range<_Vp>
> >       >       +    { return ranges::size(_M_base); }
> >       >       +
> >       >       +    constexpr auto
> >       >       +    size() const requires sized_range<const _Vp>
> >       >       +    { return ranges::size(_M_base); }
> >       >       +  };
> >       >       +
> >       >       +  template<typename _Range>
> >       >       +    to_input_view(_Range&&) ->
> to_input_view<views::all_t<_Range>>;
> >       >       +
> >       >       +  template<input_range _Vp>
> >       >       +    requires view<_Vp>
> >       >       +  template<bool _Const>
> >       >       +  class to_input_view<_Vp>::_Iterator
> >       >       +  {
> >       >       +    using _Base = __maybe_const_t<_Const, _Vp>;
> >       >       +
> >       >       +    iterator_t<_Base> _M_current = iterator_t<_Base>();
> >       >       +
> >       >       +    constexpr explicit
> >       >       +    _Iterator(iterator_t<_Base> __current)
> >       >       +    : _M_current(std::move(__current))
> >       >       +    { }
> >       >       +
> >       >       +    friend to_input_view;
> >       >       +    friend _Iterator<!_Const>;
> >       >       +
> >       >       +  public:
> >       >       +    using difference_type = range_difference_t<_Base>;
> >       >       +    using value_type = range_value_t<_Base>;
> >       >       +    using iterator_concept = input_iterator_tag;
> >       >       +
> >       >       +    _Iterator() requires
> default_initializable<iterator_t<_Base>> = default;
> >       >       +
> >       >       +    _Iterator(_Iterator&&) = default;
> >       >       +    _Iterator& operator=(_Iterator&&) = default;
> >       >       +
> >       >       +    constexpr
> >       >       +    _Iterator(_Iterator<!_Const> __i)
> >       >       +      requires _Const && convertible_to<iterator_t<_Vp>,
> iterator_t<_Base>>
> >       >       +    : _M_current(std::move(__i._M_current))
> >       >       +    { }
> >       >       +
> >       >       +    constexpr iterator_t<_Base>
> >       >       +    base() &&
> >       >       +    { return std::move(_M_current); }
> >       >       +
> >       >       +    constexpr const iterator_t<_Base>&
> >       >       +    base() const & noexcept
> >       >       +    { return _M_current; }
> >       >       +
> >       >       +    constexpr decltype(auto)
> >       >       +    operator*() const
> >       >       +    { return *_M_current; }
> >       >       +
> >       >       +    constexpr _Iterator&
> >       >       +    operator++()
> >       >       +    {
> >       >       +      ++_M_current;
> >       >       +      return *this;
> >       >       +    }
> >       >       +
> >       >       +    constexpr void
> >       >       +    operator++(int)
> >       >       +    { ++*this; }
> >       >       +
> >       >       +    friend constexpr bool
> >       >       +    operator==(const _Iterator& __x, const
> sentinel_t<_Base>& __y)
> >       >       +    { return __x._M_current == __y; }
> >       >       +
> >       >       +    friend constexpr difference_type
> >       >       +    operator-(const sentinel_t<_Base>& __y, const
> _Iterator& __x)
> >       >       +      requires sized_sentinel_for<sentinel_t<_Base>,
> iterator_t<_Base>>
> >       >       +    { return __y - __x._M_current; }
> >       >       +
> >       >       +    friend constexpr difference_type
> >       >       +    operator-(const _Iterator& __x, const
> sentinel_t<_Base>& __y)
> >       >       +      requires sized_sentinel_for<sentinel_t<_Base>,
> iterator_t<_Base>>
> >       >       +    { return __x._M_current - __y; }
> >       >       +
> >       >       +    friend constexpr range_rvalue_reference_t<_Base>
> >       >       +    iter_move(const _Iterator& __i)
> >       >       +
> noexcept(noexcept(ranges::iter_move(__i._M_current)))
> >       >       +    { return ranges::iter_move(__i._M_current); }
> >       >       +
> >       >       +    friend constexpr void
> >       >       +    iter_swap(const _Iterator& __x, const _Iterator& __y)
> >       >       +      noexcept(noexcept(ranges::iter_swap(__x._M_current,
> __y._M_current)))
> >       >       +      requires indirectly_swappable<iterator_t<_Base>>
> >       >       +    { ranges::iter_swap(__x._M_current, __y._M_current); }
> >       >       +  };
> >       >       +
> >       >       +  namespace views
> >       >       +  {
> >       >       +    namespace __detail
> >       >       +    {
> >       >       +      template<typename _Tp>
> >       >       +       concept __can_to_input = requires {
> to_input_view(std::declval<_Tp>()); };
> >       >       +    }
> >       >       +
> >       >       +    struct _ToInput :
> __adaptor::_RangeAdaptorClosure<_ToInput>
> >       >       +    {
> >       >       +      template<viewable_range _Range>
> >       >       +       requires __detail::__can_to_input<_Range>
> >       >       +       constexpr auto
> >       >       +       operator() [[nodiscard]] (_Range&& __r) const
> >       >       +       {
> >       >       +         if constexpr (input_range<_Range>
> >       >       +                       && !common_range<_Range>
> >       >       +                       && !forward_range<_Range>)
> >       >       +           return views::all(__r);
> >       >       +         else
> >       >       +           return
> to_input_view(std::forward<_Range>(__r));
> >       >       +       }
> >       >       +
> >       >       +      static constexpr bool _S_has_simple_call_op = true;
> >       >       +    };
> >       >       +
> >       >       +    inline constexpr _ToInput to_input;
> >       >       +  }
> >       >       +} // namespace ranges
> >       >       +#endif // __cpp_lib_ranges_to_input
> >       >       +
> >       >        _GLIBCXX_END_NAMESPACE_VERSION
> >       >        } // namespace std
> >       >        #endif // library concepts
> >       >       diff --git
> a/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
> b/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
> >       >       new file mode 100644
> >       >       index 00000000000..1e43281adb4
> >       >       --- /dev/null
> >       >       +++
> b/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
> >       >       @@ -0,0 +1,58 @@
> >       >       +// { dg-do run { target c++26 } }
> >       >       +
> >       >       +#include <ranges>
> >       >       +
> >       >       +#if __cpp_lib_ranges_to_input != 202502L
> >       >       +# error "Feature-test macro __cpp_lib_ranges_to_input has
> wrong value in <ranges>"
> >       >       +#endif
> >       >       +
> >       >       +#include <algorithm>
> >       >       +#include <vector>
> >       >       +#include <testsuite_hooks.h>
> >       >       +#include <testsuite_iterators.h>
> >       >       +
> >       >       +namespace ranges = std::ranges;
> >       >       +namespace views = std::views;
> >       >       +
> >       >       +void
> >       >       +test01()
> >       >       +{
> >       >       +  std::vector<int> r{1,2,3};
> >       >       +  auto v = r | views::to_input;
> >       >       +  using type = decltype(v);
> >       >       +  static_assert( ranges::input_range<type> &&
> !ranges::forward_range<type> );
> >       >       +
> >       >       +  VERIFY( ranges::equal(v.base(), r) );
> >       >       +  VERIFY( v.size() == r.size() );
> >       >       +  VERIFY( v.end() == r.end() );
> >       >       +  auto it = v.begin();
> >       >       +  VERIFY( it != r.end() );
> >       >       +  *it = 42;
> >       >       +  ++it;
> >       >       +  *it = 43;
> >       >       +  it++;
> >       >       +  ranges::iter_swap(v.begin(), it);
> >       >
> >       > As far as I can tell this is not invoking the iter_swap friend
> function, and instead defaulting to swap by 3 iter_moves.
> >       > We cannot copy the iterator, but you could replace v.begin(),
> with views::to_input(v).begin() to call the overload, and
> >       > have the same effect.
> >
> >       v is the to_input_view, not the vector, so both arguments are
> >       to_input_view iterators and I'd expect the friend function gets
> >       used here?  Looks like it is, demonstrated by a
> static_assert(false)
> >       added to its body:
> >
> > Indeed, I got confused by the name (v being for vector), and the friend
> overload is called.
>
> The range adaptor tests tend to use 'v' as the name of the adapted view
> FWIW.
>
> > We are also calling begin on input_range twice, which is not guaranteed
> to work in general,
> > but seem to be well-defined in this case.
>
> Hmm good point -- while the to_input_view iterator is always move-only,
> it's not always single pass, the latter property is inherited from the
> underlying range.
>
> Here's an updated patch that uses std::same_as in the testcase, and
> slightly extends the testcase to verify views::to_input is idempotent.
> I also added a missing forward in the views::all branch of to_input.
>
LGTM.

>
> -- >8 --
>
> libstdc++-v3/ChangeLog:
>
>         * include/bits/version.def (ranges_to_input): Define.
>         * include/bits/version.h: Regenerate.
>         * include/std/ranges (ranges::to_input_view): Define for C++26.
>         (views::__detail::__can_to_input): Likewise.
>         (views::_ToInput, views::to_input): Likewise.
>         * testsuite/std/ranges/adaptors/to_input/1.cc: New test.
> ---
>  libstdc++-v3/include/bits/version.def         |   8 +
>  libstdc++-v3/include/bits/version.h           |  10 ++
>  libstdc++-v3/include/std/ranges               | 170 ++++++++++++++++++
>  .../std/ranges/adaptors/to_input/1.cc         |  59 ++++++
>  4 files changed, 247 insertions(+)
>  create mode 100644
> libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
>
> diff --git a/libstdc++-v3/include/bits/version.def
> b/libstdc++-v3/include/bits/version.def
> index 2af5a54bff2..c2b5283df89 100644
> --- a/libstdc++-v3/include/bits/version.def
> +++ b/libstdc++-v3/include/bits/version.def
> @@ -1910,6 +1910,14 @@ ftms = {
>    };
>  };
>
> +ftms = {
> +  name = ranges_to_input;
> +  values = {
> +    v = 202502;
> +    cxxmin = 26;
> +  };
> +};
> +
>  ftms = {
>    name = to_string;
>    values = {
> diff --git a/libstdc++-v3/include/bits/version.h
> b/libstdc++-v3/include/bits/version.h
> index 9833023cfdc..775c8642139 100644
> --- a/libstdc++-v3/include/bits/version.h
> +++ b/libstdc++-v3/include/bits/version.h
> @@ -2120,6 +2120,16 @@
>  #endif /* !defined(__cpp_lib_text_encoding) &&
> defined(__glibcxx_want_text_encoding) */
>  #undef __glibcxx_want_text_encoding
>
> +#if !defined(__cpp_lib_ranges_to_input)
> +# if (__cplusplus >  202302L)
> +#  define __glibcxx_ranges_to_input 202502L
> +#  if defined(__glibcxx_want_all) ||
> defined(__glibcxx_want_ranges_to_input)
> +#   define __cpp_lib_ranges_to_input 202502L
> +#  endif
> +# endif
> +#endif /* !defined(__cpp_lib_ranges_to_input) &&
> defined(__glibcxx_want_ranges_to_input) */
> +#undef __glibcxx_want_ranges_to_input
> +
>  #if !defined(__cpp_lib_to_string)
>  # if (__cplusplus >  202302L) && _GLIBCXX_HOSTED && (__glibcxx_to_chars)
>  #  define __glibcxx_to_string 202306L
> diff --git a/libstdc++-v3/include/std/ranges
> b/libstdc++-v3/include/std/ranges
> index e21f5284b46..c2a2d6f4e05 100644
> --- a/libstdc++-v3/include/std/ranges
> +++ b/libstdc++-v3/include/std/ranges
> @@ -69,6 +69,7 @@
>  #define __glibcxx_want_ranges_slide
>  #define __glibcxx_want_ranges_stride
>  #define __glibcxx_want_ranges_to_container
> +#define __glibcxx_want_ranges_to_input
>  #define __glibcxx_want_ranges_zip
>  #include <bits/version.h>
>
> @@ -10390,6 +10391,175 @@ namespace ranges
>  } // namespace ranges
>  #endif // __cpp_lib_ranges_cache_latest
>
> +#if __cpp_lib_ranges_to_input // C++ >= 26
> +namespace ranges
> +{
> +  template<input_range _Vp>
> +    requires view<_Vp>
> +  class to_input_view : public view_interface<to_input_view<_Vp>>
> +  {
> +    _Vp _M_base = _Vp();
> +
> +    template<bool _Const>
> +    class _Iterator;
> +
> +  public:
> +    to_input_view() requires default_initializable<_Vp> = default;
> +
> +    constexpr explicit
> +    to_input_view(_Vp __base)
> +    : _M_base(std::move(__base))
> +    { }
> +
> +    constexpr _Vp
> +    base() const & requires copy_constructible<_Vp>
> +    { return _M_base; }
> +
> +    constexpr _Vp
> +    base() &&
> +    { return std::move(_M_base); }
> +
> +    constexpr auto
> +    begin() requires (!__detail::__simple_view<_Vp>)
> +    { return _Iterator<false>(ranges::begin(_M_base)); }
> +
> +    constexpr auto
> +    begin() const requires range<const _Vp>
> +    { return _Iterator<true>(ranges::begin(_M_base)); }
> +
> +    constexpr auto
> +    end() requires (!__detail::__simple_view<_Vp>)
> +    { return ranges::end(_M_base); }
> +
> +    constexpr auto
> +    end() const requires range<const _Vp>
> +    { return ranges::end(_M_base); }
> +
> +    constexpr auto
> +    size() requires sized_range<_Vp>
> +    { return ranges::size(_M_base); }
> +
> +    constexpr auto
> +    size() const requires sized_range<const _Vp>
> +    { return ranges::size(_M_base); }
> +  };
> +
> +  template<typename _Range>
> +    to_input_view(_Range&&) -> to_input_view<views::all_t<_Range>>;
> +
> +  template<input_range _Vp>
> +    requires view<_Vp>
> +  template<bool _Const>
> +  class to_input_view<_Vp>::_Iterator
> +  {
> +    using _Base = __maybe_const_t<_Const, _Vp>;
> +
> +    iterator_t<_Base> _M_current = iterator_t<_Base>();
> +
> +    constexpr explicit
> +    _Iterator(iterator_t<_Base> __current)
> +    : _M_current(std::move(__current))
> +    { }
> +
> +    friend to_input_view;
> +    friend _Iterator<!_Const>;
> +
> +  public:
> +    using difference_type = range_difference_t<_Base>;
> +    using value_type = range_value_t<_Base>;
> +    using iterator_concept = input_iterator_tag;
> +
> +    _Iterator() requires default_initializable<iterator_t<_Base>> =
> default;
> +
> +    _Iterator(_Iterator&&) = default;
> +    _Iterator& operator=(_Iterator&&) = default;
> +
> +    constexpr
> +    _Iterator(_Iterator<!_Const> __i)
> +      requires _Const && convertible_to<iterator_t<_Vp>,
> iterator_t<_Base>>
> +    : _M_current(std::move(__i._M_current))
> +    { }
> +
> +    constexpr iterator_t<_Base>
> +    base() &&
> +    { return std::move(_M_current); }
> +
> +    constexpr const iterator_t<_Base>&
> +    base() const & noexcept
> +    { return _M_current; }
> +
> +    constexpr decltype(auto)
> +    operator*() const
> +    { return *_M_current; }
> +
> +    constexpr _Iterator&
> +    operator++()
> +    {
> +      ++_M_current;
> +      return *this;
> +    }
> +
> +    constexpr void
> +    operator++(int)
> +    { ++*this; }
> +
> +    friend constexpr bool
> +    operator==(const _Iterator& __x, const sentinel_t<_Base>& __y)
> +    { return __x._M_current == __y; }
> +
> +    friend constexpr difference_type
> +    operator-(const sentinel_t<_Base>& __y, const _Iterator& __x)
> +      requires sized_sentinel_for<sentinel_t<_Base>, iterator_t<_Base>>
> +    { return __y - __x._M_current; }
> +
> +    friend constexpr difference_type
> +    operator-(const _Iterator& __x, const sentinel_t<_Base>& __y)
> +      requires sized_sentinel_for<sentinel_t<_Base>, iterator_t<_Base>>
> +    { return __x._M_current - __y; }
> +
> +    friend constexpr range_rvalue_reference_t<_Base>
> +    iter_move(const _Iterator& __i)
> +      noexcept(noexcept(ranges::iter_move(__i._M_current)))
> +    { return ranges::iter_move(__i._M_current); }
> +
> +    friend constexpr void
> +    iter_swap(const _Iterator& __x, const _Iterator& __y)
> +      noexcept(noexcept(ranges::iter_swap(__x._M_current,
> __y._M_current)))
> +      requires indirectly_swappable<iterator_t<_Base>>
> +    { ranges::iter_swap(__x._M_current, __y._M_current); }
> +  };
> +
> +  namespace views
> +  {
> +    namespace __detail
> +    {
> +      template<typename _Tp>
> +       concept __can_to_input = requires {
> to_input_view(std::declval<_Tp>()); };
> +    }
> +
> +    struct _ToInput : __adaptor::_RangeAdaptorClosure<_ToInput>
> +    {
> +      template<viewable_range _Range>
> +       requires __detail::__can_to_input<_Range>
> +       constexpr auto
> +       operator() [[nodiscard]] (_Range&& __r) const
> +       {
> +         if constexpr (input_range<_Range>
> +                       && !common_range<_Range>
> +                       && !forward_range<_Range>)
> +           return views::all(std::forward<_Range>(__r));
> +         else
> +           return to_input_view(std::forward<_Range>(__r));
> +       }
> +
> +      static constexpr bool _S_has_simple_call_op = true;
> +    };
> +
> +    inline constexpr _ToInput to_input;
> +  }
> +} // namespace ranges
> +#endif // __cpp_lib_ranges_to_input
> +
>  _GLIBCXX_END_NAMESPACE_VERSION
>  } // namespace std
>  #endif // library concepts
> diff --git a/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
> b/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
> new file mode 100644
> index 00000000000..cde368a2f64
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
> @@ -0,0 +1,59 @@
> +// { dg-do run { target c++26 } }
> +
> +#include <ranges>
> +
> +#if __cpp_lib_ranges_to_input != 202502L
> +# error "Feature-test macro __cpp_lib_ranges_to_input has wrong value in
> <ranges>"
> +#endif
> +
> +#include <algorithm>
> +#include <vector>
> +#include <testsuite_hooks.h>
> +#include <testsuite_iterators.h>
> +
> +namespace ranges = std::ranges;
> +namespace views = std::views;
> +
> +void
> +test01()
> +{
> +  std::vector<int> r{1,2,3};
> +  auto v = r | views::to_input;
> +  using type = decltype(v);
> +  static_assert( ranges::input_range<type> &&
> !ranges::forward_range<type> );
> +
> +  VERIFY( ranges::equal(v.base(), r) );
> +  VERIFY( v.size() == r.size() );
> +  VERIFY( v.end() == r.end() );
> +  auto it = v.begin();
> +  VERIFY( it != r.end() );
> +  *it = 42;
> +  ++it;
> +  *it = 43;
> +  it++;
> +  ranges::iter_swap(v.begin(), it);
> +  VERIFY( ranges::equal(r, (int[]){3,43,42}) );
> +  *it = ranges::iter_move(it);
> +  VERIFY( it == r.begin() + 2 );
> +  VERIFY( r.end() - it == 1 );
> +  VERIFY( it - r.end() == -1 );
> +}
> +
> +void
> +test02()
> +{
> +  int x[] = {1,2,3};
> +  __gnu_test::test_input_range<int> rx(x);
> +  static_assert( !ranges::common_range<decltype(rx)> );
> +  auto v = rx | views::to_input;
> +  static_assert( std::same_as<decltype(v), decltype(views::all(rx))> );
> +  static_assert( std::same_as<decltype(x | views::to_input),
> +                             decltype(x | views::to_input |
> views::to_input)> );
> +}
> +
> +int
> +main()
> +{
> +  test01();
> +  test02();
> +}
> --
> 2.49.0.rc1.37.ge969bc8759
>
> >
> >       In file included from
> src/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc:3:
> >
>  /scratchpad/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/ranges: In
> instantiation of ‘constexpr void std::ranges::iter_swap(const
> to_input_view<ref_view<std::__debug::vector<int> >
> >       >::_Iterator<true>&, const
> to_input_view<ref_view<std::__debug::vector<int> > >::_Iterator<true>&)
> requires
> indirectly_swappable<decltype(std::ranges::__access::__begin((declval<typename
> >       std::__conditional<_Const>::type<const _Vp, _Vp>&>)())),
> decltype(std::ranges::__access::__begin((declval<typename
> std::__conditional<_Const>::type<const _Vp, _Vp>&>)()))>’:
> >
>  /scratchpad/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/iterator_concepts.h:949:15:
>  required from ‘constexpr void
> std::ranges::__iswap::_IterSwap::operator()(_Tp&&, _Up&&) const
> >       [with _Tp =
> std::ranges::to_input_view<std::ranges::ref_view<std::__debug::vector<int>
> > >::_Iterator<true>; _Up =
> std::ranges::to_input_view<std::ranges::ref_view<std::__debug::vector<int> >
> >       >::_Iterator<true>&]’
> >         949 |             iter_swap(static_cast<_Tp&&>(__e1),
> static_cast<_Up&&>(__e2));
> >             |
>  ~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> >
>  src/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc:34:20:
>  required from here
> >          34 |   ranges::iter_swap(v.begin(), it);
> >             |   ~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~
> >
>  /scratchpad/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/ranges:10529:21:
> error: static assertion failed
> >       10529 |     { static_assert(false);
> ranges::iter_swap(__x._M_current, __y._M_current); }
> >             |                     ^~~~~
> >
>  /scratchpad/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/ranges:10529:21:
> note: ‘false’ evaluates to false
> >
> >       >       +  VERIFY( ranges::equal(r, (int[]){3,43,42}) );
> >       >       +  *it = ranges::iter_move(it);
> >       >       +  VERIFY( it == r.begin() + 2 );
> >       >       +  VERIFY( r.end() - it == 1 );
> >       >       +  VERIFY( it - r.end() == -1 );
> >       >       +}
> >       >       +
> >       >       +void
> >       >       +test02()
> >       >       +{
> >       >       +  int x[] = {1,2,3};
> >       >       +  __gnu_test::test_input_range<int> rx(x);
> >       >       +  static_assert( !ranges::common_range<decltype(rx)> );
> >       >       +  auto v = rx | views::to_input;
> >       >       +  using type = decltype(v);
> >       >       +  using type = ranges::ref_view<decltype(rx)>;
> >       >
> >       > I would prefer a more direct representation of this check, as
> static_assert(std::is_same_v<decltype(views::all(rx)), decltype())>);
> >
> >       Sounds good.  The 'using' technique doesn't add much here since
> the type
> >       names are short and we don't subsequently use the alias anywhere.
> >
> >       >       +}
> >       >       +
> >       >       +int
> >       >       +main()
> >       >       +{
> >       >       +  test01();
> >       >       +  test02();
> >       >       +}
> >       >       --
> >       >       2.49.0.rc1.37.ge969bc8759
> >       >
> >       >
> >       >
> >
> >
> >
  
Jonathan Wakely March 11, 2025, 5:15 p.m. UTC | #8
On 11/03/25 12:54 -0400, Patrick Palka wrote:
>Hmm good point -- while the to_input_view iterator is always move-only,
>it's not always single pass, the latter property is inherited from the
>underlying range.
>
>Here's an updated patch that uses std::same_as in the testcase, and
>slightly extends the testcase to verify views::to_input is idempotent.
>I also added a missing forward in the views::all branch of to_input.

I definitely prefer the std::same_as test rather than redefining the
'type' alias.

OK for trunk, thanks.
  

Patch

diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
index 2af5a54bff2..c2b5283df89 100644
--- a/libstdc++-v3/include/bits/version.def
+++ b/libstdc++-v3/include/bits/version.def
@@ -1910,6 +1910,14 @@  ftms = {
   };
 };
 
+ftms = {
+  name = ranges_to_input;
+  values = {
+    v = 202502;
+    cxxmin = 26;
+  };
+};
+
 ftms = {
   name = to_string;
   values = {
diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
index 9833023cfdc..775c8642139 100644
--- a/libstdc++-v3/include/bits/version.h
+++ b/libstdc++-v3/include/bits/version.h
@@ -2120,6 +2120,16 @@ 
 #endif /* !defined(__cpp_lib_text_encoding) && defined(__glibcxx_want_text_encoding) */
 #undef __glibcxx_want_text_encoding
 
+#if !defined(__cpp_lib_ranges_to_input)
+# if (__cplusplus >  202302L)
+#  define __glibcxx_ranges_to_input 202502L
+#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_ranges_to_input)
+#   define __cpp_lib_ranges_to_input 202502L
+#  endif
+# endif
+#endif /* !defined(__cpp_lib_ranges_to_input) && defined(__glibcxx_want_ranges_to_input) */
+#undef __glibcxx_want_ranges_to_input
+
 #if !defined(__cpp_lib_to_string)
 # if (__cplusplus >  202302L) && _GLIBCXX_HOSTED && (__glibcxx_to_chars)
 #  define __glibcxx_to_string 202306L
diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges
index e21f5284b46..dd97d276ef0 100644
--- a/libstdc++-v3/include/std/ranges
+++ b/libstdc++-v3/include/std/ranges
@@ -69,6 +69,7 @@ 
 #define __glibcxx_want_ranges_slide
 #define __glibcxx_want_ranges_stride
 #define __glibcxx_want_ranges_to_container
+#define __glibcxx_want_ranges_to_input
 #define __glibcxx_want_ranges_zip
 #include <bits/version.h>
 
@@ -10390,6 +10391,175 @@  namespace ranges
 } // namespace ranges
 #endif // __cpp_lib_ranges_cache_latest
 
+#if __cpp_lib_ranges_to_input // C++ >= 26
+namespace ranges
+{
+  template<input_range _Vp>
+    requires view<_Vp>
+  class to_input_view : public view_interface<to_input_view<_Vp>>
+  {
+    _Vp _M_base = _Vp();
+
+    template<bool _Const>
+    class _Iterator;
+
+  public:
+    to_input_view() requires default_initializable<_Vp> = default;
+
+    constexpr explicit
+    to_input_view(_Vp __base)
+    : _M_base(std::move(__base))
+    { }
+
+    constexpr _Vp
+    base() const & requires copy_constructible<_Vp>
+    { return _M_base; }
+
+    constexpr _Vp
+    base() &&
+    { return std::move(_M_base); }
+
+    constexpr auto
+    begin() requires (!__detail::__simple_view<_Vp>)
+    { return _Iterator<false>(ranges::begin(_M_base)); }
+
+    constexpr auto
+    begin() const requires range<const _Vp>
+    { return _Iterator<true>(ranges::begin(_M_base)); }
+
+    constexpr auto
+    end() requires (!__detail::__simple_view<_Vp>)
+    { return ranges::end(_M_base); }
+
+    constexpr auto
+    end() const requires range<const _Vp>
+    { return ranges::end(_M_base); }
+
+    constexpr auto
+    size() requires sized_range<_Vp>
+    { return ranges::size(_M_base); }
+
+    constexpr auto
+    size() const requires sized_range<const _Vp>
+    { return ranges::size(_M_base); }
+  };
+
+  template<typename _Range>
+    to_input_view(_Range&&) -> to_input_view<views::all_t<_Range>>;
+
+  template<input_range _Vp>
+    requires view<_Vp>
+  template<bool _Const>
+  class to_input_view<_Vp>::_Iterator
+  {
+    using _Base = __maybe_const_t<_Const, _Vp>;
+
+    iterator_t<_Base> _M_current = iterator_t<_Base>();
+
+    constexpr explicit
+    _Iterator(iterator_t<_Base> __current)
+    : _M_current(std::move(__current))
+    { }
+
+    friend to_input_view;
+    friend _Iterator<!_Const>;
+
+  public:
+    using difference_type = range_difference_t<_Base>;
+    using value_type = range_value_t<_Base>;
+    using iterator_concept = input_iterator_tag;
+
+    _Iterator() requires default_initializable<iterator_t<_Base>> = default;
+
+    _Iterator(_Iterator&&) = default;
+    _Iterator& operator=(_Iterator&&) = default;
+
+    constexpr
+    _Iterator(_Iterator<!_Const> __i)
+      requires _Const && convertible_to<iterator_t<_Vp>, iterator_t<_Base>>
+    : _M_current(std::move(__i._M_current))
+    { }
+
+    constexpr iterator_t<_Base>
+    base() &&
+    { return std::move(_M_current); }
+
+    constexpr const iterator_t<_Base>&
+    base() const & noexcept
+    { return _M_current; }
+
+    constexpr decltype(auto)
+    operator*() const
+    { return *_M_current; }
+
+    constexpr _Iterator&
+    operator++()
+    {
+      ++_M_current;
+      return *this;
+    }
+
+    constexpr void
+    operator++(int)
+    { ++*this; }
+
+    friend constexpr bool
+    operator==(const _Iterator& __x, const sentinel_t<_Base>& __y)
+    { return __x._M_current == __y; }
+
+    friend constexpr difference_type
+    operator-(const sentinel_t<_Base>& __y, const _Iterator& __x)
+      requires sized_sentinel_for<sentinel_t<_Base>, iterator_t<_Base>>
+    { return __y - __x._M_current; }
+
+    friend constexpr difference_type
+    operator-(const _Iterator& __x, const sentinel_t<_Base>& __y)
+      requires sized_sentinel_for<sentinel_t<_Base>, iterator_t<_Base>>
+    { return __x._M_current - __y; }
+
+    friend constexpr range_rvalue_reference_t<_Base>
+    iter_move(const _Iterator& __i)
+      noexcept(noexcept(ranges::iter_move(__i._M_current)))
+    { return ranges::iter_move(__i._M_current); }
+
+    friend constexpr void
+    iter_swap(const _Iterator& __x, const _Iterator& __y)
+      noexcept(noexcept(ranges::iter_swap(__x._M_current, __y._M_current)))
+      requires indirectly_swappable<iterator_t<_Base>>
+    { ranges::iter_swap(__x._M_current, __y._M_current); }
+  };
+
+  namespace views
+  {
+    namespace __detail
+    {
+      template<typename _Tp>
+	concept __can_to_input = requires { to_input_view(std::declval<_Tp>()); };
+    }
+
+    struct _ToInput : __adaptor::_RangeAdaptorClosure<_ToInput>
+    {
+      template<viewable_range _Range>
+	requires __detail::__can_to_input<_Range>
+	constexpr auto
+	operator() [[nodiscard]] (_Range&& __r) const
+	{
+	  if constexpr (input_range<_Range>
+			&& !common_range<_Range>
+			&& !forward_range<_Range>)
+	    return views::all(__r);
+	  else
+	    return to_input_view(std::forward<_Range>(__r));
+	}
+
+      static constexpr bool _S_has_simple_call_op = true;
+    };
+
+    inline constexpr _ToInput to_input;
+  }
+} // namespace ranges
+#endif // __cpp_lib_ranges_to_input
+
 _GLIBCXX_END_NAMESPACE_VERSION
 } // namespace std
 #endif // library concepts
diff --git a/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc b/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
new file mode 100644
index 00000000000..1e43281adb4
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/ranges/adaptors/to_input/1.cc
@@ -0,0 +1,58 @@ 
+// { dg-do run { target c++26 } }
+
+#include <ranges>
+
+#if __cpp_lib_ranges_to_input != 202502L
+# error "Feature-test macro __cpp_lib_ranges_to_input has wrong value in <ranges>"
+#endif
+
+#include <algorithm>
+#include <vector>
+#include <testsuite_hooks.h>
+#include <testsuite_iterators.h>
+
+namespace ranges = std::ranges;
+namespace views = std::views;
+
+void
+test01()
+{
+  std::vector<int> r{1,2,3};
+  auto v = r | views::to_input;
+  using type = decltype(v);
+  static_assert( ranges::input_range<type> && !ranges::forward_range<type> );
+
+  VERIFY( ranges::equal(v.base(), r) );
+  VERIFY( v.size() == r.size() );
+  VERIFY( v.end() == r.end() );
+  auto it = v.begin();
+  VERIFY( it != r.end() );
+  *it = 42;
+  ++it;
+  *it = 43;
+  it++;
+  ranges::iter_swap(v.begin(), it);
+  VERIFY( ranges::equal(r, (int[]){3,43,42}) );
+  *it = ranges::iter_move(it);
+  VERIFY( it == r.begin() + 2 );
+  VERIFY( r.end() - it == 1 );
+  VERIFY( it - r.end() == -1 );
+}
+
+void
+test02()
+{
+  int x[] = {1,2,3};
+  __gnu_test::test_input_range<int> rx(x);
+  static_assert( !ranges::common_range<decltype(rx)> );
+  auto v = rx | views::to_input;
+  using type = decltype(v);
+  using type = ranges::ref_view<decltype(rx)>;
+}
+
+int
+main()
+{
+  test01();
+  test02();
+}