On Fri, 21 Nov 2025, Tomasz Kamiński wrote:
> The iterators for transform views (views::transform, views::zip_transform,
> and views::adjacent_transform) now store a function handle from (from
> __detail::__func_handle namespace) instead of a pointer to the view object
> (_M_parent).
>
> The following handle templates are defined in __func_handle namespace:
> * _Inplace: Used if the functor is a function pointer or standard operator
> wrapper (std::less<>, etc). The functor is stored directly in __func_handle
> and the iterator. This avoid double indirection through a pointer to the
> function pointer, and reduce the size of iterator for std wrappers.
> * _InplaceMemPtr: Used for data or function member pointers. This behaves
> similarly to _Inplace, but uses __invoke for invocations.
> * _StaticCall: Used if the operator() selected by overload resolution
> for the iterator reference is static. In this case, __func_handle is empty,
> reducing the iterator size.
> * _ViaPointer: Used for all remaining cases. __func_handle stores a pointer
> to the functor object stored within the view. Only for this template the
> cv-qualification of the functor template parameter (_Fn) relevant, and
> specialization for both const and mutable types are generated.
>
> As a consequence of these changes, the iterators of transform views no longer
> depend on the view object when handle other than __func_handle::_ViaPointer
> is used. The corresponding views are not marked as borrowed_range, as they
> are not marked as such in the standard.
>
> The use of _Inplace is limited to only set of pre-C++20 standard functors,
> as for once introduced for or later operator() was retroactively made static.
> We do not extent to to any empty fuctor, as it's oprator may still depend on
> value of this pointer as illustrated by test12 in
> std/ranges/adaptors/transform.cc test file.
>
> Storing function member pointers directly increases the iterator size in that
> specific case, but this is deemed beneficial for consistent treatment of
> function and data member pointers.
>
> To avoid materializing temporaries when the underlying iterator(s) return a
> prvalue, the _M_call_deref and _M_call_subscript methods of handles are
> defined to accept the iterator(s), which are then dereferenced as arguments
> of the functor.
>
> Using _Fd::operator()(*__iters...) inside requires expression is only
> supported since clang-20, however at the point of GCC-16 release, clang-22
> should be already available.
>
> libstdc++-v3/ChangeLog:
>
> * include/std/ranges (__detail::__is_std_op_template)
> (__detail::__is_std_op_wrapper, __func_handle::_Inplace)
> (__func_handle::_InplaceMemPtr, __func_handle::_ViaPointer)
> (__func_handle::_StaticCall, __detail::__func_handle_t): Define.
> (transform_view::_Iterator, zip_transform_view::_Iterator)
> (adjacent_tranform_view::_Iterator): Replace pointer to view
> (_M_parent) with pointer to functor (_M_fun). Update constructors
> to cosntruct _M_fun from *__parent->_M_fun. Define operator* and
> operator[] in terms of _M_call_deref and _M_call_subscript.
> * testsuite/std/ranges/adaptors/adjacent_transform/1.cc: New tests.
> * testsuite/std/ranges/adaptors/transform.cc: New tests.
> * testsuite/std/ranges/zip_transform/1.cc: New tests.
>
> Reviewed-by: Patrick Palka <[email protected]>
> Signed-off-by: Tomasz Kamiński <[email protected]>
> ---
> v3 aims to reduce cost of template specialization maatching, by:
> * implementing __is_std_op_wrapper in terms of __is_std_op_template
> that has explicit specialization for each operator wrapper,
> * defining separate specialization for each functor handle, instead
> of partial specialization. They are defined in __func_handle namespace
> to emphasize that they are related to each other.
>
> Tested on x86_64-linux. OK for trunk?
>
>
> libstdc++-v3/include/std/ranges | 261 ++++++++++++++++--
> .../ranges/adaptors/adjacent_transform/1.cc | 41 +++
> .../std/ranges/adaptors/transform.cc | 178 +++++++++++-
> .../testsuite/std/ranges/zip_transform/1.cc | 97 +++++++
> 4 files changed, 540 insertions(+), 37 deletions(-)
>
> diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges
> index ae57b9a0809..7c5ac931e31 100644
> --- a/libstdc++-v3/include/std/ranges
> +++ b/libstdc++-v3/include/std/ranges
> @@ -286,6 +286,185 @@ namespace ranges
> operator->() const noexcept
> { return std::__addressof(_M_value); }
> };
> +
> + template<template<typename> class>
> + constexpr bool __is_std_op_template = false;
> +
> + template<>
> + inline constexpr bool __is_std_op_template<std::equal_to> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::not_equal_to> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::greater> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::less> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::greater_equal> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::less_equal> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::plus> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::minus> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::multiplies> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::divides> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::modulus> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::negate> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::logical_and> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::logical_or> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::logical_not> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::bit_and> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::bit_or> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::bit_xor> = true;
> + template<>
> + inline constexpr bool __is_std_op_template<std::bit_not> = true;
> +
> +
> + template<typename _Fn>
> + constexpr bool __is_std_op_wrapper = false;
> +
> + template<template<typename> class _Ft, typename _Tp>
> + constexpr bool __is_std_op_wrapper<_Ft<_Tp>>
> + = __is_std_op_template<_Ft>;
> +
> + namespace __func_handle
> + {
> + template<typename _Fn>
> + struct _Inplace
> + {
> + _Inplace() = default;
> +
> + constexpr explicit
> + _Inplace(_Fn __func) noexcept
> + : _M_ptr(__func)
> + { }
> +
> + template<typename... _Iters>
> + constexpr decltype(auto)
> + _M_call_deref(const _Iters&... __iters) const
> + noexcept(noexcept(_M_ptr(*__iters...)))
> + { return _M_ptr(*__iters...); }
> +
> + template<typename _DistType, typename... _Iters>
> + constexpr decltype(auto)
> + _M_call_subscript(const _DistType __n, const _Iters&... __iters)
> const
> +
> noexcept(noexcept(_M_ptr(__iters[iter_difference_t<_Iters>(__n)]...)))
> + { return _M_ptr(__iters[iter_difference_t<_Iters>(__n)]...); }
> +
> + private:
> + [[no_unique_address]] _Fn _M_ptr = _Fn();
> + };
> +
> + template<typename _Fn>
> + struct _InplaceMemPtr
> + {
> + _InplaceMemPtr() = default;
> +
> + constexpr explicit
> + _InplaceMemPtr(_Fn __func) noexcept
> + : _M_ptr(__func)
> + {}
> +
> + template<typename... _Iters>
> + constexpr decltype(auto)
> + _M_call_deref(const _Iters&... __iters) const
> + noexcept(noexcept(std::__invoke(_M_ptr, *__iters...)))
> + { return std::__invoke(_M_ptr, *__iters...); }
> +
> + template<typename _DistType, typename... _Iters>
> + constexpr decltype(auto)
> + _M_call_subscript(const _DistType __n, const _Iters&... __iters)
> const
> + noexcept(noexcept(std::__invoke(_M_ptr,
> __iters[iter_difference_t<_Iters>(__n)]...)))
> + { return std::__invoke(_M_ptr,
> __iters[iter_difference_t<_Iters>(__n)]...); }
> +
> + private:
> + _Fn _M_ptr = nullptr;
> + };
> +
> + template<typename _Fn>
> + struct _ViaPointer
> + {
> + _ViaPointer() = default;
> +
> + constexpr explicit
> + _ViaPointer(_Fn& __func) noexcept
> + : _M_ptr(std::addressof(__func))
> + { }
> +
> + template<typename _Un>
> + requires (!is_const_v<_Un>) && is_same_v<const _Un, _Fn>
> + constexpr
> + _ViaPointer(_ViaPointer<_Un> __other) noexcept
> + : _M_ptr(__other._M_ptr)
> + { }
> +
> + template<typename... _Iters>
> + constexpr decltype(auto)
> + _M_call_deref(const _Iters&... __iters) const
> + noexcept(noexcept((*_M_ptr)(*__iters...)))
> + { return (*_M_ptr)(*__iters...); }
> +
> + template<typename _DistType, typename... _Iters>
> + constexpr decltype(auto)
> + _M_call_subscript(const _DistType __n, const _Iters&... __iters)
> const
> +
> noexcept(noexcept((*_M_ptr)(__iters[iter_difference_t<_Iters>(__n)]...)))
> + { return (*_M_ptr)(__iters[iter_difference_t<_Iters>(__n)]...); }
> +
> + private:
> + _Fn* _M_ptr = nullptr;
> +
> + template<typename>
> + friend struct _ViaPointer;
> + };
> +
> + template<typename _Fn>
> + struct _StaticCall
> + {
> + _StaticCall() = default;
> +
> + constexpr explicit
> + _StaticCall(const _Fn&) noexcept
> + {}
> +
> + template<typename... _Iters>
> + static constexpr decltype(auto)
> + _M_call_deref(const _Iters&... __iters)
> + noexcept(noexcept(_Fn::operator()(*__iters...)))
> + { return _Fn::operator()(*__iters...); }
> +
> + template<typename _DistType, typename... _Iters>
> + static constexpr decltype(auto)
> + _M_call_subscript(_DistType __n, const _Iters&... __iters)
> +
> noexcept(noexcept(_Fn::operator()(__iters[iter_difference_t<_Iters>(__n)]...)))
> + { return
> _Fn::operator()(__iters[iter_difference_t<_Iters>(__n)]...); }
> + };
> + } // __func_handle
> +
> + template<typename _Fn, typename... _Iters>
> + using __func_handle_t = decltype([] {
> + using _Fd = remove_cv_t<_Fn>;
> + if constexpr (is_member_pointer_v<_Fd>)
> + return __func_handle::_InplaceMemPtr<_Fd>();
> + else if constexpr (is_function_v<remove_pointer_t<_Fd>>)
> + return __func_handle::_Inplace<_Fd>();
> + else if constexpr (__is_std_op_wrapper<_Fd>)
> + return __func_handle::_Inplace<_Fd>();
> + else if constexpr (requires (const _Iters&... __iters)
> + { _Fd::operator()(*__iters...); })
> + return __func_handle::_StaticCall<_Fd>();
> + else
> + return __func_handle::_ViaPointer<_Fn>();
> + }());
> } // namespace __detail
>
> /// A view that contains exactly one element.
> @@ -1874,6 +2053,10 @@ namespace views::__adaptor
> private:
> using _Parent = __detail::__maybe_const_t<_Const, transform_view>;
> using _Base = transform_view::_Base<_Const>;
> + using _Base_iter = iterator_t<_Base>;
> + using _Func_handle = __detail::__func_handle_t<
> + __detail::__maybe_const_t<_Const, _Fp>,
> + _Base_iter>;
LGTM
FWIW we have 'using __detail::__maybe_const_t' inside the ranges
namespace so we don't need to write __maybe_const_t qualified in
<ranges> nowadays.
>
> static auto
> _S_iter_concept()
> @@ -1888,10 +2071,8 @@ namespace views::__adaptor
> return input_iterator_tag{};
> }
>
> - using _Base_iter = iterator_t<_Base>;
> -
> _Base_iter _M_current = _Base_iter();
> - _Parent* _M_parent = nullptr;
> + [[no_unique_address]] _Func_handle _M_fun;
>
> public:
> using iterator_concept = decltype(_S_iter_concept());
> @@ -1904,16 +2085,20 @@ namespace views::__adaptor
> _Iterator() requires default_initializable<_Base_iter> = default;
>
> constexpr
> - _Iterator(_Parent* __parent, _Base_iter __current)
> - : _M_current(std::move(__current)),
> - _M_parent(__parent)
> + _Iterator(_Func_handle __fun, _Base_iter __current)
> + : _M_current(std::move(__current)), _M_fun(__fun)
> { }
>
> + constexpr
> + _Iterator(_Parent* __parent, _Base_iter __current)
> + : _M_current(std::move(__current)), _M_fun(*__parent->_M_fun)
> + {}
> +
> constexpr
> _Iterator(_Iterator<!_Const> __i)
> requires _Const
> && convertible_to<iterator_t<_Vp>, _Base_iter>
> - : _M_current(std::move(__i._M_current)), _M_parent(__i._M_parent)
> + : _M_current(std::move(__i._M_current)), _M_fun(__i._M_fun)
> { }
>
> constexpr const _Base_iter&
> @@ -1926,8 +2111,8 @@ namespace views::__adaptor
>
> constexpr decltype(auto)
> operator*() const
> - noexcept(noexcept(std::__invoke(*_M_parent->_M_fun, *_M_current)))
> - { return std::__invoke(*_M_parent->_M_fun, *_M_current); }
> + noexcept(noexcept(_M_fun._M_call_deref(_M_current)))
> + { return _M_fun._M_call_deref(_M_current); }
>
> constexpr _Iterator&
> operator++()
> @@ -1980,7 +2165,7 @@ namespace views::__adaptor
> constexpr decltype(auto)
> operator[](difference_type __n) const
> requires random_access_range<_Base>
> - { return std::__invoke(*_M_parent->_M_fun, _M_current[__n]); }
> + { return _M_fun._M_call_subscript(__n, _M_current); }
>
> friend constexpr bool
> operator==(const _Iterator& __x, const _Iterator& __y)
> @@ -2018,17 +2203,17 @@ namespace views::__adaptor
> friend constexpr _Iterator
> operator+(_Iterator __i, difference_type __n)
> requires random_access_range<_Base>
> - { return {__i._M_parent, __i._M_current + __n}; }
> + { return {__i._M_fun, __i._M_current + __n}; }
>
> friend constexpr _Iterator
> operator+(difference_type __n, _Iterator __i)
> requires random_access_range<_Base>
> - { return {__i._M_parent, __i._M_current + __n}; }
> + { return {__i._M_fun, __i._M_current + __n}; }
>
> friend constexpr _Iterator
> operator-(_Iterator __i, difference_type __n)
> requires random_access_range<_Base>
> - { return {__i._M_parent, __i._M_current - __n}; }
> + { return {__i._M_fun, __i._M_current - __n}; }
>
> // _GLIBCXX_RESOLVE_LIB_DEFECTS
> // 3483. transform_view::iterator's difference is overconstrained
> @@ -5126,13 +5311,21 @@ namespace views::__adaptor
> class zip_transform_view<_Fp, _Vs...>::_Iterator : public
> __iter_cat<_Const>
> {
> using _Parent = __detail::__maybe_const_t<_Const, zip_transform_view>;
> + using _Fun_handle = __detail::__func_handle_t<
> + __detail::__maybe_const_t<_Const, _Fp>,
> + iterator_t<__detail::__maybe_const_t<_Const,
> _Vs>>...>;
>
> - _Parent* _M_parent = nullptr;
> + [[no_unique_address]] _Fun_handle _M_fun;
> __ziperator<_Const> _M_inner;
>
> + constexpr
> + _Iterator(_Fun_handle __fun, __ziperator<_Const> __inner)
> + : _M_fun(__fun), _M_inner(std::move(__inner))
> + { }
> +
> constexpr
> _Iterator(_Parent& __parent, __ziperator<_Const> __inner)
> - : _M_parent(std::__addressof(__parent)), _M_inner(std::move(__inner))
> + : _M_fun(*__parent._M_fun), _M_inner(std::move(__inner))
> { }
>
> friend class zip_transform_view;
> @@ -5150,14 +5343,14 @@ namespace views::__adaptor
> constexpr
> _Iterator(_Iterator<!_Const> __i)
> requires _Const && convertible_to<__ziperator<false>,
> __ziperator<_Const>>
> - : _M_parent(__i._M_parent), _M_inner(std::move(__i._M_inner))
> + : _M_fun(__i._M_fun), _M_inner(std::move(__i._M_inner))
> { }
>
> constexpr decltype(auto)
> operator*() const
> {
> return std::apply([&](const auto&... __iters) -> decltype(auto) {
> - return std::__invoke(*_M_parent->_M_fun, *__iters...);
> + return _M_fun._M_call_deref(__iters...);
> }, _M_inner._M_current);
> }
>
> @@ -5213,7 +5406,7 @@ namespace views::__adaptor
> operator[](difference_type __n) const requires
> random_access_range<_Base<_Const>>
> {
> return std::apply([&]<typename... _Is>(const _Is&... __iters) ->
> decltype(auto) {
> - return std::__invoke(*_M_parent->_M_fun,
> __iters[iter_difference_t<_Is>(__n)]...);
> + return _M_fun._M_call_subscript(__n, __iters...);
> }, _M_inner._M_current);
> }
>
> @@ -5230,17 +5423,17 @@ namespace views::__adaptor
> friend constexpr _Iterator
> operator+(const _Iterator& __i, difference_type __n)
> requires random_access_range<_Base<_Const>>
> - { return _Iterator(*__i._M_parent, __i._M_inner + __n); }
> + { return _Iterator(__i._M_fun, __i._M_inner + __n); }
>
> friend constexpr _Iterator
> operator+(difference_type __n, const _Iterator& __i)
> requires random_access_range<_Base<_Const>>
> - { return _Iterator(*__i._M_parent, __i._M_inner + __n); }
> + { return _Iterator(__i._M_fun, __i._M_inner + __n); }
>
> friend constexpr _Iterator
> operator-(const _Iterator& __i, difference_type __n)
> requires random_access_range<_Base<_Const>>
> - { return _Iterator(*__i._M_parent, __i._M_inner - __n); }
> + { return _Iterator(__i._M_fun, __i._M_inner - __n); }
>
> friend constexpr difference_type
> operator-(const _Iterator& __x, const _Iterator& __y)
> @@ -5807,13 +6000,23 @@ namespace views::__adaptor
> {
> using _Parent = __detail::__maybe_const_t<_Const,
> adjacent_transform_view>;
> using _Base = __detail::__maybe_const_t<_Const, _Vp>;
> + using _Fun_handle = decltype([]<size_t...
> _Ids>(std::index_sequence<_Ids...>) {
> + return __detail::__func_handle_t<
> + __detail::__maybe_const_t<_Const, _Fp>,
> + iterator_t<__detail::__maybe_const_t<(_Ids,
> _Const), _Vp>>...>();
> + }(make_index_sequence<_Nm>()));
>
> - _Parent* _M_parent = nullptr;
> + [[no_unique_address]] _Fun_handle _M_fun;
> _InnerIter<_Const> _M_inner;
>
> + constexpr
> + _Iterator(_Fun_handle __fun, _InnerIter<_Const> __inner)
> + : _M_fun(__fun), _M_inner(std::move(__inner))
> + { }
> +
> constexpr
> _Iterator(_Parent& __parent, _InnerIter<_Const> __inner)
> - : _M_parent(std::__addressof(__parent)), _M_inner(std::move(__inner))
> + : _M_fun(*__parent._M_fun), _M_inner(std::move(__inner))
> { }
>
> static auto
> @@ -5854,14 +6057,14 @@ namespace views::__adaptor
> constexpr
> _Iterator(_Iterator<!_Const> __i)
> requires _Const && convertible_to<_InnerIter<false>,
> _InnerIter<_Const>>
> - : _M_parent(__i._M_parent), _M_inner(std::move(__i._M_inner))
> + : _M_fun(__i._M_fun), _M_inner(std::move(__i._M_inner))
> { }
>
> constexpr decltype(auto)
> operator*() const
> {
> return std::apply([&](const auto&... __iters) -> decltype(auto) {
> - return std::__invoke(*_M_parent->_M_fun, *__iters...);
> + return _M_fun._M_call_deref(__iters...);
> }, _M_inner._M_current);
> }
>
> @@ -5913,7 +6116,7 @@ namespace views::__adaptor
> operator[](difference_type __n) const requires random_access_range<_Base>
> {
> return std::apply([&](const auto&... __iters) -> decltype(auto) {
> - return std::__invoke(*_M_parent->_M_fun, __iters[__n]...);
> + return _M_fun._M_call_subscript(__n, __iters...);
> }, _M_inner._M_current);
> }
>
> @@ -5950,17 +6153,17 @@ namespace views::__adaptor
> friend constexpr _Iterator
> operator+(const _Iterator& __i, difference_type __n)
> requires random_access_range<_Base>
> - { return _Iterator(*__i._M_parent, __i._M_inner + __n); }
> + { return _Iterator(__i._M_fun, __i._M_inner + __n); }
>
> friend constexpr _Iterator
> operator+(difference_type __n, const _Iterator& __i)
> requires random_access_range<_Base>
> - { return _Iterator(*__i._M_parent, __i._M_inner + __n); }
> + { return _Iterator(__i._M_fun, __i._M_inner + __n); }
>
> friend constexpr _Iterator
> operator-(const _Iterator& __i, difference_type __n)
> requires random_access_range<_Base>
> - { return _Iterator(*__i._M_parent, __i._M_inner - __n); }
> + { return _Iterator(__i._M_fun, __i._M_inner - __n); }
>
> friend constexpr difference_type
> operator-(const _Iterator& __x, const _Iterator& __y)
> diff --git
> a/libstdc++-v3/testsuite/std/ranges/adaptors/adjacent_transform/1.cc
> b/libstdc++-v3/testsuite/std/ranges/adaptors/adjacent_transform/1.cc
> index 772e4b3b6a0..6890618754b 100644
> --- a/libstdc++-v3/testsuite/std/ranges/adaptors/adjacent_transform/1.cc
> +++ b/libstdc++-v3/testsuite/std/ranges/adaptors/adjacent_transform/1.cc
> @@ -113,6 +113,47 @@ test04()
> static_assert( requires { x | views::pairwise_transform(move_only{}); } );
> }
>
> +template<size_t FuncSize, typename Fn>
> +void
> +test05(Fn f)
> +{
> + int x[] = {1,2,3,4,5,6};
> + auto v = x | views::pairwise_transform(f);
> + static_assert(sizeof(v.begin()) == 2*sizeof(int*) + FuncSize);
> +}
> +
> +void
> +test05all()
> +{
> + test05<0>(std::equal_to<>());
> + test05<0>(std::equal_to<>());
> + test05<0>(std::not_equal_to<>());
> + test05<0>(std::greater<>());
> + test05<0>(std::less<>());
> + test05<0>(std::greater_equal<>());
> + test05<0>(std::less_equal<>());
> +
> + test05<0>(std::ranges::equal_to());
> + test05<0>(std::ranges::not_equal_to());
> + test05<0>(std::ranges::greater());
> + test05<0>(std::ranges::less());
> + test05<0>(std::ranges::greater_equal());
> + test05<0>(std::ranges::less_equal());
> +
> + test05<0>(std::plus<>());
> + test05<0>(std::minus<>());
> + test05<0>(std::multiplies<>());
> + test05<0>(std::divides<>());
> + test05<0>(std::modulus<>());
> +
> + test05<0>(std::logical_and<>());
> + test05<0>(std::logical_or<>());
> +
> + test05<0>(std::bit_and<>());
> + test05<0>(std::bit_or<>());
> + test05<0>(std::bit_xor<>());
> +}
> +
> int
> main()
> {
> diff --git a/libstdc++-v3/testsuite/std/ranges/adaptors/transform.cc
> b/libstdc++-v3/testsuite/std/ranges/adaptors/transform.cc
> index 1788db1ce8d..3a21a1f0f9c 100644
> --- a/libstdc++-v3/testsuite/std/ranges/adaptors/transform.cc
> +++ b/libstdc++-v3/testsuite/std/ranges/adaptors/transform.cc
> @@ -18,6 +18,7 @@
> // { dg-do run { target c++20 } }
>
> #include <algorithm>
> +#include <cstdint>
> #include <ranges>
> #include <testsuite_hooks.h>
> #include <testsuite_iterators.h>
> @@ -28,12 +29,12 @@ using __gnu_test::random_access_iterator_wrapper;
> namespace ranges = std::ranges;
> namespace views = std::ranges::views;
>
> +template<typename Fn>
> void
> -test01()
> +test01(Fn f)
> {
> int x[] = {1,2,3,4,5};
> - auto is_odd = [] (int i) { return i%2==1; };
> - auto v = x | views::transform(is_odd);
> + auto v = x | views::transform(f);
> VERIFY( ranges::equal(v, (int[]){1,0,1,0,1}) );
> using R = decltype(v);
> static_assert(std::same_as<bool, decltype(*ranges::begin(v))>);
> @@ -42,30 +43,124 @@ test01()
> static_assert(ranges::random_access_range<R>);
> }
>
> +void
> +test01a()
> +{
> + auto is_odd = [] (int i) { return i%2==1; };
> + test01(is_odd);
> +}
> +
> +void
> +test01b()
> +{
> +#if __cpp_static_call_operator >= 202207L
> + auto is_odd = [] (int i) static { return i%2==1; };
> + test01(is_odd);
> +#endif
> +}
> +
> +void
> +test01c()
> +{
> + bool(*is_odd)(int) = [] (int i) { return i%2==1; };
> + test01(is_odd);
> +}
> +
> struct X
> {
> int i,j;
> + int& first() { return i; }
> };
>
> +template<size_t FuncSize, typename Fn>
> void
> -test02()
> +test02(Fn f)
> {
> X x[] = {{1,2},{3,4},{5,6},{7,8},{9,10}};
> test_range<X, random_access_iterator_wrapper> rx(x);
> - auto v = rx | views::transform(&X::i);
> + auto v = rx | views::transform(f);
> VERIFY( ranges::size(v) == 5 );
> VERIFY( ranges::distance(v.begin(), v.end()) == 5 );
> VERIFY( ranges::equal(v, (int[]){1,3,5,7,9}) );
> VERIFY( ranges::equal(v | views::reverse, (int[]){9,7,5,3,1}) );
> using R = decltype(v);
> + using It = ranges::iterator_t<R>;
> static_assert(std::same_as<int&, decltype(*ranges::begin(v))>);
> - static_assert(std::same_as<int, std::iter_value_t<ranges::iterator_t<R>>>);
> + static_assert(std::same_as<int, std::iter_value_t<It>>);
> + static_assert(sizeof(It) == sizeof(rx.begin()) + FuncSize);
> static_assert(ranges::view<R>);
> static_assert(ranges::sized_range<R>);
> static_assert(!ranges::common_range<R>);
> static_assert(ranges::random_access_range<R>);
> }
>
> +void
> +test02a()
> +{ test02<sizeof(int X::*)>(&X::i); }
> +
> +void
> +test02b()
> +{ test02<sizeof(int(X::*)())>(&X::first); }
> +
> +void
> +test02c()
> +{
> + auto first = [](X& x) -> int& { return x.i; };
> + test02<sizeof(void*)>(first);
> +}
> +
> +void
> +test02d()
> +{
> +#if __cpp_static_call_operator >= 202207L
> + auto first = [](X& x) static -> int& { return x.i; };
> + test02<0>(first);
> +#endif
> +}
> +
> +void
> +test02e()
> +{
> + int&(*fptr)(X&) = [](X& x) -> int& { return x.i; };
> + test02<sizeof(void(*)())>(fptr);
> +}
> +
> +void
> +test02f()
> +{
> +#if __cpp_static_call_operator >= 202207L
> + struct PickStatic
> + {
> + static constexpr int&
> + operator()(X& x)
> + { return x.i; }
> +
> + constexpr int
> + operator()(char*) const
> + { return 0; };
> + };
> + test02<0>(PickStatic{});
> +#endif
> +}
> +
> +void
> +test02g()
> +{
> +#if __cpp_static_call_operator >= 202207L
> + struct PickObject
> + {
> + constexpr int&
> + operator()(X& x) const
> + { return x.i; }
> +
> + static constexpr int
> + operator()(char*)
> + { return 0; };
> + };
> + test02<sizeof(void*)>(PickObject{});
> +#endif
> +}
> +
> void
> test03()
> {
> @@ -227,11 +322,75 @@ test11()
> static_assert(std::same_as<cat, std::random_access_iterator_tag>);
> }
>
> +void
> +test12()
> +{
> + struct Obfuscate
> + {
> + int operator()(int x) const
> + { return x + reinterpret_cast<std::uintptr_t>(this); }
> + };
> +
> + int x[]{1, 2, 3, 4, 5};
> + auto v = x | views::transform(Obfuscate{});
> + VERIFY( ranges::equal(v, v) );
> +};
> +
> +void
> +test13()
> +{
> +#if __cpp_static_call_operator >= 202207L
> + struct StaticWins {
> + static int operator()(int i) { return 0; }
> + int operator()(float f) const { return 1; }
> + };
> +
> + int x[]{1, 2, 3, 4, 5};
> + auto vs = x | views::transform(StaticWins{});
> + VERIFY( vs.front() == 0 );
> + static_assert( sizeof(vs.begin()) == sizeof(int*) );
> +
> + struct MemberWins {
> + static int operator()(float f) { return 0; }
> + int operator()(int i) const { return 1; }
> + };
> +
> + auto vm = x | views::transform(MemberWins{});
> + VERIFY( vm.front() == 1 );
> + static_assert( sizeof(vm.begin()) > sizeof(int*) );
> +#endif
> +}
> +
> +template<size_t FuncSize, typename Fn>
> +void
> +test14(Fn f)
> +{
> + int x[] = {1,2,3,4,5,6};
> + auto v = x | views::transform(std::negate<>());
> + static_assert(sizeof(v.begin()) == sizeof(int*) + FuncSize);
> +}
> +
> +void
> +test14all()
> +{
> + test14<0>(std::identity());
> + test14<0>(std::negate<>());
> + test14<0>(std::bit_not<>());
> +}
> +
> int
> main()
> {
> - test01();
> - test02();
> + test01a();
> + test01b();
> + test01c();
> + test02a();
> + test02b();
> + test02c();
> + test02d();
> + test02e();
> + test02f();
> + test02g();
> test03();
> test04();
> test05();
> @@ -241,4 +400,7 @@ main()
> test09();
> test10();
> test11();
> + test12();
> + test13();
> + test14all();
> }
> diff --git a/libstdc++-v3/testsuite/std/ranges/zip_transform/1.cc
> b/libstdc++-v3/testsuite/std/ranges/zip_transform/1.cc
> index 9a0ad3814e6..8524a146eaa 100644
> --- a/libstdc++-v3/testsuite/std/ranges/zip_transform/1.cc
> +++ b/libstdc++-v3/testsuite/std/ranges/zip_transform/1.cc
> @@ -132,6 +132,97 @@ test04()
> static_assert( requires { views::zip_transform(move_only{}, x, x); } );
> }
>
> +struct X
> +{
> + int i;
> + constexpr int add(int b) const
> + { return i+b; }
> +};
> +
> +template<size_t ExtraSize, typename Fn>
> +constexpr bool
> +test05(Fn f)
> +{
> + using namespace __gnu_test;
> + X x[] = {{1},{2},{3},{4},{5}};
> + int y[] = {500,400,300,200,100};
> + test_range<X, random_access_iterator_wrapper> rx(x);
> + test_range<int, random_access_iterator_wrapper> ry(y);
> +
> + auto v = views::zip_transform(f, rx, ry);
> + VERIFY( ranges::size(v) == 5 );
> + VERIFY( ranges::distance(v.begin(), v.end()) == 5 );
> + VERIFY( ranges::equal(v, (int[]){501,402,303,204,105}) );
> + VERIFY( ranges::equal(v | views::reverse, (int[]){105,204,303,402,501}) );
> + using R = decltype(v);
> + using It = ranges::iterator_t<R>;
> + static_assert(std::same_as<int, decltype(*ranges::begin(v))>);
> + static_assert(std::same_as<int, std::iter_value_t<It>>);
> + static_assert(sizeof(It) == sizeof(rx.begin()) + sizeof(ry.begin()) +
> ExtraSize);
> + static_assert(ranges::view<R>);
> + static_assert(ranges::sized_range<R>);
> + static_assert(ranges::common_range<R>);
> + static_assert(ranges::random_access_range<R>);
> + return true;
> +}
> +
> +constexpr bool
> +test05a()
> +{
> + auto add = [](const X& x, int v) { return x.i + v; };
> + return test05<sizeof(void*)>(add);
> +}
> +
> +constexpr bool
> +test05b()
> +{
> + auto add = [](const X& x, int v) static { return x.i + v; };
> + return test05<0>(add);
> +}
> +
> +constexpr bool
> +test05c()
> +{
> + int(*ptr)(const X&, int) = [](const X& x, int v) { return x.i + v; };
> + return test05<sizeof(void(*)())>(ptr);
> +}
> +
> +constexpr bool
> +test05d()
> +{ return test05<sizeof(int(X::*)())>(&X::add); }
> +
> +constexpr bool
> +test05e()
> +{
> + struct PickStatic
> + {
> + static constexpr int
> + operator()(const X& x1, int v)
> + { return x1.i + v; }
> +
> + constexpr int
> + operator()(int x, int y) const
> + { return x + y; };
> + };
> + return test05<0>(PickStatic{});
> +}
> +
> +constexpr bool
> +test05f()
> +{
> + struct PickObject
> + {
> + constexpr int
> + operator()(const X& x1, int v) const
> + { return x1.i + v; }
> +
> + static constexpr int
> + operator()(int x, int y)
> + { return x + y; };
> + };
> + return test05<sizeof(void*)>(PickObject{});
> +}
> +
> int
> main()
> {
> @@ -139,4 +230,10 @@ main()
> static_assert(test02());
> static_assert(test03());
> test04();
> + static_assert(test05a());
> + static_assert(test05b());
> + static_assert(test05c());
> + static_assert(test05d());
> + static_assert(test05e());
> + static_assert(test05f());
> }
> --
> 2.51.1
>
>