On Thu, Nov 20, 2025 at 6:16 PM Jonathan Wakely <[email protected]> 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 <[email protected]> > >--- > >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 > > > > > >
