From 9cf16ea09c85559363cf85bcb631ef459518d652 Mon Sep 17 00:00:00 2001 From: Ole Erik Peistorpet Date: Mon, 27 May 2024 14:46:59 +0200 Subject: [PATCH] Hack: mutable member in transform_iterator for std::input_iterator concept --- unit_test/view_gtest.cpp | 5 +-- view/transform.h | 4 +- view/transform_iterator.h | 90 ++++++++++++++++++++++++--------------- 3 files changed, 60 insertions(+), 39 deletions(-) diff --git a/unit_test/view_gtest.cpp b/unit_test/view_gtest.cpp index ff327c83..1049038f 100644 --- a/unit_test/view_gtest.cpp +++ b/unit_test/view_gtest.cpp @@ -210,12 +210,11 @@ TEST(viewTest, viewTransformMutableLambda) }; int dummy[3]; auto v = view::transform(dummy, iota); - using I = decltype(v.begin()); + using I = decltype(v.begin()); static_assert(std::is_same_v); #if OEL_STD_RANGES - static_assert(std::input_or_output_iterator); - static_assert(std::ranges::range); + static_assert(std::ranges::input_range); #endif oel::dynarray test(oel::reserve, 3); diff --git a/view/transform.h b/view/transform.h index 8b381642..8bfa7154 100644 --- a/view/transform.h +++ b/view/transform.h @@ -33,8 +33,8 @@ struct _transformFn * * Unlike std::views::transform, copies or moves the function into the iterator rather than * storing it just in the view, thus saving one indirection when dereferencing the iterator. -* When used in OE-Lib, the function does not need to model std::regular_invocable, -* meaning it's all right to return different results for the same input. +* Moreover, the function does not need to model std::regular_invocable, meaning it's all right to return different results +* for the same input. This is at least true when used in OE-Lib, and probably anywhere that accepts an input_range. * * https://en.cppreference.com/w/cpp/ranges/transform_view */ inline constexpr _transformFn transform; diff --git a/view/transform_iterator.h b/view/transform_iterator.h index 709a5b7b..f2acfd05 100644 --- a/view/transform_iterator.h +++ b/view/transform_iterator.h @@ -14,25 +14,53 @@ namespace oel { +namespace _detail +{ + template< typename Func, typename Iter, + bool = std::is_invocable_v< const Func &, decltype(*std::declval()) > + > + struct TransformIterBase + { + using FnRef = const Func &; + + static constexpr auto canCallConst = true; + + TightPair< Iter, typename AssignableWrap::Type > m; + }; + + template< typename Func, typename Iter > + struct TransformIterBase + { + using FnRef = Func &; + + static constexpr auto canCallConst = false; + + TightPair< Iter, typename AssignableWrap::Type > mutable m; + }; +} + /** @brief Similar to boost::transform_iterator * * But unlike boost::transform_iterator: * - Has no size overhead for stateless function objects * - Accepts a lambda as long as any by-value captures are trivially copy constructible and trivially destructible -* - Move-only UnaryFunc and Iterator supported (then transform_iterator itself becomes move-only) */ +* - Move-only UnaryFunc and Iterator supported (then transform_iterator itself becomes move-only) +* - Function objects (including lambda) can have non-const `operator()`, then merely std::input_iterator is modeled */ template< typename UnaryFunc, typename Iterator > class transform_iterator + : private _detail::TransformIterBase { - _detail::TightPair< Iterator, typename _detail::AssignableWrap::Type > _m; + using _base = typename transform_iterator::TransformIterBase; + + using _base::m; static constexpr auto _isBidirectional = iter_is_bidirectional; - static constexpr auto _isConstCallable = std::is_invocable_v< UnaryFunc const, decltype(*_m.first) >; public: using iterator_category = std::conditional_t< - std::is_copy_constructible_v and _isConstCallable, + std::is_copy_constructible_v and _base::canCallConst, std::conditional_t< _isBidirectional, std::bidirectional_iterator_tag, @@ -41,86 +69,80 @@ class transform_iterator std::input_iterator_tag >; using difference_type = iter_difference_t; - using reference = decltype( std::declval()(*_m.first) ); + using reference = decltype( std::declval()(*m.first) ); using pointer = void; using value_type = std::remove_cv_t< std::remove_reference_t >; transform_iterator() = default; - constexpr transform_iterator(UnaryFunc f, Iterator it) : _m{std::move(it), std::move(f)} {} + constexpr transform_iterator(UnaryFunc f, Iterator it) : _base{{std::move(it), std::move(f)}} {} - constexpr Iterator base() && { return std::move(_m.first); } - constexpr Iterator base() const && { return _m.first; } - constexpr const Iterator & base() const & noexcept OEL_ALWAYS_INLINE { return _m.first; } + constexpr Iterator base() && { return std::move(m.first); } + constexpr Iterator base() const && { return m.first; } + constexpr const Iterator & base() const & noexcept OEL_ALWAYS_INLINE { return m.first; } constexpr reference operator*() const - OEL_REQUIRES(_isConstCallable) - { - const UnaryFunc & f = _m.second(); - return f(*_m.first); - } - constexpr reference operator*() { - UnaryFunc & f = _m.second(); - return f(*_m.first); + typename _base::FnRef f = m.second(); + return f(*m.first); } - constexpr transform_iterator & operator++() OEL_ALWAYS_INLINE { ++_m.first; return *this; } + constexpr transform_iterator & operator++() OEL_ALWAYS_INLINE { ++m.first; return *this; } //! Post-increment: return type is transform_iterator if iterator_category is-a forward_iterator_tag, else void constexpr auto operator++(int) & { - if constexpr (iter_is_forward) + if constexpr (std::is_same_v) { - auto tmp = *this; - ++_m.first; - return tmp; + ++m.first; } else - { ++_m.first; + { auto tmp = *this; + ++m.first; + return tmp; } } constexpr transform_iterator & operator--() - OEL_REQUIRES(_isBidirectional) OEL_ALWAYS_INLINE { --_m.first; return *this; } + OEL_REQUIRES(_isBidirectional) OEL_ALWAYS_INLINE { --m.first; return *this; } constexpr transform_iterator operator--(int) & OEL_REQUIRES(_isBidirectional) { auto tmp = *this; - --_m.first; + --m.first; return tmp; } constexpr difference_type operator -(const transform_iterator & right) const - OEL_REQUIRES(std::sized_sentinel_for) { return _m.first - right._m.first; } + OEL_REQUIRES(std::sized_sentinel_for) { return m.first - right.m.first; } template< typename S > OEL_REQUIRES(std::sized_sentinel_for) friend constexpr difference_type operator - - (sentinel_wrapper left, const transform_iterator & right) { return left._s - right._m.first; } + (sentinel_wrapper left, const transform_iterator & right) { return left._s - right.m.first; } template< typename S > OEL_REQUIRES(std::sized_sentinel_for) friend constexpr difference_type operator - - (const transform_iterator & left, sentinel_wrapper right) { return left._m.first - right._s; } + (const transform_iterator & left, sentinel_wrapper right) { return left.m.first - right._s; } - constexpr bool operator==(const transform_iterator & right) const { return _m.first == right._m.first; } + constexpr bool operator==(const transform_iterator & right) const { return m.first == right.m.first; } // These are not hidden friends because MSC 2017 gives error C3615 - constexpr bool operator!=(const transform_iterator & right) const { return _m.first != right._m.first; } + constexpr bool operator!=(const transform_iterator & right) const { return m.first != right.m.first; } template< typename S > friend constexpr bool operator== - (const transform_iterator & left, sentinel_wrapper right) { return left._m.first == right._s; } + (const transform_iterator & left, sentinel_wrapper right) { return left.m.first == right._s; } template< typename S > friend constexpr bool operator== - (sentinel_wrapper left, const transform_iterator & right) { return right._m.first == left._s; } + (sentinel_wrapper left, const transform_iterator & right) { return right == left; } template< typename S > friend constexpr bool operator!= - (const transform_iterator & left, sentinel_wrapper right) { return left._m.first != right._s; } + (const transform_iterator & left, sentinel_wrapper right) { return left.m.first != right._s; } template< typename S > friend constexpr bool operator!= - (sentinel_wrapper left, const transform_iterator & right) { return right._m.first != left._s; } + (sentinel_wrapper left, const transform_iterator & right) { return right.m.first != left._s; } }; #if __cpp_lib_concepts < 201907