[PATCH v2 1/2] libstdc++: Optimize functor storage for transform views iterators.
Tomasz Kaminski
tkaminsk@redhat.com
Thu Nov 20 18:29:11 GMT 2025
On Thu, Nov 20, 2025 at 6:16 PM Jonathan Wakely <jwakely@redhat.com> wrote:
> On Thu, 20 Nov 2025 at 11:39 +0100, Tomasz Kamiński wrote:
> >The iterators for transform views (views::transform, views::zip_transform,
> >and views::adjacent_transform) now store a __detail::__func_handle instead
> >of a pointer to the view object (_M_parent).
> >
> >The behavior of the __func_handle specialization depends on the
> _FuncHandleType
> >template parameter:
> >* _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
> specialization is
> > the cv-qualification of the functor template parameter (_Fn) relevant,
> and
> > both const and mutable specializations are generated.
> >
> >As a consequence of these changes, the iterators of transform views no
> longer
> >depend on the view object when __func_handle is specialized for values
> other
> >than _ViaPointer. 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 standard functors, as
> arbitrary
> >empty functor may still depend on value of this pointer in their
> operator(),
> >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 __func_handle
> 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/bits/utility.h (__is_specialization_of): Moved from
> > std/format.
> > * include/std/format (__is_specialization_of): Moved to
> > bits/utility.
> > * include/std/ranges (__detail::__is_std_op_wrapper)
> > (__detail::_FuncHandleType, __detail::__func_handle)
> > (__detail::func_handle_t): Define.
>
> Missing "__" on the name here.
>
> > (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.
> >
> >Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>
> >---
> >v2:
> >- stores inside of iterator selected set of standard operators
> >- renames the _FunctorType enum to _FuncHandleEnum and corresponding
> > enumertor values (_Inplace is also used for empty functors)
> >- do not define __func_handle primary template, and provide separate
> > specialization for _ViaTemplate (previously _Stateful)
> >- add test for all functors
> >
> >Tessted on x86_64-linux linux. *range*transform* additionally tested
> >with all standard modes.
> >
> > libstdc++-v3/include/bits/utility.h | 9 +
> > libstdc++-v3/include/std/format | 5 -
> > libstdc++-v3/include/std/ranges | 249 ++++++++++++++++--
> > .../ranges/adaptors/adjacent_transform/1.cc | 41 +++
> > .../std/ranges/adaptors/transform.cc | 185 ++++++++++++-
> > .../testsuite/std/ranges/zip_transform/1.cc | 101 +++++++
> > 6 files changed, 548 insertions(+), 42 deletions(-)
> >
> >diff --git a/libstdc++-v3/include/bits/utility.h
> b/libstdc++-v3/include/bits/utility.h
> >index 96ac69883f1..522a7a1d884 100644
> >--- a/libstdc++-v3/include/bits/utility.h
> >+++ b/libstdc++-v3/include/bits/utility.h
> >@@ -46,6 +46,15 @@ namespace std _GLIBCXX_VISIBILITY(default)
> > {
> > _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >
> >+/// @cond undocumented
> >+#if __cplusplus >= 201703L
> >+ template<typename _Tp, template<typename...> class _Class>
> >+ constexpr bool __is_specialization_of = false;
> >+ template<template<typename...> class _Class, typename... _Args>
> >+ constexpr bool __is_specialization_of<_Class<_Args...>, _Class> =
> true;
> >+#endif
> >+/// @endcond
> >+
> > /// Finds the size of a given tuple type.
> > template<typename _Tp>
> > struct tuple_size;
> >diff --git a/libstdc++-v3/include/std/format
> b/libstdc++-v3/include/std/format
> >index f64f35a202e..1e1ec1fb98f 100644
> >--- a/libstdc++-v3/include/std/format
> >+++ b/libstdc++-v3/include/std/format
> >@@ -417,11 +417,6 @@ namespace __format
> > };
> >
> > /// @cond undocumented
> >- template<typename _Tp, template<typename...> class _Class>
> >- constexpr bool __is_specialization_of = false;
> >- template<template<typename...> class _Class, typename... _Args>
> >- constexpr bool __is_specialization_of<_Class<_Args...>, _Class> =
> true;
> >-
> > namespace __format
> > {
> > // pre: first != last
> >diff --git a/libstdc++-v3/include/std/ranges
> b/libstdc++-v3/include/std/ranges
> >index ae57b9a0809..b384e5e45d3 100644
> >--- a/libstdc++-v3/include/std/ranges
> >+++ b/libstdc++-v3/include/std/ranges
> >@@ -286,6 +286,173 @@ namespace ranges
> > operator->() const noexcept
> > { return std::__addressof(_M_value); }
> > };
> >+
> >+ enum class _FuncHandleType
> >+ {
> >+ _Inplace,
> >+ _InplaceMemPtr,
> >+ _ViaPointer,
> >+ _StaticCall,
> >+ };
> >+
> >+ template<typename _Fn>
> >+ concept __is_std_op_wrapper = is_empty_v<_Fn> && (
> >+ is_same_v<_Fn, std::identity>
> >+ || __is_specialization_of<_Fn, std::equal_to>
> >+ || __is_specialization_of<_Fn, std::not_equal_to>
> >+ || __is_specialization_of<_Fn, std::greater>
> >+ || __is_specialization_of<_Fn, std::less>
> >+ || __is_specialization_of<_Fn, std::greater_equal>
> >+ || __is_specialization_of<_Fn, std::less_equal>
> >+ || is_same_v<_Fn, std::compare_three_way>
> >+ || is_same_v<_Fn, std::ranges::equal_to>
> >+ || is_same_v<_Fn, std::ranges::not_equal_to>
> >+ || is_same_v<_Fn, std::ranges::greater>
> >+ || is_same_v<_Fn, std::ranges::less>
> >+ || is_same_v<_Fn, std::ranges::greater_equal>
> >+ || is_same_v<_Fn, std::ranges::less_equal>
> >+ || __is_specialization_of<_Fn, std::plus>
> >+ || __is_specialization_of<_Fn, std::minus>
> >+ || __is_specialization_of<_Fn, std::multiplies>
> >+ || __is_specialization_of<_Fn, std::divides>
> >+ || __is_specialization_of<_Fn, std::modulus>
> >+ || __is_specialization_of<_Fn, std::negate>
> >+ || __is_specialization_of<_Fn, std::logical_and>
> >+ || __is_specialization_of<_Fn, std::logical_or>
> >+ || __is_specialization_of<_Fn, std::logical_not>
> >+ || __is_specialization_of<_Fn, std::bit_and>
> >+ || __is_specialization_of<_Fn, std::bit_or>
> >+ || __is_specialization_of<_Fn, std::bit_xor>
> >+ || __is_specialization_of<_Fn, std::bit_not>);
> >+
> >+ template<typename _Fn, _FuncHandleType __ft>
> >+ struct __func_handle;
> >+
> >+ template<typename _Fn>
> >+ struct __func_handle<_Fn, _FuncHandleType::_Inplace>
> >+ {
> >+ __func_handle() = default;
> >+
> >+ constexpr explicit
> >+ __func_handle(_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 __func_handle<_Fn, _FuncHandleType::_InplaceMemPtr>
> >+ {
> >+ __func_handle() = default;
> >+
> >+ constexpr explicit
> >+ __func_handle(_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 __func_handle<_Fn, _FuncHandleType::_ViaPointer>
> >+ {
> >+ __func_handle() = default;
> >+
> >+ constexpr explicit
> >+ __func_handle(_Fn& __func) noexcept
> >+ : _M_ptr(std::addressof(__func))
> >+ { }
> >+
> >+ template<typename _Un>
> >+ requires (!is_const_v<_Un>) && is_same_v<const _Un, _Fn>
> >+ constexpr
> >+ __func_handle(__func_handle<_Un, _FuncHandleType::_ViaPointer>
> __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, _FuncHandleType>
> >+ friend struct __func_handle;
> >+ };
> >+
> >+ template<typename _Fn>
> >+ struct __func_handle<_Fn, _FuncHandleType::_StaticCall>
> >+ {
> >+ __func_handle() = default;
> >+
> >+ constexpr explicit
> >+ __func_handle(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)]...); }
> >+ };
> >+
> >+ 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<_Fd, _FuncHandleType::_InplaceMemPtr>();
> >+ else if constexpr (is_function_v<remove_pointer_t<_Fd>>)
> >+ return __func_handle<_Fd, _FuncHandleType::_Inplace>();
> >+ else if constexpr (__is_std_op_wrapper<_Fd>)
> >+ return __func_handle<_Fd, _FuncHandleType::_Inplace>();
> >+ else if constexpr (requires (const _Iters&... __iters)
> >+ { _Fd::operator()(*__iters...); })
>
> Is it possible to have a type like:
>
>
> struct Stupid {
> void operator()(int);
> static void operator()(short);
> };
>
> where the expected use in transform_view would be to call it on an
> lvalue and use the non-static member function, but the
> Stupid::operator()(x) syntax is also valid. That would make your
> requires-expression above true, but now we'd change behaviour?
>
Yes, it is possible to do so, but requires expressions works correctly....
>
>
> >+ return __func_handle<_Fd, _FuncHandleType::_StaticCall>();
> >+ else
> >+ return __func_handle<_Fn, _FuncHandleType::_ViaPointer>();
> >+ }());
> > } // namespace __detail
> >
> > /// A view that contains exactly one element.
> >@@ -1874,6 +2041,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>;
> >
> > static auto
> > _S_iter_concept()
> >@@ -1888,10 +2059,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 +2073,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 +2099,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 +2153,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 +2191,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 +5299,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 +5331,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 +5394,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 +5411,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 +5988,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 +6045,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 +6104,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 +6141,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..7a0c344cc5c 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,131 @@ 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
> >+test02h()
> >+{
> >+ int&(*fptr)(X&) = [](X& x) -> int& { return x.i; };
> >+ test02<sizeof(void(*)())>(fptr);
> >+}
> >+
> > void
> > test03()
> > {
> >@@ -227,11 +329,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
>
... as tested here, I believe. Please let me know if this is not the case
you wanted to
check.
Here is also the goldbolt test: https://godbolt.org/z/51EMxhn79
Patrick summarizes that as "member functions always have an object
parameter during overload resolution, static or not",
which clarifies that for me, but I do not think I could explain it to
anybody.
> >+ 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 +407,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..d4bd7db26cb 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,14 @@ main()
> > static_assert(test02());
> > static_assert(test03());
> > test04();
> >+ static_assert(test01());
> >+ 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
> >
> >
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20251120/4921100d/attachment-0001.htm>
More information about the Libstdc++
mailing list