I can't help but wonder, if this patch can't optimize for lambdas (users generally don't explicitly spell `static`), then I don't see much point in this optimization.
I think the case of `Obfuscate` is unlikely to occur. The view class will move the original object to the `movable_box` via `std::move(fun)`, so `this` pointer has already changed. If we could actually create such a pretentious functor with a disgusting operator(), I think the standard has every reason not to care about it. Therefore, after thinking about it, I think we should perhaps follow p3117r1 and use `is_empty_v<F> && is_trivially_default_constructible_v<F> && is_trivially_destructible_v<F>` to optimize lambda. Because that is undoubtedly the most common use case (and the most worthy of optimization). This is something this patch should be interested in. Tomasz Kaminski <[email protected]> 於 2025年11月14日 週五 下午5:36寫道: > > > On Fri, Nov 14, 2025 at 10:24 AM Hewill Kang <[email protected]> wrote: > >> Yes, I do not think anything in standard clearly states the that >>> following is >>> allowed to fail: >>> struct Obfuscate >>> { >>> int operator()(int x) const >>> { return x + static_assert<unintptr_t>(this); } >>> }; >> >> >>> std::vector<int> v{1, 2, 3, 4, 5}; >>> auto tv = v | std::transform(Obfuscate{}); >>> VERIFY( ranges::equal(tv, tv) ); >>> But storing an empty object in an iterator will break. >> >> >> This is a very good observation, so recreating empty objects is still >> problematic. >> It also seems to suggest that >> https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3117r1.html (most >> likely a follow-up to this patch) and >> https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3705r1.html >> might have issues with this. >> For example, `operator()` uses `this` or `operator==` uses `this`. >> > Yes, static operator() is bulletproof way of ensuring that it does not > depend on object identity, > even if the object itself is not empty. We do not need to use > heuristics based on size anymore. > >> >> Tomasz Kaminski <[email protected]> 於 2025年11月14日 週五 下午2:36寫道: >> >>> >>> >>> On Fri, Nov 14, 2025 at 4:16 AM Patrick Palka <[email protected]> wrote: >>> >>>> On Thu, 13 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. >>>> > * _Statefull: 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. >>>> >>>> FWIW it's spelled 'stateful' with just one l. >>>> >>>> I guess you concluded that this optimization is unsafe for empty objects >>>> with non-static operator()? >>>> >>> Yes, I do not think anything in standard clearly states the that >>> following is >>> allowed to fail: >>> struct Obfuscate >>> { >>> int operator()(int x) const >>> { return x + static_assert<unintptr_t>(this); } >>> }; >>> >>> std::vector<int> v{1, 2, 3, 4, 5}; >>> auto tv = v | std::transform(Obfuscate{}); >>> VERIFY( ranges::equal(tv, tv) ); >>> >>> But storing an empty object in an iterator will break. >>> >>> >>>> > >>>> > 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 _Statefull. 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. >>>> > --- >>>> > Tested transform view files locally. More testing in progress. >>>> > >>>> > libstdc++-v3/include/std/ranges | 214 >>>> +++++++++++++++--- >>>> > .../ranges/adaptors/adjacent_transform/1.cc | 97 ++++++++ >>>> > .../std/ranges/adaptors/transform.cc | 118 +++++++++- >>>> > .../testsuite/std/ranges/zip_transform/1.cc | 101 +++++++++ >>>> > 4 files changed, 493 insertions(+), 37 deletions(-) >>>> > >>>> > diff --git a/libstdc++-v3/include/std/ranges >>>> b/libstdc++-v3/include/std/ranges >>>> > index ae57b9a0809..28b4f1d2b90 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, >>>> > + _Statefull, >>>> > + }; >>>> > + >>>> > + 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 explicit >>>> > + __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::_Statefull>(); >>>> > + }()); >>>> > } // 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<_Const || (_Ids < 0u), _Vp>>...>(); >>>> >>>> Maybe '(_Ids, _Const)' would be a little less cryptic than >>>> '_Const || (_Ids < 0u)'? >>>> >>> Yes, that is major improvement. >>> >>>> Besides that looks good! >>>> >>>> > + }(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..c6d6b916730 100644 >>>> > --- a/libstdc++-v3/testsuite/std/ranges/adaptors/transform.cc >>>> > +++ b/libstdc++-v3/testsuite/std/ranges/adaptors/transform.cc >>>> > @@ -28,12 +28,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 +42,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() >>>> > { >>>> > @@ -230,8 +324,16 @@ test11() >>>> > int >>>> > main() >>>> > { >>>> > - test01(); >>>> > - test02(); >>>> > + test01a(); >>>> > + test01b(); >>>> > + test01c(); >>>> > + test02a(); >>>> > + test02b(); >>>> > + test02c(); >>>> > + test02d(); >>>> > + test02e(); >>>> > + test02f(); >>>> > + test02g(); >>>> > test03(); >>>> > test04(); >>>> > test05(); >>>> > 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 >>>> > >>>> > >>> >>>
