[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
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
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
>
>
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
>
@@ -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)...);
}
};
@@ -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;
}