libstdc++: Optimize functor storage for transform views iterators.

The iterators for transform views (views::transform, views::zip_transform,
and views::adjacent_transform) now store a function handle from (from
__detail::__func_handle namespace) instead of a pointer to the view object
(_M_parent).

The following handle templates are defined in __func_handle namespace:
* _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 template the
  cv-qualification of the functor template parameter (_Fn) relevant, and
  specialization for both const and mutable types are generated.

As a consequence of these changes, the iterators of transform views no longer
depend on the view object when handle other than __func_handle::_ViaPointer
is used. 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 pre-C++20 standard functors,
as for once introduced later operator() was retroactively made static.
We do not extent to to any empty fuctor, as it's oprator may still depend on
value of this pointer 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 handles 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::__is_std_op_template)
	(__detail::__is_std_op_wrapper, __func_handle::_Inplace)
	(__func_handle::_InplaceMemPtr, __func_handle::_ViaPointer)
	(__func_handle::_StaticCall, __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 construct _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: Jonathan Wakely <jwakely@redhat.com>
Reviewed-by: Patrick Palka <ppalka@redhat.com>
Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>
This commit is contained in:
Tomasz Kamiński 2025-11-13 14:54:11 +01:00
parent 0ea9d760fb
commit 9ed821d107
4 changed files with 540 additions and 37 deletions

View File

@ -286,6 +286,185 @@ namespace ranges
operator->() const noexcept
{ return std::__addressof(_M_value); }
};
template<template<typename> class>
constexpr bool __is_std_op_template = false;
template<>
inline constexpr bool __is_std_op_template<std::equal_to> = true;
template<>
inline constexpr bool __is_std_op_template<std::not_equal_to> = true;
template<>
inline constexpr bool __is_std_op_template<std::greater> = true;
template<>
inline constexpr bool __is_std_op_template<std::less> = true;
template<>
inline constexpr bool __is_std_op_template<std::greater_equal> = true;
template<>
inline constexpr bool __is_std_op_template<std::less_equal> = true;
template<>
inline constexpr bool __is_std_op_template<std::plus> = true;
template<>
inline constexpr bool __is_std_op_template<std::minus> = true;
template<>
inline constexpr bool __is_std_op_template<std::multiplies> = true;
template<>
inline constexpr bool __is_std_op_template<std::divides> = true;
template<>
inline constexpr bool __is_std_op_template<std::modulus> = true;
template<>
inline constexpr bool __is_std_op_template<std::negate> = true;
template<>
inline constexpr bool __is_std_op_template<std::logical_and> = true;
template<>
inline constexpr bool __is_std_op_template<std::logical_or> = true;
template<>
inline constexpr bool __is_std_op_template<std::logical_not> = true;
template<>
inline constexpr bool __is_std_op_template<std::bit_and> = true;
template<>
inline constexpr bool __is_std_op_template<std::bit_or> = true;
template<>
inline constexpr bool __is_std_op_template<std::bit_xor> = true;
template<>
inline constexpr bool __is_std_op_template<std::bit_not> = true;
template<typename _Fn>
constexpr bool __is_std_op_wrapper = false;
template<template<typename> class _Ft, typename _Tp>
constexpr bool __is_std_op_wrapper<_Ft<_Tp>>
= __is_std_op_template<_Ft>;
namespace __func_handle
{
template<typename _Fn>
struct _Inplace
{
_Inplace() = default;
constexpr explicit
_Inplace(_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 _InplaceMemPtr
{
_InplaceMemPtr() = default;
constexpr explicit
_InplaceMemPtr(_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 _ViaPointer
{
_ViaPointer() = default;
constexpr explicit
_ViaPointer(_Fn& __func) noexcept
: _M_ptr(std::addressof(__func))
{ }
template<typename _Un>
requires (!is_const_v<_Un>) && is_same_v<const _Un, _Fn>
constexpr
_ViaPointer(_ViaPointer<_Un> __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>
friend struct _ViaPointer;
};
template<typename _Fn>
struct _StaticCall
{
_StaticCall() = default;
constexpr explicit
_StaticCall(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)]...); }
};
} // __func_handle
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::_InplaceMemPtr<_Fd>();
else if constexpr (is_function_v<remove_pointer_t<_Fd>>)
return __func_handle::_Inplace<_Fd>();
else if constexpr (__is_std_op_wrapper<_Fd>)
return __func_handle::_Inplace<_Fd>();
else if constexpr (requires (const _Iters&... __iters)
{ _Fd::operator()(*__iters...); })
return __func_handle::_StaticCall<_Fd>();
else
return __func_handle::_ViaPointer<_Fn>();
}());
} // namespace __detail
/// A view that contains exactly one element.
@ -1874,6 +2053,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 +2071,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 +2085,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 +2111,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 +2165,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 +2203,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 +5311,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 +5343,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 +5406,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 +5423,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 +6000,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 +6057,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 +6116,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 +6153,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)

View File

@ -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()
{

View File

@ -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,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 +400,7 @@ main()
test09();
test10();
test11();
test12();
test13();
test14all();
}

View File

@ -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,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());
}