On Fri, 14 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 _FunctorType > template parameter: > * _FunctionPtr: Used if the functor is a function pointer. The pointer is > stored directly in __func_handle and the iterator, avoiding double > indirection through a pointer to the function pointer. > * _MemberPointer: Used for data or function member pointers. This behaves > similarly to _FunctionPtr, but uses __invoke for invocations. > * _StaticOperator: 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. > * _Stateful: 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 discussed earlier today we could also apply the optimization to standard empty functors with non-static operator() that we know doesn't depend on the value of 'this'. And maybe the commit message could use an explanatanion of why we can't optimize empty functors in general. > > 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 _Stateful. The corresponding views are not marked as borrowed_range, > as they are not marked as such in the standard. > > 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/std/ranges (__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. > > Reviewed-by: Patrick Palka <[email protected]> > Signed-off-by: Tomasz Kamiński <[email protected]> > --- > v2: > - rename _Statefull to _Stateful > - make __func_handle<T> to __func_handle<const T> implicit, as > suggested by Hewill > - use less criptic (_Ids, _Const) when construction _Fun_handle > for adjacent_transform > - add test for empty functor that depends on *this > - add test for overload resolution selecting between static and > member operators > > Tested on x86_64-linux locally. OK for trunk? > > > libstdc++-v3/include/std/ranges | 214 +++++++++++++++--- > .../ranges/adaptors/adjacent_transform/1.cc | 97 ++++++++ > .../std/ranges/adaptors/transform.cc | 160 ++++++++++++- > .../testsuite/std/ranges/zip_transform/1.cc | 101 +++++++++ > 4 files changed, 535 insertions(+), 37 deletions(-) > > diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges > index ae57b9a0809..e4851df804f 100644 > --- a/libstdc++-v3/include/std/ranges > +++ b/libstdc++-v3/include/std/ranges > @@ -286,6 +286,138 @@ namespace ranges > operator->() const noexcept > { return std::__addressof(_M_value); } > }; > + > + enum class _FunctorType > + { > + _FunctionPtr, > + _MemberPtr, > + _StaticOperator, > + _Stateful, > + }; > + > + template<typename _Fn, _FunctorType __ft> > + struct __func_handle > + { > + __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, __ft> __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, _FunctorType> > + friend struct __func_handle; > + }; > + > + template<typename _Fn> > + struct __func_handle<_Fn, _FunctorType::_FunctionPtr> > + { > + __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: > + _Fn _M_ptr = nullptr; > + }; > + > + template<typename _Fn> > + struct __func_handle<_Fn, _FunctorType::_MemberPtr> > + { > + __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, _FunctorType::_StaticOperator> > + { > + __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, _FunctorType::_MemberPtr>(); > + else if constexpr (std::is_function_v<remove_pointer_t<_Fd>>) > + return __func_handle<_Fd, _FunctorType::_FunctionPtr>(); > + else if constexpr (requires (const _Iters&... __iters) > + { _Fd::operator()(*__iters...); }) > + return __func_handle<_Fd, _FunctorType::_StaticOperator>(); > + else > + return __func_handle<_Fn, _FunctorType::_Stateful>(); > + }()); > } // namespace __detail > > /// A view that contains exactly one element. > @@ -1874,6 +2006,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 +2024,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 +2038,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 +2064,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 +2118,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 +2156,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 +5264,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 +5296,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 +5359,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 +5376,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 +5953,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 +6010,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 +6069,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 +6106,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..1667c6250c5 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,97 @@ test04() > static_assert( requires { x | views::pairwise_transform(move_only{}); } ); > } > > +struct X > +{ > + int i,j; > + constexpr int combine(X o) const > + { return i + o.j; } > +}; > + > +template<size_t FuncSize, typename Fn> > +constexpr bool > +test05(Fn f) > +{ > + using namespace __gnu_test; > + X x[] = {{1,500},{2,400},{3,300},{4,200},{5,100}}; > + test_range<X, random_access_iterator_wrapper> rx(x); > + > + auto v = rx | views::pairwise_transform(f); > + VERIFY( ranges::size(v) == 4 ); > + VERIFY( ranges::distance(v.begin(), v.end()) == 4 ); > + VERIFY( ranges::equal(v, (int[]){401,302,203,104}) ); > + VERIFY( ranges::equal(v | views::reverse, (int[]){104,203,302,401}) ); > + 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) == 2*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>); > + return true; > +} > + > +constexpr bool > +test05a() > +{ > + auto comb = [](const X& x1, const X& x2) { return x1.i + x2.j; }; > + return test05<sizeof(void*)>(comb); > +} > + > +constexpr bool > +test05b() > +{ > + auto comb = [](const X& x1, const X& x2) static { return x1.i + x2.j; }; > + return test05<0>(comb); > +} > + > +constexpr bool > +test05c() > +{ > + int(*comb)(const X&, const X&) = [](const X& x1, const X& x2) { return > x1.i + x2.j; }; > + return test05<sizeof(void(*)())>(comb); > +} > + > +constexpr bool > +test05d() > +{ > + return test05<sizeof(int(X::*)())>(&X::combine); > +} > + > +constexpr bool > +test05e() > +{ > + struct PickStatic > + { > + static constexpr int > + operator()(const X& x1, const X& x2) > + { return x1.i + x2.j; } > + > + 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, const X& x2) const > + { return x1.i + x2.j; } > + > + static constexpr int > + operator()(int x, int y) > + { return x + y; }; > + }; > + return test05<sizeof(void*)>(PickObject{}); > +} > + > int > main() > { > @@ -120,4 +211,10 @@ main() > static_assert(test02()); > static_assert(test03()); > test04(); > + static_assert(test05a()); > + static_assert(test05b()); > + static_assert(test05c()); > + static_assert(test05d()); > + static_assert(test05e()); > + static_assert(test05f()); > } > diff --git a/libstdc++-v3/testsuite/std/ranges/adaptors/transform.cc > b/libstdc++-v3/testsuite/std/ranges/adaptors/transform.cc > index 1788db1ce8d..b0313130e26 100644 > --- a/libstdc++-v3/testsuite/std/ranges/adaptors/transform.cc > +++ b/libstdc++-v3/testsuite/std/ranges/adaptors/transform.cc > @@ -18,6 +18,7 @@ > // { dg-do run { target c++20 } } > > #include <algorithm> > +#include <cstdint> > #include <ranges> > #include <testsuite_hooks.h> > #include <testsuite_iterators.h> > @@ -28,12 +29,12 @@ using __gnu_test::random_access_iterator_wrapper; > namespace ranges = std::ranges; > namespace views = std::ranges::views; > > +template<typename Fn> > void > -test01() > +test01(Fn f) > { > int x[] = {1,2,3,4,5}; > - auto is_odd = [] (int i) { return i%2==1; }; > - auto v = x | views::transform(is_odd); > + auto v = x | views::transform(f); > VERIFY( ranges::equal(v, (int[]){1,0,1,0,1}) ); > using R = decltype(v); > static_assert(std::same_as<bool, decltype(*ranges::begin(v))>); > @@ -42,30 +43,124 @@ test01() > static_assert(ranges::random_access_range<R>); > } > > +void > +test01a() > +{ > + auto is_odd = [] (int i) { return i%2==1; }; > + test01(is_odd); > +} > + > +void > +test01b() > +{ > +#if __cpp_static_call_operator >= 202207L > + auto is_odd = [] (int i) static { return i%2==1; }; > + test01(is_odd); > +#endif > +} > + > +void > +test01c() > +{ > + bool(*is_odd)(int) = [] (int i) { return i%2==1; }; > + test01(is_odd); > +} > + > struct X > { > int i,j; > + int& first() { return i; } > }; > > +template<size_t FuncSize, typename Fn> > void > -test02() > +test02(Fn f) > { > X x[] = {{1,2},{3,4},{5,6},{7,8},{9,10}}; > test_range<X, random_access_iterator_wrapper> rx(x); > - auto v = rx | views::transform(&X::i); > + auto v = rx | views::transform(f); > VERIFY( ranges::size(v) == 5 ); > VERIFY( ranges::distance(v.begin(), v.end()) == 5 ); > VERIFY( ranges::equal(v, (int[]){1,3,5,7,9}) ); > VERIFY( ranges::equal(v | views::reverse, (int[]){9,7,5,3,1}) ); > using R = decltype(v); > + using It = ranges::iterator_t<R>; > static_assert(std::same_as<int&, decltype(*ranges::begin(v))>); > - static_assert(std::same_as<int, std::iter_value_t<ranges::iterator_t<R>>>); > + static_assert(std::same_as<int, std::iter_value_t<It>>); > + static_assert(sizeof(It) == sizeof(rx.begin()) + FuncSize); > static_assert(ranges::view<R>); > static_assert(ranges::sized_range<R>); > static_assert(!ranges::common_range<R>); > static_assert(ranges::random_access_range<R>); > } > > +void > +test02a() > +{ test02<sizeof(int X::*)>(&X::i); } > + > +void > +test02b() > +{ test02<sizeof(int(X::*)())>(&X::first); } > + > +void > +test02c() > +{ > + auto first = [](X& x) -> int& { return x.i; }; > + test02<sizeof(void*)>(first); > +} > + > +void > +test02d() > +{ > +#if __cpp_static_call_operator >= 202207L > + auto first = [](X& x) static -> int& { return x.i; }; > + test02<0>(first); > +#endif > +} > + > +void > +test02e() > +{ > + int&(*fptr)(X&) = [](X& x) -> int& { return x.i; }; > + test02<sizeof(void(*)())>(fptr); > +} > + > +void > +test02f() > +{ > +#if __cpp_static_call_operator >= 202207L > + struct PickStatic > + { > + static constexpr int& > + operator()(X& x) > + { return x.i; } > + > + constexpr int > + operator()(char*) const > + { return 0; }; > + }; > + test02<0>(PickStatic{}); > +#endif > +} > + > +void > +test02g() > +{ > +#if __cpp_static_call_operator >= 202207L > + struct PickObject > + { > + constexpr int& > + operator()(X& x) const > + { return x.i; } > + > + static constexpr int > + operator()(char*) > + { return 0; }; > + }; > + test02<sizeof(void*)>(PickObject{}); > +#endif > +} > + > void > test03() > { > @@ -227,11 +322,58 @@ 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 > +} > + > int > main() > { > - test01(); > - test02(); > + test01a(); > + test01b(); > + test01c(); > + test02a(); > + test02b(); > + test02c(); > + test02d(); > + test02e(); > + test02f(); > + test02g(); > test03(); > test04(); > test05(); > @@ -241,4 +383,6 @@ main() > test09(); > test10(); > test11(); > + test12(); > + test13(); > } > 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.0 > >
