public inbox for libstdc++@gcc.gnu.org
 help / color / mirror / Atom feed
From: Patrick Palka <ppalka@redhat.com>
To: Jonathan Wakely <jwakely@redhat.com>
Cc: libstdc++@gcc.gnu.org, gcc-patches@gcc.gnu.org
Subject: Re: [committed v2] libstdc++: Define std::ranges::to for C++23 (P1206R7) [PR111055]
Date: Wed, 29 Nov 2023 11:28:17 -0500 (EST)	[thread overview]
Message-ID: <cc39b9c5-e3aa-6296-0f37-4a14840b8d6e@idea> (raw)
In-Reply-To: <20231123175247.2451163-1-jwakely@redhat.com>

On Thu, 23 Nov 2023, Jonathan Wakely wrote:

> Here's the finished version of the std::ranges::to patch, which I've
> pushed to trunk.
> 
> Tested x86_64-linux.
> 
> -- >8 --
> 
> This adds the std::ranges::to functions for C++23. The rest of P1206R7
> is not yet implemented, i.e. the new constructors taking the
> std::from_range tag, and the new insert_range, assign_range, etc. member
> functions. std::ranges::to works with the standard containers even
> without the new constructors, so this is useful immediately.
> 
> The __cpp_lib_ranges_to_container feature test macro can be defined now,
> because that only indicates support for the changes in <ranges>, which
> are implemented by this patch. The __cpp_lib_containers_ranges macro
> will be defined once all containers support the new member functions.
> 
> libstdc++-v3/ChangeLog:
> 
> 	PR libstdc++/111055
> 	* include/bits/ranges_base.h (from_range_t): Define new tag
> 	type.
> 	(from_range): Define new tag object.
> 	* include/bits/version.def (ranges_to_container): Define.
> 	* include/bits/version.h: Regenerate.
> 	* include/std/ranges (ranges::to): Define.
> 	* testsuite/std/ranges/conv/1.cc: New test.
> 	* testsuite/std/ranges/conv/2_neg.cc: New test.
> 	* testsuite/std/ranges/conv/version.cc: New test.
> ---
>  libstdc++-v3/include/bits/ranges_base.h       |   8 +-
>  libstdc++-v3/include/bits/version.def         |  34 +-
>  libstdc++-v3/include/bits/version.h           | 111 +++---
>  libstdc++-v3/include/std/ranges               | 361 ++++++++++++++++-
>  libstdc++-v3/testsuite/std/ranges/conv/1.cc   | 369 ++++++++++++++++++
>  .../testsuite/std/ranges/conv/2_neg.cc        |  24 ++
>  .../testsuite/std/ranges/conv/version.cc      |  19 +
>  7 files changed, 866 insertions(+), 60 deletions(-)
>  create mode 100644 libstdc++-v3/testsuite/std/ranges/conv/1.cc
>  create mode 100644 libstdc++-v3/testsuite/std/ranges/conv/2_neg.cc
>  create mode 100644 libstdc++-v3/testsuite/std/ranges/conv/version.cc
> 
> diff --git a/libstdc++-v3/include/bits/ranges_base.h b/libstdc++-v3/include/bits/ranges_base.h
> index 7fa43d1965a..1ca2c5ce2bb 100644
> --- a/libstdc++-v3/include/bits/ranges_base.h
> +++ b/libstdc++-v3/include/bits/ranges_base.h
> @@ -37,6 +37,7 @@
>  #include <bits/stl_iterator.h>
>  #include <ext/numeric_traits.h>
>  #include <bits/max_size_type.h>
> +#include <bits/version.h>
>  
>  #ifdef __cpp_lib_concepts
>  namespace std _GLIBCXX_VISIBILITY(default)
> @@ -1056,8 +1057,13 @@ namespace ranges
>      using borrowed_iterator_t = __conditional_t<borrowed_range<_Range>,
>  						iterator_t<_Range>,
>  						dangling>;
> -
>  } // namespace ranges
> +
> +#if __glibcxx_ranges_to_container // C++ >= 23
> +  struct from_range_t { explicit from_range_t() = default; };
> +  inline constexpr from_range_t from_range{};
> +#endif
> +
>  _GLIBCXX_END_NAMESPACE_VERSION
>  } // namespace std
>  #endif // library concepts
> diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
> index 605708dfee7..140777832ed 100644
> --- a/libstdc++-v3/include/bits/version.def
> +++ b/libstdc++-v3/include/bits/version.def
> @@ -1439,19 +1439,21 @@ ftms = {
>    };
>  };
>  
> -ftms = {
> -  name = to_underlying;
> -  values = {
> -    v = 202102;
> -    cxxmin = 23;
> -  };
> -};
> +//ftms = {
> +//  name = container_ranges;
> +//  values = {
> +//    v = 202202;
> +//    cxxmin = 23;
> +//    hosted = yes;
> +//  };
> +//};
>  
>  ftms = {
> -  name = unreachable;
> +  name = ranges_to_container;
>    values = {
>      v = 202202;
>      cxxmin = 23;
> +    hosted = yes;
>    };
>  };
>  
> @@ -1683,6 +1685,22 @@ ftms = {
>    };
>  };
>  
> +ftms = {
> +  name = to_underlying;
> +  values = {
> +    v = 202102;
> +    cxxmin = 23;
> +  };
> +};
> +
> +ftms = {
> +  name = unreachable;
> +  values = {
> +    v = 202202;
> +    cxxmin = 23;
> +  };
> +};
> +
>  ftms = {
>    name = fstream_native_handle;
>    values = {
> diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
> index cacd9375cab..1fb1d148459 100644
> --- a/libstdc++-v3/include/bits/version.h
> +++ b/libstdc++-v3/include/bits/version.h
> @@ -1740,29 +1740,18 @@
>  #endif /* !defined(__cpp_lib_reference_from_temporary) && defined(__glibcxx_want_reference_from_temporary) */
>  #undef __glibcxx_want_reference_from_temporary
>  
> -// from version.def line 1443
> -#if !defined(__cpp_lib_to_underlying)
> -# if (__cplusplus >= 202100L)
> -#  define __glibcxx_to_underlying 202102L
> -#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_to_underlying)
> -#   define __cpp_lib_to_underlying 202102L
> +// from version.def line 1452
> +#if !defined(__cpp_lib_ranges_to_container)
> +# if (__cplusplus >= 202100L) && _GLIBCXX_HOSTED
> +#  define __glibcxx_ranges_to_container 202202L
> +#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_ranges_to_container)
> +#   define __cpp_lib_ranges_to_container 202202L
>  #  endif
>  # endif
> -#endif /* !defined(__cpp_lib_to_underlying) && defined(__glibcxx_want_to_underlying) */
> -#undef __glibcxx_want_to_underlying
> +#endif /* !defined(__cpp_lib_ranges_to_container) && defined(__glibcxx_want_ranges_to_container) */
> +#undef __glibcxx_want_ranges_to_container
>  
> -// from version.def line 1451
> -#if !defined(__cpp_lib_unreachable)
> -# if (__cplusplus >= 202100L)
> -#  define __glibcxx_unreachable 202202L
> -#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_unreachable)
> -#   define __cpp_lib_unreachable 202202L
> -#  endif
> -# endif
> -#endif /* !defined(__cpp_lib_unreachable) && defined(__glibcxx_want_unreachable) */
> -#undef __glibcxx_want_unreachable
> -
> -// from version.def line 1459
> +// from version.def line 1461
>  #if !defined(__cpp_lib_ranges_zip)
>  # if (__cplusplus >= 202100L)
>  #  define __glibcxx_ranges_zip 202110L
> @@ -2059,7 +2048,29 @@
>  #endif /* !defined(__cpp_lib_string_resize_and_overwrite) && defined(__glibcxx_want_string_resize_and_overwrite) */
>  #undef __glibcxx_want_string_resize_and_overwrite
>  
> -// from version.def line 1687
> +// from version.def line 1689
> +#if !defined(__cpp_lib_to_underlying)
> +# if (__cplusplus >= 202100L)
> +#  define __glibcxx_to_underlying 202102L
> +#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_to_underlying)
> +#   define __cpp_lib_to_underlying 202102L
> +#  endif
> +# endif
> +#endif /* !defined(__cpp_lib_to_underlying) && defined(__glibcxx_want_to_underlying) */
> +#undef __glibcxx_want_to_underlying
> +
> +// from version.def line 1697
> +#if !defined(__cpp_lib_unreachable)
> +# if (__cplusplus >= 202100L)
> +#  define __glibcxx_unreachable 202202L
> +#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_unreachable)
> +#   define __cpp_lib_unreachable 202202L
> +#  endif
> +# endif
> +#endif /* !defined(__cpp_lib_unreachable) && defined(__glibcxx_want_unreachable) */
> +#undef __glibcxx_want_unreachable
> +
> +// from version.def line 1705
>  #if !defined(__cpp_lib_fstream_native_handle)
>  # if (__cplusplus >  202302L) && _GLIBCXX_HOSTED
>  #  define __glibcxx_fstream_native_handle 202306L
> diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges
> index 26d6c013ad0..63bea862c05 100644
> --- a/libstdc++-v3/include/std/ranges
> +++ b/libstdc++-v3/include/std/ranges
> @@ -64,6 +64,7 @@
>  #define __glibcxx_want_ranges_repeat
>  #define __glibcxx_want_ranges_slide
>  #define __glibcxx_want_ranges_stride
> +#define __glibcxx_want_ranges_to_container
>  #define __glibcxx_want_ranges_zip
>  #include <bits/version.h>
>  
> @@ -9213,8 +9214,366 @@ namespace views::__adaptor
>  
>    namespace views = ranges::views;
>  
> +#if __cpp_lib_ranges_to_container // C++ >= 23
> +namespace ranges
> +{
> +/// @cond undocumented
> +namespace __detail
> +{
> +  template<typename _Container>
> +    constexpr bool __reservable_container
> +      = sized_range<_Container>
> +      && requires(_Container& __c, range_size_t<_Container> __n) {
> +	__c.reserve(__n);
> +	{ __c.capacity() } -> same_as<decltype(__n)>;
> +	{ __c.max_size() } -> same_as<decltype(__n)>;
> +      };
> +
> +  template<typename _Container, typename _Ref>
> +    constexpr bool __container_insertable
> +      = requires(_Container& __c, _Ref&& __ref) {
> +	typename _Container::value_type;
> +	requires (
> +	  requires { __c.push_back(std::forward<_Ref>(__ref)); }
> +	  || requires { __c.insert(__c.end(), std::forward<_Ref>(__ref)); }
> +	);
> +      };
> +
> +  template<typename _Ref, typename _Container>
> +    constexpr auto
> +    __container_inserter(_Container& __c)
> +    {
> +      if constexpr (requires { __c.push_back(std::declval<_Ref>()); })
> +	return std::back_inserter(__c);
> +      else
> +	return std::inserter(__c, __c.end());
> +    }
> +
> +  template<typename _Cont, typename _Range>
> +    constexpr bool __toable = requires {
> +      requires (!input_range<_Range>

It seems this sholud be input_range<_Cont>?

> +		  || convertible_to<range_reference_t<_Range>,
> +				    range_value_t<_Cont>>);
> +    };
> +} // namespace __detail
> +/// @endcond
> +
> +  /// Convert a range to a container.
> +  /**
> +   * @tparam _Cont A container type.
> +   * @param __r A range that models the `input_range` concept.
> +   * @param __args... Arguments to pass to the container constructor.
> +   * @since C++23
> +   *
> +   * This function converts a range to the `_Cont` type.
> +   *
> +   * For example, `std::ranges::to<std::vector<int>>(some_view)`
> +   * will convert the view to `std::vector<int>`.
> +   *
> +   * Additional constructor arguments for the container can be supplied after
> +   * the input range argument, e.g.
> +   * `std::ranges::to<std::vector<int, Alloc<int>>>(a_range, an_allocator)`.
> +   */
> +  template<typename _Cont, input_range _Rg, typename... _Args>
> +    requires (!view<_Cont>)
> +    constexpr _Cont
> +    to [[nodiscard]] (_Rg&& __r, _Args&&... __args)
> +    {
> +      static_assert(!is_const_v<_Cont> && !is_volatile_v<_Cont>);
> +      static_assert(is_class_v<_Cont>);
> +
> +      if constexpr (__detail::__toable<_Cont, _Rg>)
> +	{
> +	  if constexpr (constructible_from<_Cont, _Rg, _Args...>)
> +	    return _Cont(std::forward<_Rg>(__r),
> +			 std::forward<_Args>(__args)...);
> +	  else if constexpr (constructible_from<_Cont, from_range_t, _Rg, _Args...>)
> +	    return _Cont(from_range, std::forward<_Rg>(__r),
> +			 std::forward<_Args>(__args)...);
> +	  else if constexpr (requires { common_range<_Rg>;

Missing 'requires' before common_range?

> +		typename __iter_category_t<iterator_t<_Rg>>;
> +		requires derived_from<__iter_category_t<iterator_t<_Rg>>,
> +				      input_iterator_tag>;
> +		requires constructible_from<_Cont, iterator_t<_Rg>,
> +					    sentinel_t<_Rg>, _Args...>;
> +	      })
> +	    return _Cont(ranges::begin(__r), ranges::end(__r),
> +			 std::forward<_Args>(__args)...);
> +	  else
> +	    {
> +	      using __detail::__container_insertable;
> +	      using __detail::__reservable_container;
> +	      using _RefT = range_reference_t<_Rg>;
> +	      static_assert(constructible_from<_Cont, _Args...>);
> +	      static_assert(__container_insertable<_Cont, _RefT>);
> +	      _Cont __c(std::forward<_Args>(__args)...);
> +	      if constexpr (sized_range<_Rg> && __reservable_container<_Cont>)
> +		__c.reserve(static_cast<range_size_t<_Cont>>(ranges::size(__r)));
> +	      auto __ins = __detail::__container_inserter<_RefT>(__c);
> +	      for (auto&& __e : __r)
> +		*__ins++ = std::forward<decltype(__e)>(__e);
> +	      return __c;
> +	    }
> +	}
> +      else
> +	{
> +	  static_assert(input_range<range_reference_t<_Rg>>);
> +	  // _GLIBCXX_RESOLVE_LIB_DEFECTS
> +	  // 3984. ranges::to's recursion branch may be ill-formed
> +	  return ranges::to<_Cont>(ref_view(__r) | views::transform(
> +		[]<typename _Elt>(_Elt&& __elem) {
> +		  using _ValT = range_value_t<_Cont>;
> +		  return ranges::to<_ValT>(std::forward<_Elt>(__elem));
> +		}), std::forward<_Args>(__args)...);
> +	}
> +    }
> +
> +/// @cond undocumented
> +namespace __detail
> +{
> +  template<typename _Rg>
> +    struct _InputIter
> +    {
> +      using iterator_category = input_iterator_tag;
> +      using value_type = range_value_t<_Rg>;
> +      using difference_type = ptrdiff_t;
> +      using pointer = add_pointer_t<range_reference_t<_Rg>>;
> +      using reference = range_reference_t<_Rg>;
> +      reference operator*() const;
> +      pointer operator->() const;
> +      _InputIter& operator++();
> +      _InputIter operator++(int);
> +      bool operator==(const _InputIter&) const;
> +    };
> +
> +#if 0
> +  template<template<typename...> typename _Cont, typename _Rg,
> +	   typename... _Args>
> +    concept __deduce_expr_1 = requires {
> +      _Cont(std::declval<_Rg>(), std::declval<_Args>()...);
> +    };
> +
> +  template<template<typename...> typename _Cont, typename _Rg,
> +	   typename... _Args>
> +    concept __deduce_expr_2 = requires {
> +      _Cont(from_range, std::declval<_Rg>(), std::declval<_Args>()...);
> +    };
> +
> +  template<template<typename...> typename _Cont, typename _Rg,
> +	   typename... _Args>
> +    concept __deduce_expr_3 = requires(_InputIter<_Rg> __i) {
> +      _Cont(std::move(__i), std::move(__i), std::declval<_Args>()...);
> +    };
> +#endif
> +
> +  template<template<typename...> typename _Cont, input_range _Rg,
> +	   typename... _Args>
> +    using _DeduceExpr1
> +      = decltype(_Cont(std::declval<_Rg>(), std::declval<_Args>()...));
> +
> +  template<template<typename...> typename _Cont, input_range _Rg,
> +	   typename... _Args>
> +    using _DeduceExpr2
> +      = decltype(_Cont(from_range, std::declval<_Rg>(),
> +		       std::declval<_Args>()...));
> +
> +  template<template<typename...> typename _Cont, input_range _Rg,
> +	   typename... _Args>
> +    using _DeduceExpr3
> +      = decltype(_Cont(std::declval<_InputIter<_Rg>>(),
> +		       std::declval<_InputIter<_Rg>>(),
> +		       std::declval<_Args>()...));
> +
> +} // namespace __detail
> +/// @endcond
> +
> +  template<template<typename...> typename _Cont, input_range _Rg,
> +	   typename... _Args>
> +    constexpr auto
> +    to [[nodiscard]] (_Rg&& __r, _Args&&... __args)
> +    {
> +      using __detail::_DeduceExpr1;
> +      using __detail::_DeduceExpr2;
> +      using __detail::_DeduceExpr3;
> +      if constexpr (requires { typename _DeduceExpr1<_Cont, _Rg, _Args...>; })
> +	return ranges::to<_DeduceExpr1<_Cont, _Rg, _Args...>>(
> +	    std::forward<_Rg>(__r), std::forward<_Args>(__args)...);
> +      else if constexpr (requires { typename _DeduceExpr2<_Cont, _Rg, _Args...>; })
> +	return ranges::to<_DeduceExpr2<_Cont, _Rg, _Args...>>(
> +	    std::forward<_Rg>(__r), std::forward<_Args>(__args)...);
> +      else if constexpr (requires { typename _DeduceExpr3<_Cont, _Rg, _Args...>; })
> +	return ranges::to<_DeduceExpr3<_Cont, _Rg, _Args...>>(
> +	    std::forward<_Rg>(__r), std::forward<_Args>(__args)...);
> +      else
> +	static_assert(false); // Cannot deduce container specialization.
> +    }
> +
> +/// @cond undocumented
> +namespace __detail
> +{
> +  template<typename _Cont, typename... _Args>
> +    class _ToClosure
> +    : public views::__adaptor::_RangeAdaptorClosure<_ToClosure<_Cont, _Args...>>
> +    {
> +      tuple<decay_t<_Args>...> _M_bound_args;
> +
> +    public:
> +      _ToClosure(_Args&&... __args)

Missing constexpr?

> +      : _M_bound_args(std::forward<_Args>(__args)...)
> +      { }
> +
> +      // TODO: use explicit object functions ("deducing this").
> +
> +      template<typename _Rg>
> +	constexpr auto
> +	operator()(_Rg&& __r) &
> +	{
> +	  return std::apply([&__r]<typename... _Tp>(_Tp&&... __args) {
> +	    return ranges::to<_Cont>(std::forward<_Rg>(__r),
> +				     std::forward<_Tp>(__args)...);
> +	  }, _M_bound_args);
> +	}
> +
> +      template<typename _Rg>
> +	constexpr auto
> +	operator()(_Rg&& __r) const &
> +	{
> +	  return std::apply([&__r]<typename... _Tp>(_Tp&&... __args) {
> +	    return ranges::to<_Cont>(std::forward<_Rg>(__r),
> +				     std::forward<_Tp>(__args)...);
> +	  }, _M_bound_args);
> +	}
> +
> +      template<typename _Rg>
> +	constexpr auto
> +	operator()(_Rg&& __r) &&
> +	{
> +	  return std::apply([&__r]<typename... _Tp>(_Tp&&... __args) {
> +	    return ranges::to<_Cont>(std::forward<_Rg>(__r),
> +				     std::forward<_Tp>(__args)...);
> +	  }, std::move(_M_bound_args));
> +	}
> +
> +      template<typename _Rg>
> +	constexpr auto
> +	operator()(_Rg&& __r) const &&
> +	{
> +	  return std::apply([&__r]<typename... _Tp>(_Tp&&... __args) {
> +	    return ranges::to<_Cont>(std::forward<_Rg>(__r),
> +				     std::forward<_Tp>(__args)...);
> +	  }, std::move(_M_bound_args));
> +	}
> +    };
> +} // namespace __detail
> +/// @endcond
> +
> +  /// ranges::to adaptor for converting a range to a container type
> +  /**
> +   * @tparam _Cont A container type.
> +   * @param __args... Arguments to pass to the container constructor.
> +   * @since C++23
> +   *
> +   * This range adaptor returns a range adaptor closure object that converts
> +   * a range to the `_Cont` type.
> +   *
> +   * For example, `some_view | std::ranges::to<std::vector<int>>()`
> +   * will convert the view to `std::vector<int>`.
> +   *
> +   * Additional constructor arguments for the container can be supplied, e.g.
> +   * `r | std::ranges::to<std::vector<int, Alloc<int>>>(an_allocator)`.
> +   */
> +  template<typename _Cont, typename... _Args>
> +    requires (!view<_Cont>)
> +    constexpr __detail::_ToClosure<_Cont, _Args...>
> +    to [[nodiscard]] (_Args&&... __args)
> +    { return {std::forward<_Args>(__args)...}; }
> +
> +/// @cond undocumented
> +namespace __detail
> +{
> +  template<template<typename...> typename _Cont, typename... _Args>
> +    class _ToClosure2
> +    : public views::__adaptor::_RangeAdaptorClosure<_ToClosure2<_Cont, _Args...>>
> +    {
> +      tuple<decay_t<_Args>...> _M_bound_args;
> +
> +    public:
> +      _ToClosure2(_Args&&... __args)

Same here.

Would it be possible to use _RangeAdaptor instead of _RangeAdaptorClosure to
leverage the existing partial application/forwarding code?  IIUC we'd need to
allow omitting the _S_arity member to mean accept any number of arguments.
I can work on that if anything.

> +      : _M_bound_args(std::forward<_Args>(__args)...)
> +      { }
> +
> +      // TODO: use explicit object functions ("deducing this").
> +
> +      template<typename _Rg>
> +	constexpr auto
> +	operator()(_Rg&& __r) &
> +	{
> +	  return std::apply([&__r]<typename... _Tp>(_Tp&&... __args) {
> +	    return ranges::to<_Cont>(std::forward<_Rg>(__r),
> +				     std::forward<_Tp>(__args)...);
> +	  }, _M_bound_args);
> +	}
> +
> +      template<typename _Rg>
> +	constexpr auto
> +	operator()(_Rg&& __r) const &
> +	{
> +	  return std::apply([&__r]<typename... _Tp>(_Tp&&... __args) {
> +	    return ranges::to<_Cont>(std::forward<_Rg>(__r),
> +				     std::forward<_Tp>(__args)...);
> +	  }, _M_bound_args);
> +	}
> +
> +      template<typename _Rg>
> +	constexpr auto
> +	operator()(_Rg&& __r) &&
> +	{
> +	  return std::apply([&__r]<typename... _Tp>(_Tp&&... __args) {
> +	    return ranges::to<_Cont>(std::forward<_Rg>(__r),
> +				     std::forward<_Tp>(__args)...);
> +	  }, std::move(_M_bound_args));
> +	}
> +
> +      template<typename _Rg>
> +	constexpr auto
> +	operator()(_Rg&& __r) const &&
> +	{
> +	  return std::apply([&__r]<typename... _Tp>(_Tp&&... __args) {
> +	    return ranges::to<_Cont>(std::forward<_Rg>(__r),
> +				     std::forward<_Tp>(__args)...);
> +	  }, std::move(_M_bound_args));
> +	}
> +    };
> +} // namespace __detail
> +/// @endcond
> +
> +  /// ranges::to adaptor for converting a range to a deduced container type.
> +  /**
> +   * @tparam _Cont A container template.
> +   * @param __args... Arguments to pass to the container constructor.
> +   * @since C++23
> +   *
> +   * This range adaptor returns a range adaptor closure object that converts
> +   * a range to a specialization of the `_Cont` class template. The specific
> +   * specialization of `_Cont` to be used is deduced automatically.
> +   *
> +   * For example, `some_view | std::ranges::to<std::vector>(Alloc<int>{})`
> +   * will convert the view to `std::vector<T, Alloc<T>>`, where `T` is the
> +   * view's value type, i.e. `std::ranges::range_value_t<decltype(some_view)>`.
> +   *
> +   * Additional constructor arguments for the container can be supplied, e.g.
> +   * `r | std::ranges::to<std::vector>(an_allocator)`.
> +   */
> +  template<template<typename...> typename _Cont, typename... _Args>
> +    constexpr __detail::_ToClosure2<_Cont, _Args...>
> +    to [[nodiscard]] (_Args&&... __args)
> +    { return {std::forward<_Args>(__args)...}; }
> +
> +} // namespace ranges
> +#endif // __cpp_lib_ranges_to_container
> +
>  _GLIBCXX_END_NAMESPACE_VERSION
> -} // namespace
> +} // namespace std
>  #endif // library concepts
>  #endif // C++2a
>  #endif /* _GLIBCXX_RANGES */
> diff --git a/libstdc++-v3/testsuite/std/ranges/conv/1.cc b/libstdc++-v3/testsuite/std/ranges/conv/1.cc
> new file mode 100644
> index 00000000000..0032cf32688
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/std/ranges/conv/1.cc
> @@ -0,0 +1,369 @@
> +// { dg-do run { target c++23 } }
> +
> +// C++23 26.5.7 Range conversions [range.utility.conv]
> +
> +#include <ranges>
> +#include <vector>
> +#include <string>
> +#include <deque>
> +#include <list>
> +#include <forward_list>
> +#include <map>
> +#include <testsuite_hooks.h>
> +#include <testsuite_allocator.h>
> +#include <testsuite_iterators.h>
> +
> +void
> +test_p1206r7_examples()
> +{
> +  using Alloc = __gnu_test::uneq_allocator<int>;
> +  const Alloc alloc(303);
> +  const std::map<int, const char*> m{{1, "one"}, {2, "two"}, {3, "three"}};
> +  namespace ranges = std::ranges;
> +
> +  auto l = std::views::iota(1, 10);
> +  // create a vector with the elements of l
> +  auto vec = ranges::to<std::vector<int>>(l); // or vector{std::from_range, l};
> +  //Specify an allocator
> +  auto b = ranges::to<std::vector<int, Alloc>>(l, alloc); // or vector{std::from_range, l, alloc};
> +  //deducing value_type
> +  auto c = ranges::to<std::vector>(l);
> +  // explicit conversion int -> long
> +  auto d = ranges::to<std::vector<long>>(l);
> +  //Supports converting associative container to sequence containers
> +  auto f = ranges::to<std::vector>(m);
> +  //Supports converting sequence containers to associative ones
> +  auto g = ranges::to<std::map>(f);
> +  //Pipe syntax
> +  auto g2 = l | ranges::views::take(42) | ranges::to<std::vector>();
> +  //Pipe syntax with allocator
> +  auto h = l | ranges::views::take(42) | ranges::to<std::vector>(alloc);
> +  //The pipe syntax also support specifying the type and conversions
> +  auto i = l | ranges::views::take(42) | ranges::to<std::vector<long>>();
> +  // Nested ranges
> +  std::list<std::forward_list<int>> lst = {{0, 1, 2, 3}, {4, 5, 6, 7}};
> +  auto vec1 = ranges::to<std::vector<std::vector<int>>>(lst);
> +  auto vec2 = ranges::to<std::vector<std::deque<double>>>(lst);
> +
> +  VERIFY( vec == std::vector<int>(std::ranges::begin(l), std::ranges::end(l)) );
> +  static_assert(std::is_same_v<decltype(b), std::vector<int, Alloc>>);
> +  VERIFY( b == (std::vector<int, Alloc>(vec.begin(), vec.end())) );
> +  VERIFY( b.get_allocator() == alloc );
> +  static_assert(std::is_same_v<decltype(c), std::vector<int>>);
> +  VERIFY( c == vec );
> +  static_assert(std::is_same_v<decltype(d), std::vector<long>>);
> +  VERIFY( d == std::vector<long>(vec.begin(), vec.end()) );
> +  VERIFY( g == m );
> +  static_assert(std::is_same_v<decltype(g2), std::vector<int>>);
> +  VERIFY( g2 == vec );
> +  static_assert(std::is_same_v<decltype(h), std::vector<int, Alloc>>);
> +  VERIFY( h == b );
> +  VERIFY( h.get_allocator() == alloc );
> +  VERIFY( i == d );
> +  static_assert(std::is_same_v<decltype(vec1), std::vector<std::vector<int>>>);
> +  VERIFY( vec1[1][1] == 5 );
> +  static_assert(std::is_same_v<decltype(vec2), std::vector<std::deque<double>>>);
> +  VERIFY( vec2[1][2] == 6.0 );
> +}
> +
> +void
> +test_example_1()
> +{
> +  using namespace std;
> +  using ranges::to;
> +
> +  // Example 1 from C++23 [range.utility.conv.general]
> +  string_view str = "the quick brown fox";
> +  auto words = views::split(str, ' ') | to<vector<string>>();
> +
> +  VERIFY( (is_same_v<decltype(words), vector<string>>) );
> +  VERIFY( words == vector<string>({"the", "quick", "brown", "fox"}) );
> +}
> +
> +template<typename C>
> +struct Cont1
> +{
> +  template<typename R, typename... Args>
> +    requires std::constructible_from<C, R&, Args&...>
> +    Cont1(R&& r, Args&&... args)
> +    : c(r, args...)
> +    { }
> +
> +  typename C::iterator begin();
> +  typename C::iterator end();
> +
> +  C c;
> +};
> +
> +void
> +test_2_1_1()
> +{
> +  // (2.1.1) constructible_from<C, R, Args...>
> +
> +  std::vector<int> v{1, 2, 3, 4};
> +  auto v2 = std::ranges::to<std::vector<int>>(v);
> +  static_assert(std::is_same_v<decltype(v2), decltype(v)>);
> +  VERIFY( v2 == v );
> +
> +  std::initializer_list<int> il{5, 6, 7, 8};
> +  v2 = std::ranges::to<std::vector<int>>(il);
> +  VERIFY( v2 == std::vector<int>(il) );
> +
> +  v2 = std::ranges::to<std::vector<int>>(il, std::allocator<int>{});
> +  VERIFY( v2 == std::vector<int>(il) );
> +
> +  using Alloc = __gnu_test::uneq_allocator<int>;
> +  using V = std::vector<int, Alloc>;
> +
> +  V v3({10, 11, 12, 13}, Alloc(14));
> +  auto v4 = std::ranges::to<V>(v3);
> +  static_assert(std::is_same_v<decltype(v4), V>);
> +  VERIFY( v4 == v3 );
> +  VERIFY( v4.get_allocator() == v3.get_allocator() );
> +
> +  auto v5 = std::ranges::to<V>(v3, Alloc(33));
> +  VERIFY( v5 == v3 );
> +  VERIFY( v5.get_allocator() == Alloc(33) );
> +
> +  auto v6 = std::ranges::to<V>(il, Alloc(44));
> +  VERIFY( v6 == V(il) );
> +  VERIFY( v6.get_allocator() == Alloc(44) );
> +
> +  auto c = std::ranges::to<Cont1<V>>(V{1, 2, 3});
> +  static_assert(std::is_same_v<decltype(c), Cont1<V>>);
> +  VERIFY( c.c == V({1, 2, 3}) );
> +
> +  auto c2 = std::ranges::to<Cont1<V>>(V{4, 5, 6}, Alloc(55));
> +  static_assert(std::is_same_v<decltype(c2), Cont1<V>>);
> +  VERIFY( c2.c == V({4, 5, 6}) );
> +  VERIFY( c2.c.get_allocator() == Alloc(55) );
> +
> +  auto c3 = std::ranges::to<Cont1<V>>(il, Alloc(66));
> +  static_assert(std::is_same_v<decltype(c2), Cont1<V>>);
> +  VERIFY( c3.c == V(v2.begin(), v2.end()) );
> +  VERIFY( c3.c.get_allocator() == Alloc(66) );
> +}
> +
> +template<typename C>
> +struct Cont2
> +{
> +  template<typename R, typename... Args>
> +    requires std::constructible_from<C, R&, Args&...>
> +    Cont2(std::from_range_t, R&& r, Args&&... args)
> +    : c(r, args...)
> +    { }
> +
> +  typename C::iterator begin();
> +  typename C::iterator end();
> +
> +  C c;
> +};
> +
> +void
> +test_2_1_2()
> +{
> +  // (2.1.2) constructible_from<C, from_range_t, R, Args...>
> +
> +  using Alloc = __gnu_test::uneq_allocator<int>;
> +  using V = std::vector<int, Alloc>;
> +  auto c = std::ranges::to<Cont2<V>>(V{1, 2, 3});
> +  static_assert(std::is_same_v<decltype(c), Cont2<V>>);
> +  VERIFY( c.c == V({1, 2, 3}) );
> +
> +  auto c2 = std::ranges::to<Cont2<V>>(V{4, 5, 6}, Alloc(7));
> +  static_assert(std::is_same_v<decltype(c2), Cont2<V>>);
> +  VERIFY( c2.c == V({4, 5, 6}) );
> +  VERIFY( c2.c.get_allocator() == Alloc(7) );
> +}
> +
> +template<typename C>
> +struct Cont3
> +{
> +  template<typename It, typename Sent, typename... Args>
> +    requires std::same_as<It, Sent>
> +    && std::constructible_from<C, It&, Sent&, Args&...>
> +    Cont3(It first, Sent last, Args&&... args)
> +    : c(first, last, args...)
> +    { }
> +
> +  typename C::iterator begin();
> +  typename C::iterator end();
> +
> +  C c;
> +};
> +
> +void
> +test_2_1_3()
> +{
> +  // (2.1.3) constructible_from<C, iterator_t<R>, sentinel_t<R<, Args...>
> +
> +  using Alloc = __gnu_test::uneq_allocator<int>;
> +  using V = std::vector<int, Alloc>;
> +
> +  std::list<unsigned> l{1u, 2u, 3u};
> +  auto c = std::ranges::to<Cont3<V>>(l);
> +  static_assert(std::is_same_v<decltype(c), Cont3<V>>);
> +  VERIFY( c.c == V(l.begin(), l.end()) );
> +
> +  std::list<long> l2{4l, 5l, 6l};
> +  auto c2 = std::ranges::to<Cont3<V>>(l2, Alloc(78));
> +  static_assert(std::is_same_v<decltype(c2), Cont3<V>>);
> +  VERIFY( c2.c == V(l2.begin(), l2.end()) );
> +  VERIFY( c2.c.get_allocator() == Alloc(78) );
> +}
> +
> +template<typename C, bool UsePushBack = true>
> +struct Cont4
> +{
> +  using value_type = typename C::value_type;
> +
> +  // Only support construction with no args or an allocator.
> +  // This forces the use of either push_back or insert to fill the container.
> +  Cont4() { }
> +  Cont4(typename C::allocator_type a) : c(a) { }
> +
> +  // Required to satisfy range
> +  typename C::iterator begin() { return c.begin(); }
> +  typename C::iterator end() { return c.end(); }
> +
> +  // Satisfying container-insertable requires either this ...
> +  template<typename T>
> +    requires UsePushBack
> +    && requires(C& c, T&& t) { c.push_back(std::forward<T>(t)); }
> +    void
> +    push_back(T&& t)
> +    {
> +      c.push_back(std::forward<T>(t));
> +      used_push_back = true;
> +    }
> +
> +  // ... or this:
> +  template<typename T>
> +    typename C::iterator
> +    insert(typename C::iterator, T&& t)
> +    {
> +      used_push_back = false;
> +      return c.insert(c.end(), std::forward<T>(t));
> +    }
> +
> +  // Required to satisfy reservable-container
> +  void
> +  reserve(typename C::size_type n) requires requires(C& c) { c.reserve(n); }
> +  {
> +    c.reserve(n);
> +    used_reserve = true;
> +  }
> +
> +  // Required to satisfy reservable-container
> +  auto size() const { return c.size(); }
> +
> +  // Required to satisfy reservable-container
> +  auto capacity() const requires requires(C& c) { c.capacity(); }
> +  { return c.capacity(); }
> +
> +  // Required to satisfy reservable-container
> +  auto max_size() const { return c.max_size(); }
> +
> +  C c;
> +  bool used_push_back = false;
> +  bool used_reserve = false;
> +};
> +
> +void
> +test_2_1_4()
> +{
> +  // (2.1.4) constructible_from<C, Args...> and
> +  // container-insertable<C, range_reference_t<R>>
> +
> +  using Alloc = __gnu_test::uneq_allocator<int>;
> +  using V = std::vector<int, Alloc>;
> +
> +  std::list<unsigned> l{1u, 2u, 3u};
> +  auto c = std::ranges::to<Cont4<V>>(l);
> +  static_assert(std::is_same_v<decltype(c), Cont4<V>>);
> +  VERIFY( c.c == V(l.begin(), l.end()) );
> +  VERIFY( c.used_push_back );
> +  VERIFY( c.used_reserve );
> +
> +  std::list<long> l2{4l, 5l, 6l};
> +  auto c2 = std::ranges::to<Cont4<V>>(l2, Alloc(78));
> +  static_assert(std::is_same_v<decltype(c2), Cont4<V>>);
> +  VERIFY( c2.c == V(l2.begin(), l2.end()) );
> +  VERIFY( c2.c.get_allocator() == Alloc(78) );
> +  VERIFY( c2.used_push_back );
> +  VERIFY( c2.used_reserve );
> +
> +  using Alloc2 = __gnu_test::uneq_allocator<short>;
> +  using List = std::list<short, Alloc2>;
> +  auto c3 = std::ranges::to<Cont4<List>>(c.c, Alloc2(99));
> +  static_assert(std::is_same_v<decltype(c3), Cont4<List>>);
> +  VERIFY( c3.c == List(l.begin(), l.end()) );
> +  VERIFY( c3.c.get_allocator() == Alloc(99) );
> +  VERIFY( c3.used_push_back );
> +  VERIFY( ! c3.used_reserve );
> +
> +  auto c4 = std::ranges::to<Cont4<List, false>>(c.c, Alloc2(111));
> +  static_assert(std::is_same_v<decltype(c4), Cont4<List, false>>);
> +  VERIFY( c4.c == List(l.begin(), l.end()) );
> +  VERIFY( c4.c.get_allocator() == Alloc(111) );
> +  VERIFY( ! c4.used_push_back );
> +  VERIFY( ! c4.used_reserve );
> +}
> +
> +void
> +test_2_2()
> +{
> +  // (2.2) input_range<range_reference_t<R>>
> +
> +  std::string s1[]{ "one", "two", "three", "four" };
> +  std::string s2[]{ "V", "VI", "VII", "VIII" };
> +  std::string s3[]{ "0x09", "0x0a", "0x0b", "0x0c" };
> +  using R = __gnu_test::test_input_range<std::string>;
> +  R input_ranges[]{R(s1), R(s2), R(s3)};
> +  __gnu_test::test_input_range<R> rr(input_ranges);
> +  namespace pmr = std::pmr;
> +  __gnu_test::memory_resource res;
> +#if _GLIBCXX_USE_CXX11_ABI
> +  auto vvs = std::ranges::to<pmr::vector<pmr::vector<pmr::string>>>(rr, &res);
> +  auto str_alloc = pmr::polymorphic_allocator<char>(&res);
> +#else
> +  auto vvs = std::ranges::to<pmr::vector<pmr::vector<std::string>>>(rr, &res);
> +  auto str_alloc = std::allocator<char>();
> +#endif
> +  VERIFY( vvs[1][1] == "VI" );
> +  VERIFY( vvs[2][2] == "0x0b" );
> +  VERIFY( vvs[0].get_allocator().resource() == &res );
> +  VERIFY( vvs[2][2].get_allocator() == str_alloc );
> +}
> +
> +void
> +test_lwg3984()
> +{
> +  std::vector<std::vector<int>> v;
> +  auto r = std::views::all(std::move(v));
> +  auto l = std::ranges::to<std::list<std::list<int>>>(r);
> +  VERIFY(l.empty());
> +}
> +
> +void
> +test_nodiscard()
> +{
> +  std::vector<int> v;
> +  std::ranges::to<std::vector<long>>(v); // { dg-warning "ignoring return" }
> +  std::ranges::to<std::vector>(v);       // { dg-warning "ignoring return" }
> +  std::ranges::to<std::vector<long>>();  // { dg-warning "ignoring return" }
> +  std::ranges::to<std::vector>();        // { dg-warning "ignoring return" }
> +}
> +
> +int main()
> +{
> +  test_p1206r7_examples();
> +  test_example_1();
> +  test_2_1_1();
> +  test_2_1_2();
> +  test_2_1_3();
> +  test_2_1_4();
> +  test_2_2();
> +  test_lwg3984();
> +  test_nodiscard();
> +}
> diff --git a/libstdc++-v3/testsuite/std/ranges/conv/2_neg.cc b/libstdc++-v3/testsuite/std/ranges/conv/2_neg.cc
> new file mode 100644
> index 00000000000..1e5f6f18408
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/std/ranges/conv/2_neg.cc
> @@ -0,0 +1,24 @@
> +// { dg-do compile { target c++23 } }
> +
> +// C++23 26.5.7 Range conversions [range.utility.conv]
> +
> +#include <ranges>
> +#include <vector>
> +#include <testsuite_allocator.h>
> +
> +void
> +test_2_1_5()
> +{
> +  // (2.1.5) Otherwise, the program is ill-formed.
> +
> +  using Alloc = __gnu_test::uneq_allocator<int>;
> +  using Vec = std::vector<int, Alloc>;
> +
> +  std::vector<int> v;
> +  (void) std::ranges::to<Vec>(v, v.get_allocator()); // { dg-error "here" }
> +
> +  (void) std::ranges::to<Vec>(Vec{}, 1, 2, 3, 4, 5, 6); // { dg-error "here" }
> +}
> +
> +// { dg-error "static assertion failed" "" { target *-*-* } 0 }
> +// { dg-prune-output "no matching function" }
> diff --git a/libstdc++-v3/testsuite/std/ranges/conv/version.cc b/libstdc++-v3/testsuite/std/ranges/conv/version.cc
> new file mode 100644
> index 00000000000..33736807eb4
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/std/ranges/conv/version.cc
> @@ -0,0 +1,19 @@
> +// { dg-do preprocess { target c++23 } }
> +// { dg-add-options no_pch }
> +
> +#include <ranges>
> +
> +#ifndef __cpp_lib_ranges_to_container
> +# error "Feature test macro for ranges_to_container is missing in <ranges>"
> +#elif __cpp_lib_ranges_to_container < 202202L
> +# error "Feature test macro for ranges_to_container has wrong value in <ranges>"
> +#endif
> +
> +#undef __cpp_lib_ranges_to_container
> +#include <version>
> +
> +#ifndef __cpp_lib_ranges_to_container
> +# error "Feature test macro for ranges_to_container is missing in <version>"
> +#elif __cpp_lib_ranges_to_container < 202202L
> +# error "Feature test macro for ranges_to_container has wrong value in <version>"
> +#endif
> -- 
> 2.42.0
> 
> 


  parent reply	other threads:[~2023-11-29 16:28 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-11-17 15:49 [PATCH] " Jonathan Wakely
2023-11-23 17:51 ` [committed v2] " Jonathan Wakely
2023-11-28  0:02   ` Hans-Peter Nilsson
2023-11-29 16:28   ` Patrick Palka [this message]
2023-11-30 15:49     ` Jonathan Wakely

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=cc39b9c5-e3aa-6296-0f37-4a14840b8d6e@idea \
    --to=ppalka@redhat.com \
    --cc=gcc-patches@gcc.gnu.org \
    --cc=jwakely@redhat.com \
    --cc=libstdc++@gcc.gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).