[PATCH v2 1/2] libstdc++: Optimize functor storage for transform views iterators.

Jonathan Wakely jwakely@redhat.com
Thu Nov 20 17:16:33 GMT 2025


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?


>+	    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
>+  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
>
>



More information about the Libstdc++ mailing list