On Fri, Nov 21, 2025 at 5:22 AM Patrick Palka <[email protected]> wrote:
> On Thu, 20 Nov 2025, 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. > > (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>); > > While the use of __is_specialization_of is nice and clean here, it means > the concept is effectively O(n) in the number of standard functors we're > accepting, which for n=27 seems too high. > > Rather than using __is_specialization_of we can define this concept > in terms of two variable templates, one for the non-template functors > and one for the template functors, and enumerate the set of allowed > standard functors via explicit specializations. Explicit > specializations are O(1) to resolve, making the overall concept O(1). > That make's sense, however I will change the order of patches here, and make the static operator() for C++20 functors first one in the list, so I have only one case here. > > > + > > + 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)]...); } > > + }; > > Partial specializations are O(n) to resolve, but here n is just 4 which > isn't so bad. But if you want we can make this O(1) as well by making > each partial specialization its own distinct class template. > I will in doing this, why kepping the connection between them. > > > + > > + 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...); }) > > + 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); > > +} > > Identical to test02e? > Oh yes, I ended up adding test14 instead of a separate test for functors. > > > + > > 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 > > > >
