diff --git a/include/etl/ranges.h b/include/etl/ranges.h index 5fd8b6fe..b502f82d 100644 --- a/include/etl/ranges.h +++ b/include/etl/ranges.h @@ -585,6 +585,12 @@ namespace etl using iterator_category = ETL_OR_STD::random_access_iterator_tag; + constexpr repeat_iterator() + : _value{} + , _i{} + { + } + constexpr explicit repeat_iterator(T value, B i = etl::numeric_limits::max()) : _value{value} , _i{i} @@ -901,7 +907,11 @@ namespace etl private: - Range _r; + // 'mutable' so that the const begin()/end() observe a non-const '_r' and + // therefore call the non-const ETL_OR_STD::begin/end overloads, yielding + // the (mutable) 'iterator' rather than a 'const_iterator' that cannot be + // converted back to 'iterator'. + mutable Range _r; }; template @@ -989,12 +999,15 @@ namespace etl using const_iterator = typename trait::const_iterator; using value_type = typename trait::value_type; using difference_type = typename trait::difference_type; - using pointer = typename trait::pointer; - using reference = typename trait::reference; + // Derive the reference from the mutable iterator so filtered elements + // remain mutable when the underlying range is non-const, as required for + // std::ranges::filter_view. + using reference = decltype(*etl::declval()); + using pointer = etl::remove_reference_t*; using iterator_category = ETL_OR_STD::bidirectional_iterator_tag; - filter_iterator(const_iterator it, const_iterator it_end, const Pred& p) + filter_iterator(iterator it, iterator it_end, const Pred& p) : _it{it} , _it_begin{it} , _it_end{it_end} @@ -1099,7 +1112,7 @@ namespace etl return *this; } - value_type operator*() + reference operator*() { return *_it; } @@ -1116,10 +1129,10 @@ namespace etl private: - const_iterator _it; - const_iterator _it_begin; - const_iterator _it_end; - const Pred& _p; + iterator _it; + iterator _it_begin; + iterator _it_end; + const Pred& _p; }; template @@ -1162,18 +1175,18 @@ namespace etl constexpr const_iterator begin() const { - return const_iterator(ETL_OR_STD::cbegin(_r), ETL_OR_STD::cend(_r), _pred); + return const_iterator(ETL_OR_STD::begin(_r), ETL_OR_STD::end(_r), _pred); } constexpr const_iterator end() const { - return const_iterator(ETL_OR_STD::cend(_r), ETL_OR_STD::cend(_r), _pred); + return const_iterator(ETL_OR_STD::end(_r), ETL_OR_STD::end(_r), _pred); } private: - const Pred _pred; - Range _r; + const Pred _pred; + mutable Range _r; }; template @@ -1231,10 +1244,14 @@ namespace etl using iterator = typename trait::iterator; using const_iterator = typename trait::const_iterator; - using value_type = typename trait::value_type; using difference_type = typename trait::difference_type; - using pointer = typename trait::pointer; - using reference = typename trait::reference; + + // transform_view is type-changing: the element type is the result of applying + // the transform function to the underlying element, not the underlying range's + // element type (as specified for std::ranges::transform_view). + using reference = decltype(etl::declval()(*etl::declval())); + using value_type = etl::remove_cvref_t; + using pointer = void; using iterator_category = ETL_OR_STD::forward_iterator_tag; @@ -1270,9 +1287,9 @@ namespace etl return *this; } - value_type operator*() + reference operator*() { - return static_cast(_f(*_it)); + return _f(*_it); } bool operator==(const transform_iterator& other) const @@ -2269,9 +2286,12 @@ namespace etl using inner_trait = typename etl::ranges::private_ranges::iterator_trait; using inner_iterator = typename inner_trait::iterator; - using value_type = typename inner_trait::value_type; + // Derive the reference type from the inner iterator's actual dereference, so that + // inner ranges whose iterators yield prvalues (e.g. repeat_view) are supported, + // matching std::ranges::join_view (reference = range_reference_t). + using reference = decltype(*etl::declval()); + using value_type = etl::remove_cvref_t; using pointer = typename inner_trait::pointer; - using reference = typename inner_trait::reference; join_iterator(iterator it, iterator it_end) : _it(it) @@ -2430,6 +2450,31 @@ namespace etl inline constexpr private_views::join join; } // namespace views + namespace private_ranges + { + //********************************************************************* + /// Computes the element reference type for join_with, following + /// [range.join.with.iterator]: common_reference of the inner range's and + /// the pattern's reference types. etl::common_reference_t is only available + /// in C++20, so for the common case (inner and pattern share an underlying + /// value type) this yields the const-combined lvalue reference - preserving + /// writability when neither side is const - and degrades to a prvalue value + /// type when either side yields a prvalue or the underlying types differ. + //********************************************************************* + template + struct join_with_reference + { + using value_type = etl::common_type_t, etl::remove_cvref_t>; + + static constexpr bool same_underlying = etl::is_same, etl::remove_cvref_t>::value; + static constexpr bool both_lvalue = etl::is_lvalue_reference::value && etl::is_lvalue_reference::value; + static constexpr bool any_const = + etl::is_const>::value || etl::is_const>::value; + + using type = etl::conditional_t, value_type>; + }; + } // namespace private_ranges + template class join_with_iterator { @@ -2447,13 +2492,21 @@ namespace etl using inner_trait = typename etl::ranges::private_ranges::iterator_trait; using inner_iterator = typename inner_trait::iterator; - using value_type = typename inner_trait::value_type; - using pointer = typename inner_trait::pointer; - using reference = typename inner_trait::reference; + // Deduce the pattern iterator from iterating the (const) pattern view directly, + // so reference-backed patterns (e.g. ref_view) stay mutable while value-backed + // patterns (e.g. single_view) are read as const. + using pattern_iterator = decltype(ETL_OR_STD::begin(etl::declval())); - using pattern_trait = typename etl::ranges::private_ranges::iterator_trait; - using pattern_iterator = typename pattern_trait::iterator; - using pattern_const_iterator = typename pattern_trait::const_iterator; + // Element type follows [range.join.with.iterator]: the common type / common + // reference of the inner range's and the pattern's elements. This keeps the + // declared reference consistent with operator* and supports inner ranges whose + // iterators yield prvalues (e.g. repeat_view). + using inner_reference = decltype(*etl::declval()); + using pattern_reference = decltype(*etl::declval()); + + using value_type = etl::common_type_t, etl::remove_cvref_t>; + using reference = typename etl::ranges::private_ranges::join_with_reference::type; + using pointer = typename inner_trait::pointer; join_with_iterator(iterator it, iterator it_end, const Pattern& pattern) : _it(it) @@ -2461,8 +2514,8 @@ namespace etl , _inner_it(it != it_end ? ETL_OR_STD::begin(*it) : inner_iterator{}) , _inner_it_end(it != it_end ? ETL_OR_STD::end(*it) : inner_iterator{}) , _pattern(pattern) - , _pattern_it(pattern.cend()) - , _pattern_it_end(pattern.cend()) + , _pattern_it(ETL_OR_STD::end(pattern)) + , _pattern_it_end(ETL_OR_STD::end(pattern)) { adjust_iterator(); } @@ -2517,7 +2570,7 @@ namespace etl return *this; } - value_type operator*() const + reference operator*() const { if (_pattern_it != _pattern_it_end) { @@ -2545,21 +2598,21 @@ namespace etl ++_it; if (_it != _it_end) { - _pattern_it = ETL_OR_STD::cbegin(_pattern); - _pattern_it_end = ETL_OR_STD::cend(_pattern); + _pattern_it = ETL_OR_STD::begin(_pattern); + _pattern_it_end = ETL_OR_STD::end(_pattern); _inner_it = ETL_OR_STD::begin(*_it); _inner_it_end = ETL_OR_STD::end(*_it); } } } - iterator _it; - iterator _it_end; - inner_iterator _inner_it; - inner_iterator _inner_it_end; - const Pattern& _pattern; - pattern_const_iterator _pattern_it; - pattern_const_iterator _pattern_it_end; + iterator _it; + iterator _it_end; + inner_iterator _inner_it; + inner_iterator _inner_it_end; + const Pattern& _pattern; + pattern_iterator _pattern_it; + pattern_iterator _pattern_it_end; }; template @@ -2695,11 +2748,11 @@ namespace etl using pattern_trait = typename etl::ranges::private_ranges::iterator_trait; using pattern_const_iterator = typename pattern_trait::const_iterator; - using value_type = etl::ranges::subrange; + using value_type = etl::ranges::subrange; using pointer = value_type*; using reference = value_type; - split_iterator(const_iterator it, const_iterator it_end, const Pattern& pattern, bool is_end = false) + split_iterator(iterator it, iterator it_end, const Pattern& pattern, bool is_end = false) : _it(it) , _it_end(it_end) , _pattern(pattern) @@ -2776,7 +2829,7 @@ namespace etl private: - const_iterator find_next() const + iterator find_next() const { auto pat_begin = ETL_OR_STD::cbegin(_pattern); auto pat_end = ETL_OR_STD::cend(_pattern); @@ -2818,10 +2871,10 @@ namespace etl return _it_end; } - const_iterator _it; - const_iterator _it_end; + iterator _it; + iterator _it_end; const Pattern& _pattern; - const_iterator _next; + iterator _next; // there is still one empty segment to emit after the last delimiter if // the last delimiter is at the end of the range bool _trailing_empty; @@ -2956,6 +3009,7 @@ namespace etl public: using trait = typename etl::ranges::private_ranges::iterator_trait; + using iterator_type = typename trait::iterator; using const_iterator_type = typename trait::const_iterator; using value_type = typename trait::value_type; @@ -2968,20 +3022,20 @@ namespace etl using value_type = typename trait::value_type; using difference_type = typename trait::difference_type; - using pointer = const value_type*; - using reference = const value_type&; + using reference = decltype(*etl::declval()); + using pointer = etl::remove_reference_t*; using iterator_category = ETL_OR_STD::forward_iterator_tag; iterator() = default; - iterator(const_iterator_type current, const_iterator_type segment_end, bool is_end) + iterator(iterator_type current, iterator_type segment_end, bool is_end) : _current_it(current) , _segment_end(segment_end) , _is_end(is_end || (current == segment_end)) { } - reference operator*() const + constexpr decltype(auto) operator*() const { return *_current_it; } @@ -3028,14 +3082,14 @@ namespace etl private: - const_iterator_type _current_it{}; - const_iterator_type _segment_end{}; - bool _is_end = true; + iterator_type _current_it{}; + iterator_type _segment_end{}; + bool _is_end = true; }; using const_iterator = iterator; - lazy_split_inner_range(const_iterator_type segment_begin, const_iterator_type segment_end) + lazy_split_inner_range(iterator_type segment_begin, iterator_type segment_end) : _segment_begin(segment_begin) , _segment_end(segment_end) { @@ -3058,8 +3112,8 @@ namespace etl private: - const_iterator_type _segment_begin; - const_iterator_type _segment_end; + iterator_type _segment_begin; + iterator_type _segment_end; }; /// Outer iterator for lazy_split_view. @@ -3085,7 +3139,7 @@ namespace etl using pointer = value_type*; using reference = value_type; - lazy_split_iterator(const_iterator it, const_iterator it_end, const Pattern& pattern, bool is_end = false) + lazy_split_iterator(source_iterator it, source_iterator it_end, const Pattern& pattern, bool is_end = false) : _it(it) , _it_end(it_end) , _pattern(pattern) @@ -3166,7 +3220,7 @@ namespace etl /// Scans forward from _it looking for the pattern; returns the /// position of the first match (i.e. the end of the current segment). - const_iterator find_next() const + source_iterator find_next() const { auto pat_begin = ETL_OR_STD::cbegin(_pattern); auto pat_end = ETL_OR_STD::cend(_pattern); @@ -3208,11 +3262,11 @@ namespace etl return _it_end; } - const_iterator _it; - const_iterator _it_end; - const Pattern& _pattern; - const_iterator _next; - bool _trailing_empty; + source_iterator _it; + source_iterator _it_end; + const Pattern& _pattern; + source_iterator _next; + bool _trailing_empty; }; template @@ -3668,11 +3722,14 @@ namespace etl public: - using iterators_type = etl::tuple< typename etl::ranges::private_ranges::iterator_trait< Ranges>::const_iterator...>; + using iterators_type = etl::tuple< typename etl::ranges::private_ranges::iterator_trait< Ranges>::iterator...>; using value_type = etl::tuple::value_type...>; using difference_type = ptrdiff_t; - using pointer = const value_type*; - using reference = value_type; + // Each tuple element is a reference into the corresponding underlying + // range, so zipped elements remain mutable when the underlying ranges + // are non-const, as required for std::ranges::zip_view. + using reference = etl::tuple::iterator&>())...>; + using pointer = value_type*; using iterator_category = ETL_OR_STD::forward_iterator_tag; @@ -3698,7 +3755,7 @@ namespace etl return tmp; } - constexpr value_type operator*() const + constexpr reference operator*() const { return deref(etl::make_index_sequence{}); } @@ -3722,9 +3779,9 @@ namespace etl } template - constexpr value_type deref(etl::index_sequence) const + constexpr reference deref(etl::index_sequence) const { - return value_type(*etl::get(_iters)...); + return reference(*etl::get(_iters)...); } // zip terminates when ANY iterator reaches its end (shortest range @@ -4275,12 +4332,16 @@ namespace etl using trait = typename etl::ranges::private_ranges::iterator_trait; - using base_iterator = typename trait::const_iterator; + using base_iterator = typename trait::iterator; using base_value_type = typename trait::value_type; + using base_reference = decltype(*etl::declval()); using value_type = etl::tuple; using difference_type = typename trait::difference_type; - using pointer = const value_type*; - using reference = value_type; + // The second tuple element is a reference into the underlying range, so + // elements remain mutable when the underlying range is non-const, as + // required for std::ranges::enumerate_view. + using reference = etl::tuple; + using pointer = value_type*; using iterator_category = ETL_OR_STD::forward_iterator_tag; @@ -4318,9 +4379,9 @@ namespace etl return *this; } - value_type operator*() const + reference operator*() const { - return value_type(_index, *_it); + return reference(_index, *_it); } bool operator==(const enumerate_iterator& other) const @@ -4435,12 +4496,27 @@ namespace etl using trait = typename etl::ranges::private_ranges::iterator_trait; - using base_iterator = typename trait::const_iterator; + using base_iterator = typename trait::iterator; using base_value_type = typename trait::value_type; using value_type = etl::tuple_element_t; using difference_type = typename trait::difference_type; - using pointer = const value_type*; - using reference = const value_type&; + + private: + + // Computes the dereference type via ADL get, so that std::pair and + // etl::tuple both resolve correctly. When the underlying range is + // non-const this yields a mutable reference, as required for + // std::ranges::elements_view. + static decltype(auto) deref_element(const base_iterator& it) + { + using etl::get; + return get(*it); + } + + public: + + using reference = decltype(deref_element(etl::declval())); + using pointer = etl::remove_reference_t*; using iterator_category = ETL_OR_STD::forward_iterator_tag; @@ -4475,8 +4551,7 @@ namespace etl decltype(auto) operator*() const { - using etl::get; - return get(*_it); + return deref_element(_it); } bool operator==(const elements_iterator& other) const @@ -4645,12 +4720,16 @@ namespace etl using trait = typename etl::ranges::private_ranges::iterator_trait; - using base_iterator = typename trait::const_iterator; + using base_iterator = typename trait::iterator; using base_value_type = typename trait::value_type; + using base_reference = decltype(*etl::declval()); using value_type = private_ranges::repeat_tuple_t; using difference_type = typename trait::difference_type; - using pointer = const value_type*; - using reference = value_type; + // Each tuple element is a reference into the underlying range, so the + // window elements remain mutable when the underlying range is non-const, + // as required for std::ranges::adjacent_view. + using reference = private_ranges::repeat_tuple_t; + using pointer = value_type*; using iterator_category = ETL_OR_STD::forward_iterator_tag; @@ -4679,7 +4758,7 @@ namespace etl return tmp; } - constexpr value_type operator*() const + constexpr reference operator*() const { return deref(etl::make_index_sequence{}); } @@ -4714,9 +4793,9 @@ namespace etl } template - constexpr value_type deref(etl::index_sequence) const + constexpr reference deref(etl::index_sequence) const { - return value_type(*_iters[Is]...); + return reference(*_iters[Is]...); } base_iterator _iters[N]; @@ -5038,11 +5117,11 @@ namespace etl using iterator_category = ETL_OR_STD::forward_iterator_tag; - using value_type = etl::ranges::subrange; + using value_type = etl::ranges::subrange; using pointer = value_type*; using reference = value_type; - chunk_iterator(const_inner_iterator it, const_inner_iterator it_end, difference_type chunk_size) + chunk_iterator(inner_iterator it, inner_iterator it_end, difference_type chunk_size) : _it(it) , _it_end(it_end) , _chunk_size(chunk_size) @@ -5070,9 +5149,9 @@ namespace etl value_type operator*() const { - difference_type remaining = etl::distance(_it, _it_end); - difference_type step = (_chunk_size < remaining) ? _chunk_size : remaining; - const_inner_iterator chunk_end = _it; + difference_type remaining = etl::distance(_it, _it_end); + difference_type step = (_chunk_size < remaining) ? _chunk_size : remaining; + inner_iterator chunk_end = _it; etl::advance(chunk_end, step); return value_type(_it, chunk_end); } @@ -5089,9 +5168,9 @@ namespace etl private: - const_inner_iterator _it; - const_inner_iterator _it_end; - difference_type _chunk_size; + inner_iterator _it; + inner_iterator _it_end; + difference_type _chunk_size; }; //************************************************************************* @@ -5200,11 +5279,11 @@ namespace etl using iterator_category = ETL_OR_STD::forward_iterator_tag; - using value_type = etl::ranges::subrange; + using value_type = etl::ranges::subrange; using pointer = value_type*; using reference = value_type; - slide_iterator(const_inner_iterator it, const_inner_iterator it_end, difference_type window_size) + slide_iterator(inner_iterator it, inner_iterator it_end, difference_type window_size) : _it(it) , _it_end(it_end) , _window_size(window_size) @@ -5230,7 +5309,7 @@ namespace etl value_type operator*() const { - const_inner_iterator window_end = _it; + inner_iterator window_end = _it; etl::advance(window_end, _window_size); return value_type(_it, window_end); } @@ -5247,9 +5326,9 @@ namespace etl private: - const_inner_iterator _it; - const_inner_iterator _it_end; - difference_type _window_size; + inner_iterator _it; + inner_iterator _it_end; + difference_type _window_size; }; //************************************************************************* @@ -5376,11 +5455,11 @@ namespace etl using iterator_category = ETL_OR_STD::forward_iterator_tag; - using value_type = etl::ranges::subrange; + using value_type = etl::ranges::subrange; using pointer = value_type*; using reference = value_type; - chunk_by_iterator(const_inner_iterator it, const_inner_iterator it_end, const Pred& pred) + chunk_by_iterator(inner_iterator it, inner_iterator it_end, const Pred& pred) : _it(it) , _it_end(it_end) , _pred(pred) @@ -5423,15 +5502,15 @@ namespace etl private: - const_inner_iterator find_next_chunk_end() const + inner_iterator find_next_chunk_end() const { if (_it == _it_end) { return _it_end; } - const_inner_iterator it_prev = _it; - const_inner_iterator it_curr = _it; + inner_iterator it_prev = _it; + inner_iterator it_curr = _it; ++it_curr; while (it_curr != _it_end) @@ -5447,10 +5526,10 @@ namespace etl return _it_end; } - const_inner_iterator _it; - const_inner_iterator _it_end; - const_inner_iterator _chunk_end; - Pred _pred; + inner_iterator _it; + inner_iterator _it_end; + inner_iterator _chunk_end; + Pred _pred; }; //************************************************************************* @@ -5565,10 +5644,10 @@ namespace etl using iterator_category = ETL_OR_STD::forward_iterator_tag; using value_type = typename trait::value_type; - using pointer = typename trait::pointer; - using reference = typename trait::reference; + using reference = decltype(*etl::declval()); + using pointer = etl::remove_reference_t*; - constexpr stride_iterator(const_inner_iterator it, const_inner_iterator it_end, difference_type stride_n) + constexpr stride_iterator(inner_iterator it, inner_iterator it_end, difference_type stride_n) : _it(it) , _it_end(it_end) , _stride_n(stride_n) @@ -5594,12 +5673,12 @@ namespace etl return tmp; } - constexpr auto operator*() const + constexpr decltype(auto) operator*() const { return *_it; } - constexpr auto operator->() const + constexpr pointer operator->() const { return &(*_it); } @@ -5616,9 +5695,9 @@ namespace etl private: - mutable const_inner_iterator _it; - const_inner_iterator _it_end; - difference_type _stride_n; + mutable inner_iterator _it; + inner_iterator _it_end; + difference_type _stride_n; }; //************************************************************************* @@ -5950,7 +6029,7 @@ namespace etl to_input_iterator() = default; - to_input_iterator(const_iterator it) + to_input_iterator(iterator it) : _it(it) { } @@ -5972,7 +6051,7 @@ namespace etl return tmp; } - reference operator*() const + decltype(auto) operator*() const { return *_it; } @@ -5994,7 +6073,7 @@ namespace etl private: - mutable const_iterator _it; + mutable iterator _it; }; //************************************************************************* diff --git a/include/etl/tuple.h b/include/etl/tuple.h index 98c6dc5e..5b7257ec 100644 --- a/include/etl/tuple.h +++ b/include/etl/tuple.h @@ -771,10 +771,13 @@ namespace etl ETL_STATIC_ASSERT(Index < sizeof...(TTypes), "etl::get - Index out of range"); // Get the type at this index. - using tuple_type = etl::nth_base_t>&&; + using tuple_type = etl::nth_base_t>&&; + using element_type = etl::tuple_element_t>; - // Cast the tuple to the selected type and get the value. - return etl::move(static_cast(t).get_value()); + // Forward the element. A reference member must not be turned into an + // rvalue, so cast to element_type&& (which collapses to a reference type + // when element_type is itself a reference). + return static_cast(static_cast(t).get_value()); } //*************************************************************************** @@ -788,10 +791,13 @@ namespace etl ETL_STATIC_ASSERT(Index < sizeof...(TTypes), "etl::get - Index out of range"); // Get the type at this index. - using tuple_type = const etl::nth_base_t>&&; + using tuple_type = const etl::nth_base_t>&&; + using element_type = etl::tuple_element_t>; - // Cast the tuple to the selected type and get the value. - return etl::move(static_cast(t).get_value()); + // Forward the element. A reference member must not be turned into an + // rvalue, so cast to const element_type&& (which collapses to a reference + // type when element_type is itself a reference). + return static_cast(static_cast(t).get_value()); } //*************************************************************************** @@ -844,8 +850,10 @@ namespace etl // Get the tuple base type that contains a T using tuple_type = etl::private_tuple::tuple_type_base_t>&&; - // Cast the tuple to the selected type and get the value. - return etl::move(static_cast(t).get_value()); + // Forward the element. A reference type T must not be turned into an + // rvalue, so cast to T&& (which collapses to a reference when T is a + // reference type). + return static_cast(static_cast(t).get_value()); } //*************************************************************************** @@ -862,8 +870,10 @@ namespace etl // Get the tuple base type that contains a T using tuple_type = const etl::private_tuple::tuple_type_base_t>&&; - // Cast the tuple to the selected type and get the value. - return etl::move(static_cast(t).get_value()); + // Forward the element. A reference type T must not be turned into an + // rvalue, so cast to const T&& (which collapses to a reference when T is a + // reference type). + return static_cast(static_cast(t).get_value()); } #if ETL_USING_CPP17 diff --git a/test/test_ranges.cpp b/test/test_ranges.cpp index 3f4b4e00..efe64148 100644 --- a/test/test_ranges.cpp +++ b/test/test_ranges.cpp @@ -28,6 +28,8 @@ SOFTWARE. #include "unit_test_framework.h" +#include "etl/algorithm.h" +#include "etl/array.h" #include "etl/ranges.h" #include "etl/vector.h" @@ -1564,6 +1566,53 @@ namespace CHECK(it == tv.end()); } + TEST(test_ranges_transform_view_element_type_is_function_result) + { + // transform_view must be type-changing: the element type is the result of the + // transform function, not the underlying range's element type. + auto to_double = [](int i) -> double + { + return i * 1.5; + }; + + etl::vector v_in{1, 2, 3, 4}; + + auto tv = v_in | etl::views::transform(to_double); + + using element_type = etl::remove_cvref_t; + static_assert(etl::is_same::value, "transform element should be the function result type"); + + // Fractional results must survive (they would be truncated to int by the old behaviour). + etl::vector v_out; + for (auto x : tv) + { + v_out.push_back(x); + } + + CHECK_CLOSE(1.5, v_out[0], 0.001); + CHECK_CLOSE(3.0, v_out[1], 0.001); + CHECK_CLOSE(4.5, v_out[2], 0.001); + CHECK_CLOSE(6.0, v_out[3], 0.001); + } + + TEST(test_ranges_transform_view_yields_range_then_join) + { + // A transform that returns a range, joined together, only compiles when the + // transform element type is the range returned by the function. + etl::array samples = {10, 20, 30, 40}; + etl::array dst = {0, 0, 0, 0, 0, 0, 0, 0}; + + auto stereo = samples | etl::views::transform([](int s) { return etl::views::repeat(s, 2); }) | etl::views::join; + + etl::copy(stereo.begin(), stereo.end(), dst.begin()); + + etl::array expected = {10, 10, 20, 20, 30, 30, 40, 40}; + for (size_t i = 0; i < dst.size(); ++i) + { + CHECK_EQUAL(expected[i], dst[i]); + } + } + TEST(test_ranges_transform_view_iterator_increment) { auto square = [](int i) -> int @@ -2177,6 +2226,36 @@ namespace CHECK_EQUAL(v_out_expected, v_out); } + TEST(test_ranges_filter_view_non_const_reference_type) + { + // filter must yield mutable element references when the underlying range + // is non-const, matching std::ranges::filter_view. + std::vector v = {1, 2, 3, 4}; + auto fv = v | etl::views::filter([](int i) { return i % 2 == 0; }); + + using element_ref = decltype(*fv.begin()); + + static_assert(etl::is_same::value, "filter element should be a non-const reference"); + static_assert(!etl::is_const_v>, "filter element should be mutable"); + + *fv.begin() = 99; // first even element is v[1] + CHECK_EQUAL(99, v[1]); + } + + TEST(test_ranges_filter_view_modify_elements) + { + // Double every even element in place; odd elements are left untouched. + std::vector v = {1, 2, 3, 4, 5, 6}; + + for (auto& element : v | etl::views::filter([](int i) { return i % 2 == 0; })) + { + element *= 10; + } + + std::vector expected{1, 20, 3, 40, 5, 60}; + CHECK_EQUAL(expected, v); + } + TEST(test_ranges_join_functional) { using range_type = etl::vector; @@ -2432,6 +2511,101 @@ namespace CHECK_EQUAL(result, v_expected); } + TEST(test_ranges_join_with_view_yields_range_then_join_with) + { + // join_with must support inner ranges whose iterators yield prvalues + // (e.g. repeat_view produced by a type-changing transform), and its declared + // reference type must stay consistent with what operator* actually yields. + etl::array samples = {10, 20, 30}; + etl::array dst = {0, 0, 0, 0, 0, 0, 0, 0}; + + auto stereo = samples | etl::views::transform([](int s) { return etl::views::repeat(s, 2); }) | etl::views::join_with(0); + + using iterator_type = decltype(stereo.begin()); + using declared_reference = typename iterator_type::reference; + using actual_deref = decltype(*etl::declval()); + static_assert(etl::is_same::value, "join_with_iterator::reference must match operator*"); + + etl::copy(stereo.begin(), stereo.end(), dst.begin()); + + etl::array expected = {10, 10, 0, 20, 20, 0, 30, 30}; + for (size_t i = 0; i < dst.size(); ++i) + { + CHECK_EQUAL(expected[i], dst[i]); + } + } + + TEST(test_ranges_join_with_writable_reference) + { + // For a mutable range pattern, join_with must yield mutable references and be + // writable, matching std::ranges::join_with_view (reference is the common + // reference of the inner range's and the pattern's references: int&). + etl::vector, 2> v{{1, 2, 3}, {4, 5, 6}}; + etl::vector pattern{0}; + + auto jv = etl::views::join_with(v, pattern); + + using element_ref = decltype(*jv.begin()); + static_assert(etl::is_same::value, "join_with element should be a non-const reference for a mutable range pattern"); + static_assert(!etl::is_const_v>, "join_with element should be mutable"); + + // Sequence: 1, 2, 3, [0], 4, 5, 6 + auto it = jv.begin(); + *it = 99; // first inner element -> v[0][0] + CHECK_EQUAL(99, v[0][0]); + + ++it; // 2 + ++it; // 3 + ++it; // pattern separator + *it = 77; + CHECK_EQUAL(77, pattern[0]); + } + + TEST(test_ranges_join_with_single_value_reference_is_const) + { + // A single-value pattern is backed by a value (single_view) and is therefore + // read as const, so the common reference degrades to const int&. (This is a + // minor deviation from std::ranges, where a single-element pattern is writable.) + etl::vector, 2> v{{1, 2, 3}, {4, 5, 6}}; + + auto jv = etl::views::join_with(v, 0); + + using element_ref = decltype(*jv.begin()); + static_assert(etl::is_same::value, "single-value join_with pattern should be read as const"); + + etl::vector out; + for (auto x : jv) + { + out.push_back(x); + } + + etl::vector expected{1, 2, 3, 0, 4, 5, 6}; + CHECK_EQUAL(expected, out); + } + + TEST(test_ranges_join_with_common_value_type) + { + // When the inner range and the pattern have different (but common-convertible) + // element types, the element type is their common type, per + // [range.join.with.iterator]. + etl::vector, 2> v{{1, 2, 3}, {4, 5, 6}}; + etl::vector pattern{char(0)}; + + auto jv = etl::views::join_with(v, pattern); + + using element_type = etl::remove_cvref_t; + static_assert(etl::is_same::value, "join_with element type should be the common type of inner and pattern"); + + etl::vector out; + for (auto x : jv) + { + out.push_back(x); + } + + etl::vector expected{1, 2, 3, 0, 4, 5, 6}; + CHECK_EQUAL(expected, out); + } + //************************************************************************* // split_view and views::split tests //************************************************************************* @@ -2597,6 +2771,44 @@ namespace CHECK_EQUAL(idx, expected.size()); } + //************************************************************************* + TEST(test_ranges_split_view_non_const_reference_type) + { + // split subranges must expose mutable element references when the + // underlying range is non-const, matching std::ranges::split_view. + std::vector v_in{1, 0, 2}; + auto sv = etl::ranges::split_view(v_in, 0); + + auto first_seg = *sv.begin(); + using element_ref = decltype(*first_seg.begin()); + + static_assert(etl::is_same::value, "split element should be a non-const reference"); + static_assert(!etl::is_const_v>, "split element should be mutable"); + + *first_seg.begin() = 99; + CHECK_EQUAL(99, v_in[0]); + } + + //************************************************************************* + TEST(test_ranges_split_view_modify_elements) + { + // Split on 0, then negate every element of every segment in place. + std::vector v_in{1, 2, 0, 3, 0, 4, 5}; + auto sv = etl::ranges::split_view(v_in, 0); + + for (auto seg : sv) + { + for (auto& element : seg) + { + element = -element; + } + } + + // Delimiters (the zeros) are not part of any segment and stay untouched. + std::vector expected{-1, -2, 0, -3, 0, -4, -5}; + CHECK_EQUAL(expected, v_in); + } + //************************************************************************* // lazy_split_view and views::lazy_split tests //************************************************************************* @@ -2814,6 +3026,42 @@ namespace CHECK(seg2.empty()); } + TEST(test_ranges_lazy_split_view_non_const_reference_type) + { + // lazy_split inner-range elements must be mutable references when the + // underlying range is non-const, matching std::ranges::lazy_split_view. + std::vector v_in{1, 2, 0, 3}; + auto sv = etl::ranges::lazy_split_view(v_in, 0); + + auto first_seg = *sv.begin(); + using element_ref = decltype(*first_seg.begin()); + + static_assert(etl::is_same::value, "lazy_split element should be a non-const reference"); + static_assert(!etl::is_const_v>, "lazy_split element should be mutable"); + + *first_seg.begin() = 99; + CHECK_EQUAL(99, v_in[0]); + } + + TEST(test_ranges_lazy_split_view_modify_elements) + { + // Split lazily on 0, then double every element of every segment in place. + std::vector v_in{1, 2, 0, 3, 0, 4}; + auto sv = etl::ranges::lazy_split_view(v_in, 0); + + for (auto seg : sv) + { + for (auto& element : seg) + { + element *= 2; + } + } + + // Delimiters (the zeros) are skipped and remain unchanged. + std::vector expected{2, 4, 0, 6, 0, 8}; + CHECK_EQUAL(expected, v_in); + } + TEST(test_counted) { { @@ -3527,6 +3775,39 @@ namespace CHECK(it2 == ev2.end()); } + //************************************************************************* + TEST(test_ranges_elements_view_non_const_reference_type) + { + // elements must yield a mutable reference to the selected tuple element + // when the underlying range is non-const, matching std::ranges::elements_view. + std::vector> v = {{1, 1.1}, {2, 2.2}}; + auto ev = v | etl::views::keys; + + using element_ref = decltype(*ev.begin()); + + static_assert(etl::is_same::value, "elements element should be a non-const reference"); + static_assert(!etl::is_const_v>, "elements element should be mutable"); + + *ev.begin() = 99; + CHECK_EQUAL(99, v[0].first); + } + + //************************************************************************* + TEST(test_ranges_elements_view_modify_elements) + { + // Write through the values (second element) of each pair in place. + std::vector> v = {{1, 10}, {2, 20}, {3, 30}}; + + for (auto& value : v | etl::views::values) + { + value += 1; + } + + CHECK_EQUAL(11, v[0].second); + CHECK_EQUAL(21, v[1].second); + CHECK_EQUAL(31, v[2].second); + } + //************************************************************************* TEST(test_ranges_enumerate_view_basic) { @@ -3635,6 +3916,38 @@ namespace CHECK(it == ev.end()); } + //************************************************************************* + TEST(test_ranges_enumerate_view_non_const_reference_type) + { + // The value element of the tuple must be a mutable reference when the + // underlying range is non-const, matching std::ranges::enumerate_view. + std::vector v = {10, 20, 30}; + auto ev = v | etl::views::enumerate; + + using value_ref = decltype(etl::get<1>(*ev.begin())); + + static_assert(etl::is_same::value, "enumerate value element should be a non-const reference"); + static_assert(!etl::is_const_v>, "enumerate value element should be mutable"); + + etl::get<1>(*ev.begin()) = 99; + CHECK_EQUAL(99, v[0]); + } + + //************************************************************************* + TEST(test_ranges_enumerate_view_modify_elements) + { + // Add the index to each element in place via structured bindings. + std::vector v = {10, 20, 30}; + + for (auto&& [index, value] : v | etl::views::enumerate) + { + value += static_cast(index); + } + + std::vector expected{10, 21, 32}; + CHECK_EQUAL(expected, v); + } + //************************************************************************* TEST(test_ranges_zip_view_basic) { @@ -3800,6 +4113,46 @@ namespace CHECK(it == zv.end()); } + //************************************************************************* + TEST(test_ranges_zip_view_non_const_reference_type) + { + // Each zipped element must be a mutable reference into its source range + // when that range is non-const, matching std::ranges::zip_view. + std::vector v1 = {1, 2, 3}; + std::vector v2 = {'a', 'b', 'c'}; + auto zv = etl::views::zip(v1, v2); + + using first_ref = decltype(etl::get<0>(*zv.begin())); + using second_ref = decltype(etl::get<1>(*zv.begin())); + + static_assert(etl::is_same::value, "zip element 0 should be a non-const reference"); + static_assert(etl::is_same::value, "zip element 1 should be a non-const reference"); + + etl::get<0>(*zv.begin()) = 99; + etl::get<1>(*zv.begin()) = 'Z'; + CHECK_EQUAL(99, v1[0]); + CHECK_EQUAL('Z', v2[0]); + } + + //************************************************************************* + TEST(test_ranges_zip_view_modify_elements) + { + // Write through both zipped ranges in place. + std::vector v1 = {1, 2, 3}; + std::vector v2 = {10, 20, 30}; + + for (auto&& [a, b] : etl::views::zip(v1, v2)) + { + a += 1; + b *= 2; + } + + std::vector expected1{2, 3, 4}; + std::vector expected2{20, 40, 60}; + CHECK_EQUAL(expected1, v1); + CHECK_EQUAL(expected2, v2); + } + //************************************************************************* TEST(test_ranges_zip_transform_view_basic) { @@ -4225,6 +4578,41 @@ namespace CHECK(it == av.end()); } + //************************************************************************* + TEST(test_ranges_adjacent_view_non_const_reference_type) + { + // Each window element must be a mutable reference into the underlying + // range when it is non-const, matching std::ranges::adjacent_view. + std::vector v = {1, 2, 3}; + auto av = etl::views::adjacent<2>(v); + + using first_ref = decltype(etl::get<0>(*av.begin())); + using second_ref = decltype(etl::get<1>(*av.begin())); + + static_assert(etl::is_same::value, "adjacent element 0 should be a non-const reference"); + static_assert(etl::is_same::value, "adjacent element 1 should be a non-const reference"); + + etl::get<0>(*av.begin()) = 99; + CHECK_EQUAL(99, v[0]); + } + + //************************************************************************* + TEST(test_ranges_adjacent_view_modify_elements) + { + // Negate the first element of every adjacent pair in place. Each element + // 0..n-2 is the start of exactly one window; the last element is only a + // second member, so it is left unchanged. + std::vector v = {1, 2, 3, 4}; + + for (auto&& window : etl::views::adjacent<2>(v)) + { + etl::get<0>(window) = -etl::get<0>(window); + } + + std::vector expected{-1, -2, -3, 4}; + CHECK_EQUAL(expected, v); + } + //************************************************************************* TEST(test_ranges_adjacent_transform_view_basic_sum) { @@ -4470,6 +4858,25 @@ namespace CHECK_EQUAL(expected.size(), idx); } + //************************************************************************* + TEST(test_ranges_chunk_view_rvalue_range_owning_view) + { + // Chunking an rvalue range stores it in an owning_view (by value). + // The owning_view's const begin()/end() must still yield the mutable + // 'iterator' rather than a 'const_iterator', otherwise instantiation fails. + auto cv = etl::ranges::views::chunk(std::vector{1, 2, 3, 4, 5, 6, 7}, 3); + + std::vector> expected{{1, 2, 3}, {4, 5, 6}, {7}}; + size_t idx = 0; + for (auto chunk : cv) + { + std::vector actual(chunk.begin(), chunk.end()); + CHECK_EQUAL(expected[idx], actual); + ++idx; + } + CHECK_EQUAL(expected.size(), idx); + } + //************************************************************************* TEST(test_ranges_chunk_view_remainder) { @@ -4658,6 +5065,81 @@ namespace CHECK_EQUAL(expected.size(), idx); } + //************************************************************************* + TEST(test_ranges_chunk_view_non_const_reference_type) + { + // The chunk elements must expose a non-const iterator/reference when the + // underlying range is non-const, matching std::ranges::chunk_view. + std::vector v = {1, 2, 3, 4}; + auto cv = etl::ranges::chunk_view(v, 2); + + auto first_chunk = *cv.begin(); + using element_ref = decltype(*first_chunk.begin()); + + static_assert(etl::is_same::value, "chunk element should be a non-const reference"); + static_assert(!etl::is_const_v>, "chunk element should be mutable"); + + // Prove it at runtime by writing through the chunk. + *first_chunk.begin() = 99; + CHECK_EQUAL(99, v[0]); + } + + //************************************************************************* + TEST(test_ranges_chunk_view_modify_elements) + { + // Write through the chunks and verify the underlying range is mutated. + std::vector v = {1, 2, 3, 4, 5, 6, 7}; + auto cv = etl::ranges::chunk_view(v, 3); + + for (auto chunk : cv) + { + for (auto& element : chunk) + { + element *= 2; + } + } + + std::vector expected{2, 4, 6, 8, 10, 12, 14}; + CHECK_EQUAL(expected, v); + } + + //************************************************************************* + TEST(test_ranges_chunk_view_modify_elements_pipe) + { + // Write through chunks produced via the pipe syntax. + std::vector v = {10, 20, 30, 40, 50}; + + for (auto chunk : v | etl::views::chunk(2)) + { + for (auto& element : chunk) + { + element += 1; + } + } + + std::vector expected{11, 21, 31, 41, 51}; + CHECK_EQUAL(expected, v); + } + + //************************************************************************* + TEST(test_ranges_chunk_view_modify_elements_etl_vector) + { + // Non-const access also works with ETL containers. + etl::vector v = {1, 2, 3, 4, 5, 6}; + auto cv = etl::ranges::chunk_view(v, 2); + + for (auto chunk : cv) + { + for (auto& element : chunk) + { + element = -element; + } + } + + etl::vector expected = {-1, -2, -3, -4, -5, -6}; + CHECK(expected == v); + } + //************************************************************************* // slide_view tests //************************************************************************* @@ -4858,6 +5340,43 @@ namespace CHECK_EQUAL(expected.size(), idx); } + //************************************************************************* + TEST(test_ranges_slide_view_non_const_reference_type) + { + // A slide window must expose mutable element references when the + // underlying range is non-const, matching std::ranges::slide_view. + std::vector v = {1, 2, 3, 4}; + auto sv = etl::ranges::slide_view(v, 2); + + auto first_window = *sv.begin(); + using element_ref = decltype(*first_window.begin()); + + static_assert(etl::is_same::value, "slide window element should be a non-const reference"); + static_assert(!etl::is_const_v>, "slide window element should be mutable"); + + *first_window.begin() = 99; + CHECK_EQUAL(99, v[0]); + } + + //************************************************************************* + TEST(test_ranges_slide_view_modify_elements) + { + // Each underlying element is the start of exactly one window, so writing + // through the first element of every window doubles all but the tail. + std::vector v = {1, 2, 3, 4, 5}; + auto sv = etl::ranges::slide_view(v, 2); + + for (auto window : sv) + { + auto it = window.begin(); + *it *= 2; + } + + // Windows start at indices 0..3, so the final element is untouched. + std::vector expected{2, 4, 6, 8, 5}; + CHECK_EQUAL(expected, v); + } + //************************************************************************* // chunk_by_view tests //************************************************************************* @@ -5070,6 +5589,45 @@ namespace CHECK_EQUAL(expected.size(), idx); } + //************************************************************************* + TEST(test_ranges_chunk_by_view_non_const_reference_type) + { + // chunk_by chunks must expose mutable element references when the + // underlying range is non-const, matching std::ranges::chunk_by_view. + std::vector v = {1, 1, 2}; + auto cv = etl::ranges::chunk_by_view(v, [](int a, int b) { return a == b; }); + + auto first_chunk = *cv.begin(); + using element_ref = decltype(*first_chunk.begin()); + + static_assert(etl::is_same::value, "chunk_by element should be a non-const reference"); + static_assert(!etl::is_const_v>, "chunk_by element should be mutable"); + + *first_chunk.begin() = 99; + CHECK_EQUAL(99, v[0]); + } + + //************************************************************************* + TEST(test_ranges_chunk_by_view_modify_elements) + { + // Group consecutive equal elements, then negate every element in place. + // Each chunk boundary is computed from the chunk start forwards before + // that chunk's elements are modified, so the grouping stays correct. + std::vector v = {1, 1, 2, 2, 2, 3}; + auto cv = etl::ranges::chunk_by_view(v, [](int a, int b) { return a == b; }); + + for (auto chunk : cv) + { + for (auto& element : chunk) + { + element = -element; + } + } + + std::vector expected{-1, -1, -2, -2, -2, -3}; + CHECK_EQUAL(expected, v); + } + //************************************************************************* // stride_view tests //************************************************************************* @@ -5341,6 +5899,40 @@ namespace CHECK_EQUAL(expected, actual); } + //************************************************************************* + TEST(test_ranges_stride_view_non_const_reference_type) + { + // stride must yield mutable element references when the underlying range + // is non-const, matching std::ranges::stride_view. + std::vector v = {1, 2, 3, 4}; + auto sv = etl::ranges::stride_view(v, 2); + + using element_ref = decltype(*sv.begin()); + + static_assert(etl::is_same::value, "stride element should be a non-const reference"); + static_assert(!etl::is_const_v>, "stride element should be mutable"); + + *sv.begin() = 99; + CHECK_EQUAL(99, v[0]); + } + + //************************************************************************* + TEST(test_ranges_stride_view_modify_elements) + { + // Stride by 2 and double every visited element in place. + std::vector v = {1, 2, 3, 4, 5, 6}; + auto sv = etl::ranges::stride_view(v, 2); + + for (auto& element : sv) + { + element *= 2; + } + + // Elements at indices 0, 2, 4 are visited (1, 3, 5 -> 2, 6, 10). + std::vector expected{2, 2, 6, 4, 10, 6}; + CHECK_EQUAL(expected, v); + } + //************************************************************************* TEST(test_ranges_cartesian_product_view_basic) { @@ -5776,6 +6368,37 @@ namespace CHECK_EQUAL(20, v_out[1]); CHECK_EQUAL(30, v_out[2]); } + + TEST(test_ranges_to_input_view_non_const_reference_type) + { + // to_input_view must preserve the underlying range's mutable references, + // matching std::ranges::as_input_view. + std::vector v_in{1, 2, 3}; + auto iv = etl::ranges::to_input_view(v_in); + + using element_ref = decltype(*iv.begin()); + + static_assert(etl::is_same::value, "to_input element should be a non-const reference"); + static_assert(!etl::is_const_v>, "to_input element should be mutable"); + + *iv.begin() = 99; + CHECK_EQUAL(99, v_in[0]); + } + + TEST(test_ranges_to_input_view_modify_elements) + { + // Write through every element of the input view in place. + std::vector v_in{1, 2, 3, 4}; + auto iv = etl::ranges::to_input_view(v_in); + + for (auto& element : iv) + { + element *= 10; + } + + std::vector expected{10, 20, 30, 40}; + CHECK_EQUAL(expected, v_in); + } } #include "etl/private/diagnostic_pop.h" } // namespace