[v2] libstdc++: Fix views::zip_transform constraints for empty range pack [PR111138]

Message ID 20250307172413.306302-1-tkaminsk@redhat.com
State Committed
Commit 5abe571e0276fafcc6eed27c27abb28943e67c6f
Headers
Series [v2] libstdc++: Fix views::zip_transform constraints for empty range pack [PR111138] |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_simplebootstrap_build--master-arm-bootstrap success Build passed
linaro-tcwg-bot/tcwg_gcc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_simplebootstrap_build--master-aarch64-bootstrap success Build passed
linaro-tcwg-bot/tcwg_gcc_check--master-arm success Test passed

Commit Message

Tomasz Kamiński March 7, 2025, 4:44 p.m. UTC
  Add missing move_constructible && regular_invocable constrains on functor type,
and is_object on functor result type for invocations of views::zip_transform without range arguments.

	PR libstdc++/111138

libstdc++-v3/ChangeLog:

	* include/std/ranges (_ZipTransform::operator()):
	Create separate overload for calls with empty range pack,
	and add move_constructible, regular_invocable and
	is_object_v<invoke_result_t<...>>> constraints.
	* testsuite/std/ranges/zip_transform/1.cc: New tests
---
 Move the empty-ranges case into separate `operator()`.
 Add missing `is_object<invoke_result_t<....>>`.

 Tested on x86_64-linux. OK for trunk?

 libstdc++-v3/include/std/ranges               | 16 +++++++++-----
 .../testsuite/std/ranges/zip_transform/1.cc   | 21 +++++++++++++++++++
 2 files changed, 32 insertions(+), 5 deletions(-)
  

Comments

Patrick Palka March 7, 2025, 6:11 p.m. UTC | #1
On Fri, 7 Mar 2025, Tomasz Kamiński wrote:

> Add missing move_constructible && regular_invocable constrains on functor type,
> and is_object on functor result type for invocations of views::zip_transform without range arguments.
> 
> 	PR libstdc++/111138
> 
> libstdc++-v3/ChangeLog:
> 
> 	* include/std/ranges (_ZipTransform::operator()):
> 	Create separate overload for calls with empty range pack,
> 	and add move_constructible, regular_invocable and
> 	is_object_v<invoke_result_t<...>>> constraints.
> 	* testsuite/std/ranges/zip_transform/1.cc: New tests

LGTM

> ---
>  Move the empty-ranges case into separate `operator()`.
>  Add missing `is_object<invoke_result_t<....>>`.
> 
>  Tested on x86_64-linux. OK for trunk?
> 
>  libstdc++-v3/include/std/ranges               | 16 +++++++++-----
>  .../testsuite/std/ranges/zip_transform/1.cc   | 21 +++++++++++++++++++
>  2 files changed, 32 insertions(+), 5 deletions(-)
> 
> diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges
> index e21f5284b46..97d22a12dc6 100644
> --- a/libstdc++-v3/include/std/ranges
> +++ b/libstdc++-v3/include/std/ranges
> @@ -5331,15 +5331,21 @@ namespace views::__adaptor
>  
>      struct _ZipTransform
>      {
> +      template<typename _Fp>
> +	requires move_constructible<decay_t<_Fp>> && regular_invocable<decay_t<_Fp>&>
> +	  && is_object_v<decay_t<invoke_result_t<decay_t<_Fp>&>>>
> +	constexpr auto
> +	operator() [[nodiscard]] (_Fp&& __f) const
> +	{
> +	  return views::empty<decay_t<invoke_result_t<decay_t<_Fp>&>>>;
> +	}
> +
>        template<typename _Fp, typename... _Ts>
> -	requires (sizeof...(_Ts) == 0) || __detail::__can_zip_transform_view<_Fp, _Ts...>
> +	requires (sizeof...(_Ts) != 0) && __detail::__can_zip_transform_view<_Fp, _Ts...>
>  	constexpr auto
>  	operator() [[nodiscard]] (_Fp&& __f, _Ts&&... __ts) const
>  	{
> -	  if constexpr (sizeof...(_Ts) == 0)
> -	    return views::empty<decay_t<invoke_result_t<decay_t<_Fp>&>>>;
> -	  else
> -	    return zip_transform_view(std::forward<_Fp>(__f), std::forward<_Ts>(__ts)...);
> +          return zip_transform_view(std::forward<_Fp>(__f), std::forward<_Ts>(__ts)...);
>  	}
>      };
>  
> diff --git a/libstdc++-v3/testsuite/std/ranges/zip_transform/1.cc b/libstdc++-v3/testsuite/std/ranges/zip_transform/1.cc
> index 20abdcba0f8..9a0ad3814e6 100644
> --- a/libstdc++-v3/testsuite/std/ranges/zip_transform/1.cc
> +++ b/libstdc++-v3/testsuite/std/ranges/zip_transform/1.cc
> @@ -9,6 +9,23 @@
>  namespace ranges = std::ranges;
>  namespace views = std::views;
>  
> +template<typename T>
> +concept can_zip_transform = requires (T t) {
> +  views::zip_transform(std::forward<T>(t));
> +};
> +
> +static_assert(!can_zip_transform<int>);
> +
> +struct NonMovable {
> +  NonMovable(NonMovable&&) = delete;
> +};
> +
> +static_assert(!can_zip_transform<NonMovable>);
> +static_assert(!can_zip_transform<NonMovable&>);
> +
> +static_assert(!can_zip_transform<void(*)()>);
> +static_assert(can_zip_transform<int(&(*)())[3]>);
> +
>  constexpr bool
>  test01()
>  {
> @@ -46,6 +63,10 @@ test01()
>    VERIFY( ranges::size(z3) == 3 );
>    VERIFY( ranges::equal(z3, (int[]){3, 6, 9}) );
>  
> +  auto z4 = views::zip_transform([] () { return 1; });
> +  VERIFY( ranges::size(z4) == 0 );
> +  static_assert( std::same_as<ranges::range_value_t<decltype(z4)>, int> );
> +
>    return true;
>  }
>  
> -- 
> 2.48.1
> 
>
  
Jonathan Wakely March 14, 2025, 10:51 a.m. UTC | #2
On 07/03/25 17:44 +0100, Tomasz Kamiński wrote:
>Add missing move_constructible && regular_invocable constrains on functor type,
>and is_object on functor result type for invocations of views::zip_transform without range arguments.
>
>	PR libstdc++/111138
>
>libstdc++-v3/ChangeLog:
>
>	* include/std/ranges (_ZipTransform::operator()):
>	Create separate overload for calls with empty range pack,
>	and add move_constructible, regular_invocable and
>	is_object_v<invoke_result_t<...>>> constraints.
>	* testsuite/std/ranges/zip_transform/1.cc: New tests
>---
> Move the empty-ranges case into separate `operator()`.
> Add missing `is_object<invoke_result_t<....>>`.
>
> Tested on x86_64-linux. OK for trunk?
>
> libstdc++-v3/include/std/ranges               | 16 +++++++++-----
> .../testsuite/std/ranges/zip_transform/1.cc   | 21 +++++++++++++++++++
> 2 files changed, 32 insertions(+), 5 deletions(-)
>
>diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges
>index e21f5284b46..97d22a12dc6 100644
>--- a/libstdc++-v3/include/std/ranges
>+++ b/libstdc++-v3/include/std/ranges
>@@ -5331,15 +5331,21 @@ namespace views::__adaptor
>
>     struct _ZipTransform
>     {
>+      template<typename _Fp>

I'd have been tempted to write this:

       template<typename _Fp, typename _Fd = decay_t<_Fp>>

and then use _Fd in the constraints and the body, but this way is fine
(and the downside of adding _Fd is that it would allow sociopaths to
call zip_transform.operator()<Foo&, Foo&>(foo) which would be even
worse than allowing zip_transform.operator()<Foo&>(foo) today!)

OK for trunk, without the change above. Thanks.


>+	requires move_constructible<decay_t<_Fp>> && regular_invocable<decay_t<_Fp>&>
>+	  && is_object_v<decay_t<invoke_result_t<decay_t<_Fp>&>>>
>+	constexpr auto
>+	operator() [[nodiscard]] (_Fp&& __f) const
>+	{
>+	  return views::empty<decay_t<invoke_result_t<decay_t<_Fp>&>>>;
>+	}
>+
>       template<typename _Fp, typename... _Ts>
>-	requires (sizeof...(_Ts) == 0) || __detail::__can_zip_transform_view<_Fp, _Ts...>
>+	requires (sizeof...(_Ts) != 0) && __detail::__can_zip_transform_view<_Fp, _Ts...>
> 	constexpr auto
> 	operator() [[nodiscard]] (_Fp&& __f, _Ts&&... __ts) const
> 	{
>-	  if constexpr (sizeof...(_Ts) == 0)
>-	    return views::empty<decay_t<invoke_result_t<decay_t<_Fp>&>>>;
>-	  else
>-	    return zip_transform_view(std::forward<_Fp>(__f), std::forward<_Ts>(__ts)...);
>+          return zip_transform_view(std::forward<_Fp>(__f), std::forward<_Ts>(__ts)...);
> 	}
>     };
>
>diff --git a/libstdc++-v3/testsuite/std/ranges/zip_transform/1.cc b/libstdc++-v3/testsuite/std/ranges/zip_transform/1.cc
>index 20abdcba0f8..9a0ad3814e6 100644
>--- a/libstdc++-v3/testsuite/std/ranges/zip_transform/1.cc
>+++ b/libstdc++-v3/testsuite/std/ranges/zip_transform/1.cc
>@@ -9,6 +9,23 @@
> namespace ranges = std::ranges;
> namespace views = std::views;
>
>+template<typename T>
>+concept can_zip_transform = requires (T t) {
>+  views::zip_transform(std::forward<T>(t));
>+};
>+
>+static_assert(!can_zip_transform<int>);
>+
>+struct NonMovable {
>+  NonMovable(NonMovable&&) = delete;
>+};
>+
>+static_assert(!can_zip_transform<NonMovable>);
>+static_assert(!can_zip_transform<NonMovable&>);
>+
>+static_assert(!can_zip_transform<void(*)()>);
>+static_assert(can_zip_transform<int(&(*)())[3]>);
>+
> constexpr bool
> test01()
> {
>@@ -46,6 +63,10 @@ test01()
>   VERIFY( ranges::size(z3) == 3 );
>   VERIFY( ranges::equal(z3, (int[]){3, 6, 9}) );
>
>+  auto z4 = views::zip_transform([] () { return 1; });
>+  VERIFY( ranges::size(z4) == 0 );
>+  static_assert( std::same_as<ranges::range_value_t<decltype(z4)>, int> );
>+
>   return true;
> }
>
>-- 
>2.48.1
>
  

Patch

diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges
index e21f5284b46..97d22a12dc6 100644
--- a/libstdc++-v3/include/std/ranges
+++ b/libstdc++-v3/include/std/ranges
@@ -5331,15 +5331,21 @@  namespace views::__adaptor
 
     struct _ZipTransform
     {
+      template<typename _Fp>
+	requires move_constructible<decay_t<_Fp>> && regular_invocable<decay_t<_Fp>&>
+	  && is_object_v<decay_t<invoke_result_t<decay_t<_Fp>&>>>
+	constexpr auto
+	operator() [[nodiscard]] (_Fp&& __f) const
+	{
+	  return views::empty<decay_t<invoke_result_t<decay_t<_Fp>&>>>;
+	}
+
       template<typename _Fp, typename... _Ts>
-	requires (sizeof...(_Ts) == 0) || __detail::__can_zip_transform_view<_Fp, _Ts...>
+	requires (sizeof...(_Ts) != 0) && __detail::__can_zip_transform_view<_Fp, _Ts...>
 	constexpr auto
 	operator() [[nodiscard]] (_Fp&& __f, _Ts&&... __ts) const
 	{
-	  if constexpr (sizeof...(_Ts) == 0)
-	    return views::empty<decay_t<invoke_result_t<decay_t<_Fp>&>>>;
-	  else
-	    return zip_transform_view(std::forward<_Fp>(__f), std::forward<_Ts>(__ts)...);
+          return zip_transform_view(std::forward<_Fp>(__f), std::forward<_Ts>(__ts)...);
 	}
     };
 
diff --git a/libstdc++-v3/testsuite/std/ranges/zip_transform/1.cc b/libstdc++-v3/testsuite/std/ranges/zip_transform/1.cc
index 20abdcba0f8..9a0ad3814e6 100644
--- a/libstdc++-v3/testsuite/std/ranges/zip_transform/1.cc
+++ b/libstdc++-v3/testsuite/std/ranges/zip_transform/1.cc
@@ -9,6 +9,23 @@ 
 namespace ranges = std::ranges;
 namespace views = std::views;
 
+template<typename T>
+concept can_zip_transform = requires (T t) {
+  views::zip_transform(std::forward<T>(t));
+};
+
+static_assert(!can_zip_transform<int>);
+
+struct NonMovable {
+  NonMovable(NonMovable&&) = delete;
+};
+
+static_assert(!can_zip_transform<NonMovable>);
+static_assert(!can_zip_transform<NonMovable&>);
+
+static_assert(!can_zip_transform<void(*)()>);
+static_assert(can_zip_transform<int(&(*)())[3]>);
+
 constexpr bool
 test01()
 {
@@ -46,6 +63,10 @@  test01()
   VERIFY( ranges::size(z3) == 3 );
   VERIFY( ranges::equal(z3, (int[]){3, 6, 9}) );
 
+  auto z4 = views::zip_transform([] () { return 1; });
+  VERIFY( ranges::size(z4) == 0 );
+  static_assert( std::same_as<ranges::range_value_t<decltype(z4)>, int> );
+
   return true;
 }