/******************************************************************************
The MIT License(MIT)

Embedded Template Library.
https://github.com/ETLCPP/etl
https://www.etlcpp.com

Copyright(c) 2026 BMW AG

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files(the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions :

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
******************************************************************************/

#include "unit_test_framework.h"

#include "etl/algorithm.h"
#include "etl/array.h"
#include "etl/ranges.h"
#include "etl/vector.h"

#include <array>
#include <ios>
#include <list>
#include <sstream>
#include <vector>

// Issue #1391: include <ranges> when available to verify etl::operator|
// does not conflict with std::ranges::operator|.
#if ETL_USING_STL && ETL_USING_CPP20 && defined(__has_include)
  #if __has_include(<ranges>)
    #include <ranges>
  #endif
#endif

#if ETL_USING_CPP17

  // C++03 does not support move semantics as used in the ranges library
  #if !defined(ETL_FORCE_TEST_CPP03_IMPLEMENTATION)

namespace
{
  class MoveInt
  {
  public:

    MoveInt(int v)
      : _v{v}
    {
    }

    MoveInt(const MoveInt& other)
      : _v{other._v}
    {
    }

    MoveInt& operator=(const MoveInt& other)
    {
      _v = other._v;
      return *this;
    }

    MoveInt(MoveInt&& other)
      : _v{other._v}
    {
      other._v = 0;
    }

    MoveInt& operator=(MoveInt&& other)
    {
      _v       = other._v;
      other._v = 0;
      return *this;
    }

    int get() const
    {
      return _v;
    }

    bool operator==(const MoveInt& other) const
    {
      return _v == other._v;
    }

    bool operator!=(const MoveInt& other) const
    {
      return !(*this == other);
    }

  private:

    int _v;
  };

} // namespace

namespace std
{
  template < typename Container, etl::enable_if_t< etl::is_base_of_v<std::vector<int>, Container> || etl::is_base_of_v<etl::ivector<int>, Container>
                                                     || etl::is_base_of_v<etl::ranges::view_interface<Container>, Container>,
                                                   int> = 0>
  std::basic_ostream<char>& operator<<(std::basic_ostream<char>& s, const Container& v)
  {
    auto it    = v.cbegin();
    auto e     = v.cend();
    bool first = true;
    for (; it != e; ++it)
    {
      if (!first)
      {
        s << " ";
      }
      s << *it;
      first = false;
    }
    return s;
  }

  std::ostringstream& operator<<(std::ostringstream& s, const etl::ivector<MoveInt>& v)
  {
    auto it    = v.cbegin();
    auto e     = v.cend();
    bool first = true;
    for (; it != e; ++it)
    {
      if (!first)
      {
        s << " ";
      }
      s << it->get();
      first = false;
    }
    return s;
  }

  template <class T>
  std::ostringstream& operator<<(std::ostringstream& s, const etl::ranges::range_iterator<T>& v)
  {
    auto value{v.get()};
    s << value;
    return s;
  }
} // namespace std

namespace
{
    #include "etl/private/diagnostic_null_dereference_push.h"
  SUITE(test_ranges)
  {
    //*************************************************************************
    // Iterators.
    //*************************************************************************
    TEST(test_ranges_begin)
    {
      etl::vector<int, 10> v_in{3, 2, 1};

      auto it = etl::ranges::begin(v_in);

      CHECK_EQUAL(*it, 3);
    }

    TEST(test_ranges_end)
    {
      etl::vector<int, 10> v_in{3, 2, 1};

      auto it = etl::ranges::end(v_in);

      CHECK_EQUAL(it, ETL_OR_STD::end(v_in));
    }

    TEST(test_ranges_cbegin)
    {
      etl::vector<int, 10> v_in{3, 2, 1};

      auto it = etl::ranges::cbegin(v_in);

      CHECK_EQUAL(*it, 3);
    }

    TEST(test_ranges_cend)
    {
      etl::vector<int, 10> v_in{3, 2, 1};

      auto it = etl::ranges::cend(v_in);

      CHECK_EQUAL(it, ETL_OR_STD::cend(v_in));
    }

    TEST(test_ranges_rbegin)
    {
      etl::vector<int, 10> v_in{3, 2, 1};

      auto it = etl::ranges::rbegin(v_in);

      CHECK_EQUAL(*it, 1);
    }

    TEST(test_ranges_rend)
    {
      etl::vector<int, 10> v_in{3, 2, 1};

      auto it = etl::ranges::rend(v_in);

      CHECK_EQUAL(&(*it), &(*ETL_OR_STD::rend(v_in)));
    }

    TEST(test_ranges_crbegin)
    {
      etl::vector<int, 10> v_in{3, 2, 1};

      auto it = etl::ranges::crbegin(v_in);

      CHECK_EQUAL(*it, 1);
    }

    TEST(test_ranges_crend)
    {
      etl::vector<int, 10> v_in{3, 2, 1};

      auto it = etl::ranges::crend(v_in);

      CHECK(it == ETL_OR_STD::crend(v_in));
    }

    TEST(test_ranges_size)
    {
      etl::vector<int, 10> v_in{11, 3, 2, 1};

      CHECK_EQUAL(etl::ranges::size(v_in), 4);

      using size_type = decltype(etl::ranges::size(v_in));
      static_assert(etl::is_signed<size_type>::value == false, "Result of size must be unsigned");
    }

    TEST(test_ranges_ssize)
    {
      etl::vector<int, 10> v_in{11, 3, 2, 1};

      CHECK_EQUAL(etl::ranges::ssize(v_in), 4);

      using signed_type   = decltype(etl::ranges::ssize(v_in));
      using unsigned_type = decltype(etl::ranges::size(v_in));
      static_assert(etl::is_signed<signed_type>::value, "Result of ssize must be signed");
      static_assert(sizeof(signed_type) >= sizeof(unsigned_type), "Signed size type needs to be as wide as unsigned size type");
    }

    TEST(test_ranges_empty)
    {
      etl::vector<int, 10> v_in0{11, 3, 2, 1};
      etl::vector<int, 10> v_in1{};

      CHECK_EQUAL(etl::ranges::empty(v_in0), false);
      CHECK_EQUAL(etl::ranges::empty(v_in1), true);
    }

    TEST(test_ranges_data)
    {
      etl::vector<int, 10> v_in{11, 3, 2, 1};

      CHECK_EQUAL(*etl::ranges::data(v_in), 11);
    }

    TEST(test_ranges_cdata)
    {
      etl::vector<int, 10> v_in{11, 3, 2, 1};

      CHECK_EQUAL(*etl::ranges::cdata(v_in), 11);
    }

    //*************************************************************************
    // Range primitives.
    //*************************************************************************
    TEST(test_ranges_iterator_t)
    {
      using range_type = etl::vector<int, 10>;

      static_assert(etl::is_same<etl::ranges::iterator_t<range_type>, int*>::value, "Bad iterator type from etl::ranges::iterator_t");
    }

    TEST(test_ranges_const_iterator_t)
    {
      using range_type = etl::vector<int, 10>;

      static_assert(etl::is_same<etl::ranges::const_iterator_t<range_type>, const int*>::value,
                    "Bad iterator type from etl::ranges::const_iterator_t");
    }

    TEST(test_ranges_sentinel_t)
    {
      using range_type = etl::vector<int, 10>;

      static_assert(etl::is_same<etl::ranges::sentinel_t<range_type>, int*>::value, "Bad sentinel type from etl::ranges::sentinel_t");
    }

    TEST(test_ranges_const_sentinel_t)
    {
      using range_type = etl::vector<int, 10>;

      static_assert(etl::is_same<etl::ranges::const_sentinel_t<range_type>, const int*>::value,
                    "Bad sentinel type from etl::ranges::const_sentinel_t");
    }

    TEST(test_ranges_range_size_t)
    {
      using range_type0 = int[10];
      using range_type1 = etl::vector<int, 10>;
      using range_type2 = etl::ranges::empty_view<int>;

      static_assert(etl::is_same<etl::ranges::range_size_t<range_type0>, size_t>::value, "Bad size type from etl::ranges::range_size_t");
      static_assert(etl::is_same<etl::ranges::range_size_t<range_type1>, size_t>::value, "Bad size type from etl::ranges::range_size_t");
      static_assert(etl::is_same<etl::ranges::range_size_t<range_type2>, size_t>::value, "Bad size type from etl::ranges::range_size_t");
    }

    TEST(test_ranges_range_difference_t)
    {
      using range_type0 = int[10];
      using range_type1 = etl::vector<int, 10>;
      using range_type2 = etl::ranges::empty_view<int>;

      static_assert(etl::is_same<etl::ranges::range_difference_t<range_type0>, ptrdiff_t>::value,
                    "Bad size type from etl::ranges::range_difference_t");
      static_assert(etl::is_same<etl::ranges::range_difference_t<range_type1>, ptrdiff_t>::value,
                    "Bad size type from etl::ranges::range_difference_t");
      static_assert(etl::is_same<etl::ranges::range_difference_t<range_type2>, ptrdiff_t>::value,
                    "Bad size type from etl::ranges::range_difference_t");
    }

    TEST(test_ranges_range_value_t)
    {
      using range_type0 = int[10];
      using range_type1 = etl::vector<int, 10>;
      using range_type2 = etl::ranges::empty_view<int>;

      static_assert(etl::is_same<etl::ranges::range_value_t<range_type0>, int>::value, "Bad size type from etl::ranges::range_value_t");
      static_assert(etl::is_same<etl::ranges::range_value_t<range_type1>, int>::value, "Bad size type from etl::ranges::range_value_t");
      static_assert(etl::is_same<etl::ranges::range_value_t<range_type2>, int>::value, "Bad size type from etl::ranges::range_value_t");
    }

    TEST(test_ranges_subrange)
    {
      etl::vector<int, 10> v{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      auto                 sr = etl::ranges::subrange{v.begin(), v.end()};

      CHECK_EQUAL(sr.begin(), v.begin());
      CHECK_EQUAL(sr.end(), v.end());
      CHECK_EQUAL(sr.empty(), false);
      CHECK_EQUAL(sr.size(), 10);

      sr.advance(1);
      CHECK_EQUAL(sr.size(), 9);
      CHECK_EQUAL(sr[0], 1);

      sr.advance(2);
      CHECK_EQUAL(sr.size(), 7);
      CHECK_EQUAL(sr[0], 3);

      CHECK_EQUAL(sr.next().size(), 6);
      CHECK_EQUAL(sr.next()[0], 4);
      CHECK_EQUAL(sr.next(1).size(), 6);
      CHECK_EQUAL(sr.next(1)[0], 4);
      CHECK_EQUAL(sr.next(2).size(), 5);
      CHECK_EQUAL(sr.next(2)[0], 5);

      CHECK_EQUAL(sr.prev().size(), 8);
      CHECK_EQUAL(sr.prev()[0], 2);
      CHECK_EQUAL(sr.prev(1).size(), 8);
      CHECK_EQUAL(sr.prev(1)[0], 2);
      CHECK_EQUAL(sr.prev(2).size(), 9);
      CHECK_EQUAL(sr.prev(2)[0], 1);
    }

    //*************************************************************************
    // Range factories.
    //*************************************************************************
    TEST(test_ranges_empty_view)
    {
      auto ev = etl::ranges::empty_view<int>{};

      CHECK_EQUAL(ev.begin(), nullptr);
      CHECK_EQUAL(ev.end(), nullptr);
      CHECK_EQUAL(ev.data(), nullptr);
      CHECK_EQUAL(ev.size(), 0);
      CHECK_EQUAL(ev.empty(), true);
    }

    TEST(test_ranges_views_empty)
    {
      auto e = etl::ranges::views::empty<int>;

      CHECK_EQUAL(e.begin(), nullptr);
      CHECK_EQUAL(e.end(), nullptr);
      CHECK_EQUAL(e.data(), nullptr);
      CHECK_EQUAL(e.size(), 0);
      CHECK_EQUAL(e.empty(), true);
    }

    TEST(test_ranges_single_view)
    {
      auto s0 = etl::ranges::single_view<int>(12);
      CHECK_EQUAL(s0.size(), 1);

      auto s = etl::ranges::single_view(23);

      CHECK_EQUAL(*s.begin(), 23);
      CHECK_EQUAL(s.end(), s.begin() + 1);
      CHECK_EQUAL(*s.data(), 23);
      CHECK_EQUAL(s.size(), 1);
      CHECK_EQUAL(s.empty(), false);

      *s.begin() = 45;
      CHECK_EQUAL(*s.data(), 45);
      CHECK_EQUAL(*s.begin(), 45);
    }

    TEST(test_ranges_views_single)
    {
      auto s = etl::ranges::views::single(23);

      CHECK_EQUAL(*s.begin(), 23);
      CHECK_EQUAL(s.end(), s.begin() + 1);
      CHECK_EQUAL(*s.data(), 23);
      CHECK_EQUAL(s.size(), 1);
      CHECK_EQUAL(s.empty(), false);

      *s.begin() = 45;
      CHECK_EQUAL(*s.data(), 45);
      CHECK_EQUAL(*s.begin(), 45);
    }

    TEST(test_ranges_iota_view)
    {
      auto iv = etl::ranges::iota_view(1, 7);

      int compare = 1;
      for (auto i : iv)
      {
        CHECK_EQUAL(i, compare);
        ++compare;
      }

      CHECK_EQUAL(*iv.begin(), 1);
      CHECK_EQUAL(iv.end(), iv.begin() + 6);
      CHECK_EQUAL(iv.size(), 6);
      CHECK_EQUAL(iv.empty(), false);

      CHECK_EQUAL(iv[4], 5);

      auto iv2 = etl::ranges::iota_view(3);
      CHECK_EQUAL(iv2[0], 3);
      CHECK_EQUAL(iv2[1], 4);
      CHECK_EQUAL(iv2[2], 5);
      CHECK_EQUAL(iv2[3], 6);
      CHECK_EQUAL(iv2[4], 7);

      auto iv3 = etl::ranges::iota_view<int>();
      CHECK_EQUAL(iv3.size(), 0);
      CHECK_EQUAL(iv3.end(), iv3.begin());
      CHECK_EQUAL(iv3.empty(), true);
    }

    TEST(test_ranges_views_iota)
    {
      auto iv = etl::ranges::views::iota(1, 7);

      CHECK_EQUAL(*iv.begin(), 1);
      CHECK_EQUAL(iv.end(), iv.begin() + 6);
      CHECK_EQUAL(iv.size(), 6);
      CHECK_EQUAL(iv.empty(), false);

      CHECK_EQUAL(iv[4], 5);
    }

    TEST(test_ranges_iota_view_pipe_take)
    {
      auto iv = etl::ranges::iota_view(3) | etl::views::take(5);

      auto i = iv.begin();
      auto j = i;
      CHECK_EQUAL(*i, 3);
      i++;
      CHECK_EQUAL(*i, 4);
      ++i;
      CHECK_EQUAL(*i, 5);

      CHECK_EQUAL(i - j, 2);

      CHECK_EQUAL(iv.size(), 5);
      CHECK_EQUAL(iv.front(), 3);
      CHECK_EQUAL(iv.back(), 7);
      CHECK_EQUAL(iv[0], 3);
      CHECK_EQUAL(iv[1], 4);
      CHECK_EQUAL(iv[2], 5);
      CHECK_EQUAL(iv[3], 6);
      CHECK_EQUAL(iv[4], 7);
    }

    TEST(test_ranges_repeat_view)
    {
      // bounded
      auto iv = etl::ranges::repeat_view(1, 7);

      for (auto i : iv)
      {
        CHECK_EQUAL(i, 1);
      }

      CHECK_EQUAL(*iv.begin(), 1);
      CHECK_EQUAL(iv.end(), iv.begin() + 7);
      CHECK_EQUAL(iv.size(), 7);
      CHECK_EQUAL(iv.empty(), false);

      CHECK_EQUAL(iv[4], 1);

      // unbounded
      auto iv2 = etl::ranges::repeat_view(3);
      CHECK_EQUAL(iv2[0], 3);
      CHECK_EQUAL(iv2[1], 3);
      CHECK_EQUAL(iv2[2], 3);
      CHECK_EQUAL(iv2[3], 3);
      CHECK_EQUAL(iv2[4], 3);

      auto iv3 = etl::ranges::repeat_view<int>();
      CHECK_EQUAL(iv3.size(), 0);
      CHECK_EQUAL(iv3.end(), iv3.begin());
      CHECK_EQUAL(iv3.empty(), true);
    }

    TEST(test_ranges_views_repeat)
    {
      auto iv = etl::ranges::views::repeat(1, 7);

      CHECK_EQUAL(*iv.begin(), 1);
      CHECK_EQUAL(iv.end(), iv.begin() + 7);
      CHECK_EQUAL(iv.size(), 7);
      CHECK_EQUAL(iv.empty(), false);

      CHECK_EQUAL(iv[4], 1);
    }

    TEST(test_ranges_repeat_view_pipe_take)
    {
      auto iv = etl::ranges::repeat_view(3) | etl::views::take(5);

      auto i = iv.begin();
      CHECK_EQUAL(*i, 3);
      i++;
      CHECK_EQUAL(*i, 3);
      ++i;
      CHECK_EQUAL(*i, 3);

      CHECK_EQUAL(iv.size(), 5);
      CHECK_EQUAL(iv.front(), 3);
      CHECK_EQUAL(iv.back(), 3);
      CHECK_EQUAL(iv[0], 3);
      CHECK_EQUAL(iv[1], 3);
      CHECK_EQUAL(iv[2], 3);
      CHECK_EQUAL(iv[3], 3);
      CHECK_EQUAL(iv[4], 3);
    }

    TEST(test_ranges_repeat_view_pipe_take_bounded)
    {
      auto iv = etl::ranges::repeat_view(3, 30) | etl::views::take(5);

      auto i = iv.begin();
      CHECK_EQUAL(*i, 3);
      i++;
      CHECK_EQUAL(*i, 3);
      ++i;
      CHECK_EQUAL(*i, 3);

      CHECK_EQUAL(iv.size(), 5);
      CHECK_EQUAL(iv.front(), 3);
      CHECK_EQUAL(iv.back(), 3);
      CHECK_EQUAL(iv[0], 3);
      CHECK_EQUAL(iv[1], 3);
      CHECK_EQUAL(iv[2], 3);
      CHECK_EQUAL(iv[3], 3);
      CHECK_EQUAL(iv[4], 3);
    }

    TEST(test_ranges_repeat_view_pipe_take_bounded_limited)
    {
      auto iv = etl::ranges::repeat_view(3, 4) | etl::views::take(5);

      auto i = iv.begin();
      CHECK_EQUAL(*i, 3);
      i++;
      CHECK_EQUAL(*i, 3);
      ++i;
      CHECK_EQUAL(*i, 3);

      CHECK_EQUAL(iv.size(), 4);
      CHECK_EQUAL(iv.front(), 3);
      CHECK_EQUAL(iv.back(), 3);
      CHECK_EQUAL(iv[0], 3);
      CHECK_EQUAL(iv[1], 3);
      CHECK_EQUAL(iv[2], 3);
      CHECK_EQUAL(iv[3], 3);
    }

    //*************************************************************************
    // Range adaptors
    //*************************************************************************
    TEST(test_ranges_iterate_c_array)
    {
      auto even = [](int i) -> bool
      {
        return 0 == i % 2;
      };

      int                  v_in[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 2, 4, 6, 8};

      for (int i : etl::views::filter(v_in, even))
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_iterate_plain)
    {
      auto even = [](int i) -> bool
      {
        return 0 == i % 2;
      };

      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 2, 4, 6, 8};

      for (int i : etl::views::filter(v_in, even))
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_iterate_pipe)
    {
      auto even = [](int i) -> bool
      {
        return 0 == i % 2;
      };

      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 2, 4, 6, 8};

      for (int i : v_in | etl::views::filter(even))
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_drop_functional)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{2, 3, 4, 5, 6, 7, 8, 9};

      auto rv = etl::views::drop(v_in, 2);

      CHECK_EQUAL(etl::views::all(v_out_expected), rv);
      CHECK_EQUAL(rv.base().base(), v_in);
      CHECK_EQUAL(*rv.begin(), 2);
      CHECK_EQUAL(rv.size(), 8);

      CHECK_EQUAL(*rv.cbegin(), 2);
      CHECK_EQUAL(rv.empty(), false);
      CHECK_EQUAL(rv, true);
      CHECK_EQUAL(rv.front(), 2);
      CHECK_EQUAL(rv.back(), 9);
      CHECK_EQUAL(rv[7], 9);

      rv[1] = 33;
      CHECK_EQUAL(rv[1], 33);
      CHECK_EQUAL(v_in[3], 33);

      v_in[2] = 44;
      CHECK_EQUAL(rv[0], 44);
      CHECK_EQUAL(v_in[2], 44);
    }

    TEST(test_ranges_iterate_pipe_drop)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{2, 3, 4, 5, 6, 7, 8, 9};

      auto rv = v_in | etl::views::drop(2);
      for (int i : rv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
      CHECK_EQUAL(rv.base().base(), v_in);
      CHECK_EQUAL(*rv.begin(), 2);
      CHECK_EQUAL(rv.size(), 8);

      CHECK_EQUAL(*rv.cbegin(), 2);
      CHECK_EQUAL(rv.empty(), false);
      CHECK_EQUAL(rv, true);
      CHECK_EQUAL(rv.front(), 2);
      CHECK_EQUAL(rv.back(), 9);
      CHECK_EQUAL(rv[7], 9);

      rv[1] = 33;
      CHECK_EQUAL(rv[1], 33);
      CHECK_EQUAL(v_in[3], 33);

      v_in[2] = 44;
      CHECK_EQUAL(rv[0], 44);
      CHECK_EQUAL(v_in[2], 44);
    }

    TEST(test_ranges_iterate_pipe_drop_out_of_bounds)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{};

      auto rv = v_in | etl::views::drop(12);
      for (int i : rv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
      CHECK_EQUAL(rv.size(), 0);
    }

    TEST(test_ranges_iterate_pipe_twice)
    {
      auto even = [](int i) -> bool
      {
        return 0 == i % 2;
      };

      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{2, 4, 6, 8};

      for (int i : v_in | etl::views::filter(even) | etl::views::drop(1))
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_iterate_pipe_drop_while)
    {
      auto below_three = [](int i) -> bool
      {
        return i < 3;
      };

      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{3, 4, 5, 6, 7, 8, 9};

      auto rv = v_in | etl::views::drop_while(below_three);
      for (int i : rv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
      CHECK_EQUAL(rv.base().base(), v_in);
      CHECK_EQUAL(*rv.begin(), 3);
      CHECK_EQUAL(rv.size(), 7);

      CHECK_EQUAL(*rv.cbegin(), 3);
      CHECK_EQUAL(rv.empty(), false);
      CHECK_EQUAL(rv, true);
      CHECK_EQUAL(rv.front(), 3);
      CHECK_EQUAL(rv.back(), 9);
      CHECK_EQUAL(rv[6], 9);
    }

    TEST(test_ranges_take_functional)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out_expected{0, 1, 2, 3};

      auto rv = etl::views::take(v_in, 4);

      CHECK_EQUAL(etl::views::all(v_out_expected), rv);
      CHECK_EQUAL(rv.base().base(), v_in);
      CHECK_EQUAL(*rv.begin(), 0);
      CHECK_EQUAL(rv.size(), 4);

      CHECK_EQUAL(*rv.cbegin(), 0);
      CHECK_EQUAL(rv.empty(), false);
      CHECK_EQUAL(rv, true);
      CHECK_EQUAL(rv.front(), 0);
      CHECK_EQUAL(rv.back(), 3);
      CHECK_EQUAL(rv[2], 2);

      rv[2] = 33;
      CHECK_EQUAL(rv[2], 33);
      CHECK_EQUAL(v_in[2], 33);

      v_in[3] = 44;
      CHECK_EQUAL(rv[3], 44);
      CHECK_EQUAL(v_in[3], 44);
    }

    TEST(test_ranges_iterate_pipe_take)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 1, 2, 3};

      auto rv = v_in | etl::views::take(4);
      for (int i : rv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
      CHECK_EQUAL(rv.base().base(), v_in);
      CHECK_EQUAL(*rv.begin(), 0);
      CHECK_EQUAL(rv.size(), 4);

      CHECK_EQUAL(*rv.cbegin(), 0);
      CHECK_EQUAL(rv.empty(), false);
      CHECK_EQUAL(rv, true);
      CHECK_EQUAL(rv.front(), 0);
      CHECK_EQUAL(rv.back(), 3);
      CHECK_EQUAL(rv[2], 2);

      rv[2] = 33;
      CHECK_EQUAL(rv[2], 33);
      CHECK_EQUAL(v_in[2], 33);

      v_in[3] = 44;
      CHECK_EQUAL(rv[3], 44);
      CHECK_EQUAL(v_in[3], 44);
    }

    TEST(test_ranges_iterate_pipe_take_while)
    {
      auto below_three = [](int i) -> bool
      {
        return i < 3;
      };

      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 1, 2};

      auto rv = v_in | etl::views::take_while(below_three);
      for (int i : rv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
      CHECK_EQUAL(rv.base().base(), v_in);
      CHECK_EQUAL(*rv.begin(), 0);
      CHECK_EQUAL(rv.size(), 3);

      CHECK_EQUAL(*rv.cbegin(), 0);
      CHECK_EQUAL(rv.empty(), false);
      CHECK_EQUAL(rv, true);
      CHECK_EQUAL(rv.front(), 0);
      CHECK_EQUAL(rv.back(), 2);
      CHECK_EQUAL(rv[2], 2);
    }

    TEST(test_ranges_reverse_view_functional)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{4, 3, 2, 1, 0};

      auto rv = etl::ranges::reverse_view(v_in);
      for (int i : rv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
      CHECK_EQUAL(rv.base().base(), v_in);
      CHECK_EQUAL(*rv.begin(), 4);
      CHECK_EQUAL(rv.size(), 5);

      CHECK_EQUAL(*rv.cbegin(), 4);
      CHECK_EQUAL(rv.empty(), false);
      CHECK_EQUAL(rv, true);
      CHECK_EQUAL(rv.front(), 4);
      CHECK_EQUAL(rv.back(), 0);
      CHECK_EQUAL(rv[2], 2);

      rv[0] = 22;
      CHECK_EQUAL(rv[0], 22);
      CHECK_EQUAL(v_in[4], 22);

      v_in[0] = 11;
      CHECK_EQUAL(rv.back(), 11);
      CHECK_EQUAL(rv[4], 11);
    }

    TEST(test_ranges_iterate_pipe_reverse)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{4, 3, 2, 1, 0};

      auto rv = v_in | etl::views::reverse;
      for (int i : rv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
      CHECK_EQUAL(rv.base().base(), v_in);
      CHECK_EQUAL(*rv.begin(), 4);
      CHECK_EQUAL(rv.size(), 5);
      CHECK_EQUAL(rv.front(), 4);
      CHECK_EQUAL(rv.back(), 0);
    }

    TEST(test_ranges_iterate_pipe_reverse_reverse)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 1, 2, 3, 4};

      auto rv = v_in | etl::views::reverse | etl::views::reverse;
      for (int i : rv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
      CHECK_EQUAL(rv.base(), v_in);
      CHECK_EQUAL(*rv.begin(), 0);
      CHECK_EQUAL(rv.size(), 5);
      CHECK_EQUAL(rv.front(), 0);
      CHECK_EQUAL(rv.back(), 4);
    }

    TEST(test_ranges_iterate_pipe_all)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

      for (int i : v_in | etl::views::all)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_iterate_pipe_ref)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

      auto r = v_in | etl::views::ref();
      for (int i : r)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);

      v_in[9] = 99;
      CHECK_EQUAL(r[9], 99);

      CHECK_EQUAL(r.base(), v_in);
      CHECK_EQUAL(*r.begin(), 0);
      CHECK_EQUAL(r.end(), r.begin() + 10);
      CHECK_EQUAL(r.empty(), false);
      CHECK_EQUAL(r.size(), 10);
      CHECK_EQUAL(r.data(), v_in.data());
    }

    TEST(test_ranges_iterate_pipe_owning)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

      auto r = v_in | etl::views::owning();
      for (int i : r)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);

      CHECK_EQUAL(0, v_in.size());

      etl::ranges::owning_view<etl::vector<int, 10>> ov2;

      CHECK_NOT_EQUAL(r.base(), v_in);
      CHECK_EQUAL(*r.begin(), 0);
      CHECK_EQUAL(r.end(), r.begin() + 10);
      CHECK_EQUAL(r.empty(), false);
      CHECK_EQUAL(r.size(), 10);
      CHECK_NOT_EQUAL(r.data(), v_in.data());

      // ov2 = r; // expected: compile error!
      ov2 = etl::move(r);
    }

    TEST(test_ranges_iterate_pipe_to)
    {
      auto even = [](int i) -> bool
      {
        return 0 == i % 2;
      };

      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out_expected{2, 4, 6, 8};

      auto v_out = v_in | etl::views::filter(even) | etl::views::drop(1) | etl::ranges::to<etl::vector<int, 10>>();

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_iterate_pipe_as_rvalue)
    {
      etl::vector<MoveInt, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<MoveInt, 10> v_out;

      etl::vector<MoveInt, 10> v_out_expected_0{0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
      etl::vector<MoveInt, 10> v_out_expected_1{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

      for (auto&& i : v_in | etl::views::as_rvalue)
      {
        v_out.emplace_back(etl::move(i));
      }

      CHECK_EQUAL(v_out_expected_0, v_in);
      CHECK_EQUAL(v_out_expected_1, v_out);
    }

    TEST(test_ranges_iterate_pipe_as_rvalue_functional)
    {
      etl::vector<MoveInt, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<MoveInt, 10> v_out;

      etl::vector<MoveInt, 10> v_out_expected_0{0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
      etl::vector<MoveInt, 10> v_out_expected_1{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

      v_out = etl::views::as_rvalue(v_in) | etl::ranges::to<etl::vector<MoveInt, 10>>();

      CHECK_EQUAL(v_out_expected_0, v_in);
      CHECK_EQUAL(v_out_expected_1, v_out);
    }

    TEST(test_ranges_as_const_view_functional)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

      auto cv = etl::ranges::as_const_view(v_in);

      // Iterator type should be const_iterator
      using cv_reference = decltype(*cv.begin());
      static_assert(etl::is_const_v<etl::remove_reference_t<cv_reference>>, "as_const_view iterator should dereference to const");

      for (auto i : cv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
      CHECK_EQUAL(cv.size(), 10u);
      CHECK_EQUAL(cv.empty(), false);
      CHECK_EQUAL(cv.front(), 0);
      CHECK_EQUAL(cv.back(), 9);
    }

    TEST(test_ranges_as_const_view_reflects_base_changes)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4};

      auto cv = etl::ranges::as_const_view(v_in);

      CHECK_EQUAL(cv.front(), 0);
      CHECK_EQUAL(cv.back(), 4);
      CHECK_EQUAL(cv.size(), 5u);

      v_in[0] = 99;
      v_in[4] = 77;
      CHECK_EQUAL(cv.front(), 99);
      CHECK_EQUAL(cv.back(), 77);
    }

    TEST(test_ranges_as_const_view_pipe)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

      auto cv = v_in | etl::views::as_const();

      using cv_reference = decltype(*cv.begin());
      static_assert(etl::is_const_v<etl::remove_reference_t<cv_reference>>, "piped as_const view iterator should dereference to const");

      for (auto i : cv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
      CHECK_EQUAL(cv.size(), 10u);
    }

    TEST(test_ranges_views_as_const_functional)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

      auto cv = etl::views::as_const(v_in);

      using cv_reference = decltype(*cv.begin());
      static_assert(etl::is_const_v<etl::remove_reference_t<cv_reference>>, "views::as_const iterator should dereference to const");

      for (auto i : cv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_as_const_view_pipe_chained_with_take)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 1, 2};

      auto cv = v_in | etl::views::as_const() | etl::views::take(3);

      for (auto i : cv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_as_const_view_pipe_chained_with_drop)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{7, 8, 9};

      auto cv = v_in | etl::views::as_const() | etl::views::drop(7);

      for (auto i : cv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_as_const_view_pipe_chained_with_reverse)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{4, 3, 2, 1, 0};

      auto cv = v_in | etl::views::as_const() | etl::views::reverse;

      for (auto i : cv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_as_const_view_with_std_vector)
    {
      std::vector<int> v_in{0, 1, 2, 3, 4};
      std::vector<int> v_out;
      std::vector<int> v_out_expected{0, 1, 2, 3, 4};

      auto cv = etl::ranges::as_const_view(v_in);

      using cv_reference = decltype(*cv.begin());
      static_assert(etl::is_const_v<etl::remove_reference_t<cv_reference>>, "as_const_view over std::vector should dereference to const");

      for (auto i : cv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected.size(), v_out.size());
      for (size_t idx = 0; idx < v_out_expected.size(); ++idx)
      {
        CHECK_EQUAL(v_out_expected[idx], v_out[idx]);
      }
    }

    //*************************************************************************
    /// cache_latest_view tests
    //*************************************************************************
    TEST(test_ranges_cache_latest_view_functional)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

      auto cv = etl::ranges::cache_latest_view(v_in);

      for (auto i : cv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
      CHECK_EQUAL(cv.size(), 10u);
      CHECK_EQUAL(cv.empty(), false);
    }

    TEST(test_ranges_cache_latest_view_caches_value)
    {
      int  dereference_count  = 0;
      auto counting_transform = [&dereference_count](int i) -> int
      {
        ++dereference_count;
        return i * 2;
      };

      etl::vector<int, 10> v_in{1, 2, 3};

      auto tv = v_in | etl::views::transform(counting_transform) | etl::views::cache_latest();

      auto it = tv.begin();

      // First dereference should evaluate
      dereference_count = 0;
      int val1          = *it;
      CHECK_EQUAL(1, dereference_count);
      CHECK_EQUAL(2, val1);

      // Second dereference of same position should use cache (no extra
      // transform call)
      int val2 = *it;
      CHECK_EQUAL(1, dereference_count);
      CHECK_EQUAL(2, val2);

      // Advance and dereference
      ++it;
      int val3 = *it;
      CHECK_EQUAL(2, dereference_count);
      CHECK_EQUAL(4, val3);
    }

    TEST(test_ranges_cache_latest_view_pipe)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

      auto cv = v_in | etl::views::cache_latest();

      for (auto i : cv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_views_cache_latest_functional)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

      auto cv = etl::views::cache_latest(v_in);

      for (auto i : cv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_cache_latest_view_pipe_chained_with_take)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 1, 2};

      auto cv = v_in | etl::views::cache_latest() | etl::views::take(3);

      for (auto i : cv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_cache_latest_view_pipe_chained_with_transform)
    {
      auto square = [](int i) -> int
      {
        return i * i;
      };

      etl::vector<int, 10> v_in{0, 1, 2, 3, 4};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 1, 4, 9, 16};

      auto cv = v_in | etl::views::transform(square) | etl::views::cache_latest();

      for (auto i : cv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_cache_latest_view_empty_range)
    {
      etl::vector<int, 10> v_in;

      auto cv = etl::ranges::cache_latest_view(v_in);

      CHECK_EQUAL(cv.empty(), true);
      CHECK_EQUAL(cv.size(), 0u);
      CHECK(cv.begin() == cv.end());
    }

    TEST(test_ranges_cache_latest_view_with_std_vector)
    {
      std::vector<int> v_in{0, 1, 2, 3, 4};
      std::vector<int> v_out;
      std::vector<int> v_out_expected{0, 1, 2, 3, 4};

      auto cv = etl::ranges::cache_latest_view(v_in);

      for (auto i : cv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected.size(), v_out.size());
      for (size_t idx = 0; idx < v_out_expected.size(); ++idx)
      {
        CHECK_EQUAL(v_out_expected[idx], v_out[idx]);
      }
    }

    TEST(test_ranges_iterate_transform)
    {
      auto square = [](int i) -> int
      {
        return i * i;
      };

      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 1, 4, 9, 16, 25, 36, 49, 64, 81};

      for (int i : v_in | etl::views::transform(square))
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    //*************************************************************************
    /// transform_view tests
    //*************************************************************************
    TEST(test_ranges_transform_view_functional)
    {
      auto square = [](int i) -> int
      {
        return i * i;
      };

      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 1, 4, 9, 16, 25, 36, 49, 64, 81};

      auto tv = etl::views::transform(v_in, square);

      for (auto i : tv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_transform_view_direct_construction)
    {
      auto negate = [](int i) -> int
      {
        return -i;
      };

      etl::vector<int, 5> v_in{1, 2, 3, 4, 5};
      etl::vector<int, 5> v_out;
      etl::vector<int, 5> v_out_expected{-1, -2, -3, -4, -5};

      auto tv = etl::ranges::transform_view(etl::views::all(v_in), negate);

      for (auto i : tv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_transform_view_pipe)
    {
      auto triple = [](int i) -> int
      {
        return i * 3;
      };

      etl::vector<int, 5> v_in{1, 2, 3, 4, 5};
      etl::vector<int, 5> v_out;
      etl::vector<int, 5> v_out_expected{3, 6, 9, 12, 15};

      auto tv = v_in | etl::views::transform(triple);

      for (auto i : tv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_transform_view_size)
    {
      auto identity = [](int i) -> int
      {
        return i;
      };

      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

      auto tv = v_in | etl::views::transform(identity);

      CHECK_EQUAL(10u, tv.size());
    }

    TEST(test_ranges_transform_view_empty)
    {
      auto identity = [](int i) -> int
      {
        return i;
      };

      etl::vector<int, 10> v_in;

      auto tv = v_in | etl::views::transform(identity);

      CHECK_EQUAL(true, tv.empty());
      CHECK_EQUAL(0u, tv.size());
    }

    TEST(test_ranges_transform_view_non_empty)
    {
      auto identity = [](int i) -> int
      {
        return i;
      };

      etl::vector<int, 10> v_in{42};

      auto tv = v_in | etl::views::transform(identity);

      CHECK_EQUAL(false, tv.empty());
      CHECK_EQUAL(1u, tv.size());
    }

    TEST(test_ranges_transform_view_front)
    {
      auto square = [](int i) -> int
      {
        return i * i;
      };

      etl::vector<int, 5> v_in{3, 4, 5};

      auto tv = v_in | etl::views::transform(square);

      CHECK_EQUAL(9, tv.front());
    }

    TEST(test_ranges_transform_view_index_operator)
    {
      auto square = [](int i) -> int
      {
        return i * i;
      };

      etl::vector<int, 5> v_in{1, 2, 3, 4, 5};

      auto tv = v_in | etl::views::transform(square);

      CHECK_EQUAL(1, tv[0]);
      CHECK_EQUAL(4, tv[1]);
      CHECK_EQUAL(9, tv[2]);
      CHECK_EQUAL(16, tv[3]);
      CHECK_EQUAL(25, tv[4]);
    }

    TEST(test_ranges_transform_view_type_change)
    {
      auto to_double = [](int i) -> double
      {
        return i * 1.5;
      };

      etl::vector<int, 5> v_in{2, 4, 6};

      auto tv = v_in | etl::views::transform(to_double);

      auto it = tv.begin();
      CHECK_CLOSE(3.0, *it, 0.001);
      ++it;
      CHECK_CLOSE(6.0, *it, 0.001);
      ++it;
      CHECK_CLOSE(9.0, *it, 0.001);
      ++it;
      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<int, 4> v_in{1, 2, 3, 4};

      auto tv = v_in | etl::views::transform(to_double);

      using element_type = etl::remove_cvref_t<decltype(*tv.begin())>;
      static_assert(etl::is_same<double, element_type>::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<double, 4> 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<int, 4> samples = {10, 20, 30, 40};
      etl::array<int, 8> 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<int, 8> 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
      {
        return i * i;
      };

      etl::vector<int, 5> v_in{1, 2, 3};

      auto tv = v_in | etl::views::transform(square);

      // Pre-increment
      auto it = tv.begin();
      CHECK_EQUAL(1, *it);
      ++it;
      CHECK_EQUAL(4, *it);

      // Post-increment
      auto it2 = tv.begin();
      auto old = it2++;
      CHECK_EQUAL(1, *old);
      CHECK_EQUAL(4, *it2);
    }

    TEST(test_ranges_transform_view_iterator_equality)
    {
      auto identity = [](int i) -> int
      {
        return i;
      };

      etl::vector<int, 5> v_in{1, 2, 3};

      auto tv = v_in | etl::views::transform(identity);

      auto b = tv.begin();
      auto e = tv.end();
      CHECK(b != e);
      CHECK(b == tv.begin());
      CHECK(e == tv.end());
    }

    TEST(test_ranges_transform_view_chained_with_take)
    {
      auto square = [](int i) -> int
      {
        return i * i;
      };

      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 1, 4};

      auto tv = v_in | etl::views::transform(square) | etl::views::take(3);

      for (auto i : tv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_transform_view_chained_with_temporary)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 1, 4};

      auto tv = v_in | etl::views::transform([](int i) -> int { return i * i; }) | etl::views::take(3);

      for (auto i : tv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_transform_view_chained_with_drop)
    {
      auto square = [](int i) -> int
      {
        return i * i;
      };

      etl::vector<int, 10> v_in{0, 1, 2, 3, 4};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{9, 16};

      auto tv = v_in | etl::views::transform(square) | etl::views::drop(3);

      for (auto i : tv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_transform_view_chained_transforms)
    {
      auto add_one = [](int i) -> int
      {
        return i + 1;
      };
      auto multiply = [](int i) -> int
      {
        return i * 10;
      };

      etl::vector<int, 5> v_in{0, 1, 2, 3, 4};
      etl::vector<int, 5> v_out;
      etl::vector<int, 5> v_out_expected{10, 20, 30, 40, 50};

      auto tv = v_in | etl::views::transform(add_one) | etl::views::transform(multiply);

      for (auto i : tv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_transform_view_with_std_vector)
    {
      auto square = [](int i) -> int
      {
        return i * i;
      };

      std::vector<int> v_in{1, 2, 3, 4, 5};
      std::vector<int> v_out;
      std::vector<int> v_out_expected{1, 4, 9, 16, 25};

      auto tv = v_in | etl::views::transform(square);

      for (auto i : tv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected.size(), v_out.size());
      for (size_t idx = 0; idx < v_out_expected.size(); ++idx)
      {
        CHECK_EQUAL(v_out_expected[idx], v_out[idx]);
      }
    }

    TEST(test_ranges_transform_view_with_std_array)
    {
      auto square = [](int i) -> int
      {
        return i * i;
      };

      std::array<int, 5> v_in{1, 2, 3, 4, 5};
      std::vector<int>   v_out;
      std::vector<int>   v_out_expected{1, 4, 9, 16, 25};

      auto tv = v_in | etl::views::transform(square);

      for (auto i : tv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected.size(), v_out.size());
      for (size_t idx = 0; idx < v_out_expected.size(); ++idx)
      {
        CHECK_EQUAL(v_out_expected[idx], v_out[idx]);
      }
    }

    TEST(test_ranges_transform_view_single_element)
    {
      auto negate = [](int i) -> int
      {
        return -i;
      };

      etl::vector<int, 5> v_in{42};
      etl::vector<int, 5> v_out;
      etl::vector<int, 5> v_out_expected{-42};

      auto tv = v_in | etl::views::transform(negate);

      for (auto i : tv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    //*************************************************************************
    /// filter_view tests
    //*************************************************************************
    TEST(test_ranges_filter_view_functional)
    {
      auto even = [](int i) -> bool
      {
        return 0 == i % 2;
      };

      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 2, 4, 6, 8};

      auto fv = etl::views::filter(v_in, even);

      for (auto i : fv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_filter_view_direct_construction)
    {
      auto odd = [](int i) -> bool
      {
        return 1 == i % 2;
      };

      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{1, 3, 5, 7, 9};

      auto fv = etl::ranges::filter_view(etl::views::all(v_in), odd);

      for (auto i : fv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_filter_view_pipe)
    {
      auto even = [](int i) -> bool
      {
        return 0 == i % 2;
      };

      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 2, 4, 6, 8};

      auto fv = v_in | etl::views::filter(even);

      for (auto i : fv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_filter_view_size)
    {
      auto even = [](int i) -> bool
      {
        return 0 == i % 2;
      };

      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

      auto fv = v_in | etl::views::filter(even);

      CHECK_EQUAL(5u, fv.size());
    }

    TEST(test_ranges_filter_view_empty)
    {
      auto even = [](int i) -> bool
      {
        return 0 == i % 2;
      };

      etl::vector<int, 10> v_in;

      auto fv = v_in | etl::views::filter(even);

      CHECK_EQUAL(true, fv.empty());
      CHECK_EQUAL(0u, fv.size());
    }

    TEST(test_ranges_filter_view_non_empty)
    {
      auto even = [](int i) -> bool
      {
        return 0 == i % 2;
      };

      etl::vector<int, 10> v_in{42};

      auto fv = v_in | etl::views::filter(even);

      CHECK_EQUAL(false, fv.empty());
      CHECK_EQUAL(1u, fv.size());
    }

    TEST(test_ranges_filter_view_all_filtered_out)
    {
      auto even = [](int i) -> bool
      {
        return 0 == i % 2;
      };

      etl::vector<int, 10> v_in{1, 3, 5, 7, 9};

      auto fv = v_in | etl::views::filter(even);

      CHECK_EQUAL(true, fv.empty());
      CHECK_EQUAL(0u, fv.size());
    }

    TEST(test_ranges_filter_view_all_pass)
    {
      auto even = [](int i) -> bool
      {
        return 0 == i % 2;
      };

      etl::vector<int, 10> v_in{0, 2, 4, 6, 8};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 2, 4, 6, 8};

      auto fv = v_in | etl::views::filter(even);

      for (auto i : fv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
      CHECK_EQUAL(5u, fv.size());
    }

    TEST(test_ranges_filter_view_front)
    {
      auto even = [](int i) -> bool
      {
        return 0 == i % 2;
      };

      etl::vector<int, 10> v_in{1, 2, 3, 4, 5};

      auto fv = v_in | etl::views::filter(even);

      CHECK_EQUAL(2, fv.front());
    }

    TEST(test_ranges_filter_view_index_operator)
    {
      auto even = [](int i) -> bool
      {
        return 0 == i % 2;
      };

      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

      auto fv = v_in | etl::views::filter(even);

      CHECK_EQUAL(0, fv[0]);
      CHECK_EQUAL(2, fv[1]);
      CHECK_EQUAL(4, fv[2]);
      CHECK_EQUAL(6, fv[3]);
      CHECK_EQUAL(8, fv[4]);
    }

    TEST(test_ranges_filter_view_iterator_increment)
    {
      auto even = [](int i) -> bool
      {
        return 0 == i % 2;
      };

      etl::vector<int, 10> v_in{0, 1, 2, 3, 4};

      auto fv = v_in | etl::views::filter(even);

      // Pre-increment
      auto it = fv.begin();
      CHECK_EQUAL(0, *it);
      ++it;
      CHECK_EQUAL(2, *it);

      // Post-increment
      auto it2 = fv.begin();
      auto old = it2++;
      CHECK_EQUAL(0, *old);
      CHECK_EQUAL(2, *it2);
    }

    TEST(test_ranges_filter_view_iterator_equality)
    {
      auto even = [](int i) -> bool
      {
        return 0 == i % 2;
      };

      etl::vector<int, 10> v_in{0, 1, 2, 3, 4};

      auto fv = v_in | etl::views::filter(even);

      auto b = fv.begin();
      auto e = fv.end();
      CHECK(b != e);
      CHECK(b == fv.begin());
      CHECK(e == fv.end());
    }

    TEST(test_ranges_filter_view_chained_with_take)
    {
      auto even = [](int i) -> bool
      {
        return 0 == i % 2;
      };

      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 2};

      auto fv = v_in | etl::views::filter(even) | etl::views::take(2);

      for (auto i : fv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_filter_view_chained_with_temporary)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 2};

      auto fv = v_in | etl::views::filter([](int i) -> bool { return 0 == i % 2; }) | etl::views::take(2);

      for (auto i : fv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_filter_view_chained_with_drop)
    {
      auto even = [](int i) -> bool
      {
        return 0 == i % 2;
      };

      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{4, 6, 8};

      auto fv = v_in | etl::views::filter(even) | etl::views::drop(2);

      for (auto i : fv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_filter_view_chained_with_transform)
    {
      auto even = [](int i) -> bool
      {
        return 0 == i % 2;
      };
      auto square = [](int i) -> int
      {
        return i * i;
      };

      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 4, 16, 36, 64};

      auto fv = v_in | etl::views::filter(even) | etl::views::transform(square);

      for (auto i : fv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_filter_view_with_std_vector)
    {
      auto even = [](int i) -> bool
      {
        return 0 == i % 2;
      };

      std::vector<int> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      std::vector<int> v_out;
      std::vector<int> v_out_expected{0, 2, 4, 6, 8};

      auto fv = v_in | etl::views::filter(even);

      for (auto i : fv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected.size(), v_out.size());
      for (size_t idx = 0; idx < v_out_expected.size(); ++idx)
      {
        CHECK_EQUAL(v_out_expected[idx], v_out[idx]);
      }
    }

    TEST(test_ranges_filter_view_with_std_array)
    {
      auto even = [](int i) -> bool
      {
        return 0 == i % 2;
      };

      std::array<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      std::vector<int>    v_out;
      std::vector<int>    v_out_expected{0, 2, 4, 6, 8};

      auto fv = v_in | etl::views::filter(even);

      for (auto i : fv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected.size(), v_out.size());
      for (size_t idx = 0; idx < v_out_expected.size(); ++idx)
      {
        CHECK_EQUAL(v_out_expected[idx], v_out[idx]);
      }
    }

    TEST(test_ranges_filter_view_single_element)
    {
      auto even = [](int i) -> bool
      {
        return 0 == i % 2;
      };

      etl::vector<int, 5> v_in{42};
      etl::vector<int, 5> v_out;
      etl::vector<int, 5> v_out_expected{42};

      auto fv = v_in | etl::views::filter(even);

      for (auto i : fv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_filter_view_c_array)
    {
      auto even = [](int i) -> bool
      {
        return 0 == i % 2;
      };

      int                  v_in[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 2, 4, 6, 8};

      auto fv = etl::views::filter(v_in, even);

      for (auto i : fv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_filter_view_c_array_pipe)
    {
      auto even = [](int i) -> bool
      {
        return 0 == i % 2;
      };

      int                  v_in[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 2, 4, 6, 8};

      auto fv = v_in | etl::views::filter(even);

      for (auto i : fv)
      {
        v_out.push_back(i);
      }

      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<int> 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<element_ref, int&>::value, "filter element should be a non-const reference");
      static_assert(!etl::is_const_v<etl::remove_reference_t<element_ref>>, "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<int> v = {1, 2, 3, 4, 5, 6};

      for (auto& element : v | etl::views::filter([](int i) { return i % 2 == 0; }))
      {
        element *= 10;
      }

      std::vector<int> expected{1, 20, 3, 40, 5, 60};
      CHECK_EQUAL(expected, v);
    }

    TEST(test_ranges_join_functional)
    {
      using range_type = etl::vector<int, 3>;
      etl::vector<range_type, 4> v{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}};

      auto result = etl::views::join(v);

      using result_type = etl::vector<int, 30>;
      result_type v_expected{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};

      auto expected_view = etl::views::all(v_expected);
      CHECK_EQUAL(result, expected_view);
    }

    TEST(test_ranges_join_pipe)
    {
      using range_type = etl::vector<int, 3>;
      etl::vector<range_type, 4> v{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}};

      auto result = v | etl::views::join;

      using result_type = etl::vector<int, 30>;
      result_type v_expected{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};

      auto expected_view = etl::views::all(v_expected);
      CHECK_EQUAL(result, expected_view);
    }

    TEST(test_ranges_join_degenerated_1)
    {
      using range_type = etl::vector<int, 3>;
      etl::vector<range_type, 6> v{{1}, {1, 2, 3}, {1}, {7, 8, 9}, {1}};

      auto result = v | etl::views::join;

      using result_type = etl::vector<int, 30>;
      result_type v_expected{1, 1, 2, 3, 1, 7, 8, 9, 1};
      auto        expected_view = etl::views::all(v_expected);

      CHECK_EQUAL(9, result.size());
      CHECK_EQUAL(expected_view, result);
    }

    TEST(test_ranges_join_degenerated_2)
    {
      using range_type = etl::vector<int, 3>;
      etl::vector<range_type, 7> v{{}, {1, 2, 3}, {}, {7, 8, 9}, {}};

      auto rv = v | etl::views::join;

      using result_type = etl::vector<int, 30>;
      result_type v_expected{1, 2, 3, 7, 8, 9};
      auto        expected_view = etl::views::all(v_expected);

      CHECK_EQUAL(6, rv.size());
      CHECK_EQUAL(expected_view, rv);
    }

    TEST(test_ranges_join_iterate)
    {
      using range_type = etl::vector<int, 3>;
      etl::vector<range_type, 4> v{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}};

      using result_type = etl::vector<int, 30>;
      auto result       = v | etl::views::join | etl::ranges::to<result_type>();

      result_type result_v;
      for (auto i : result)
      {
        result_v.emplace_back(i);
      }
      result_type v_expected{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
      CHECK_EQUAL(result_v, v_expected);
    }

    TEST(test_ranges_join_view)
    {
      using range_type = etl::vector<int, 3>;
      etl::vector<range_type, 4> v{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}};

      using result_type = etl::vector<int, 30>;
      auto result       = v | etl::views::join | etl::ranges::to<result_type>();

      result_type v_expected{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
      CHECK_EQUAL(result, v_expected);
    }

    TEST(test_ranges_join_with_functional)
    {
      using range_type = etl::vector<int, 3>;
      etl::vector<range_type, 4> v{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}};
      range_type                 pattern{111, 222};

      auto result = etl::views::join_with(v, pattern);

      using result_type = etl::vector<int, 30>;
      result_type v_expected{1, 2, 3, 111, 222, 4, 5, 6, 111, 222, 7, 8, 9, 111, 222, 10, 11, 12};

      auto expected_view = etl::views::all(v_expected);
      CHECK_EQUAL(result, expected_view);
    }

    TEST(test_ranges_join_with_pipe)
    {
      using range_type = etl::vector<int, 3>;
      etl::vector<range_type, 4> v{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}};
      range_type                 pattern{111, 222};

      auto result = v | etl::views::join_with(pattern);

      using result_type = etl::vector<int, 30>;
      result_type v_expected{1, 2, 3, 111, 222, 4, 5, 6, 111, 222, 7, 8, 9, 111, 222, 10, 11, 12};

      auto expected_view = etl::views::all(v_expected);
      CHECK_EQUAL(result, expected_view);
    }

    TEST(test_ranges_join_with_degenerated_1)
    {
      using range_type = etl::vector<int, 3>;
      etl::vector<range_type, 6> v{{1}, {1, 2, 3}, {1}, {7, 8, 9}, {1}};
      range_type                 pattern{111, 222};

      auto result = v | etl::views::join_with(pattern);

      using result_type = etl::vector<int, 30>;
      result_type v_expected{1, 111, 222, 1, 2, 3, 111, 222, 1, 111, 222, 7, 8, 9, 111, 222, 1};
      auto        expected_view = etl::views::all(v_expected);

      CHECK_EQUAL(17, result.size());
      CHECK_EQUAL(expected_view, result);
    }

    TEST(test_ranges_join_with_degenerated_2)
    {
      using range_type = etl::vector<int, 3>;
      etl::vector<range_type, 7> v{{}, {1, 2, 3}, {}, {7, 8, 9}, {}};
      range_type                 pattern{111, 222};

      auto rv = v | etl::views::join_with(pattern);

      using result_type = etl::vector<int, 30>;
      result_type v_expected{111, 222, 1, 2, 3, 111, 222, 111, 222, 7, 8, 9, 111, 222};
      auto        expected_view = etl::views::all(v_expected);

      CHECK_EQUAL(14, rv.size());
      CHECK_EQUAL(expected_view, rv);
    }

    TEST(test_ranges_join_with_iterate)
    {
      using range_type = etl::vector<int, 3>;
      etl::vector<range_type, 4> v{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}};
      range_type                 pattern{111, 222};

      using result_type = etl::vector<int, 30>;
      auto result       = v | etl::views::join_with(pattern) | etl::ranges::to<result_type>();

      result_type result_v;
      for (auto i : result)
      {
        result_v.emplace_back(i);
      }
      result_type v_expected{1, 2, 3, 111, 222, 4, 5, 6, 111, 222, 7, 8, 9, 111, 222, 10, 11, 12};
      CHECK_EQUAL(result_v, v_expected);
    }

    TEST(test_ranges_join_with_view)
    {
      using range_type = etl::vector<int, 3>;
      etl::vector<range_type, 4> v{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}};
      range_type                 pattern{111, 222};

      using result_type = etl::vector<int, 30>;
      auto result       = v | etl::views::join_with(pattern) | etl::ranges::to<result_type>();

      result_type v_expected{1, 2, 3, 111, 222, 4, 5, 6, 111, 222, 7, 8, 9, 111, 222, 10, 11, 12};
      CHECK_EQUAL(result, v_expected);
    }

    TEST(test_ranges_join_with_functional_single)
    {
      using range_type = etl::vector<int, 3>;
      etl::vector<range_type, 4> v{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}};
      int                        pattern{111};

      auto result = etl::views::join_with(v, pattern);

      using result_type = etl::vector<int, 30>;
      result_type v_expected{1, 2, 3, 111, 4, 5, 6, 111, 7, 8, 9, 111, 10, 11, 12};

      auto expected_view = etl::views::all(v_expected);
      CHECK_EQUAL(result, expected_view);
    }

    TEST(test_ranges_join_with_functional_single_immediate)
    {
      using range_type = etl::vector<int, 3>;
      etl::vector<range_type, 4> v{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}};

      auto result = etl::views::join_with(v, 111);

      using result_type = etl::vector<int, 30>;
      result_type v_expected{1, 2, 3, 111, 4, 5, 6, 111, 7, 8, 9, 111, 10, 11, 12};

      auto expected_view = etl::views::all(v_expected);
      CHECK_EQUAL(result, expected_view);
    }

    TEST(test_ranges_join_with_pipe_single)
    {
      using range_type = etl::vector<int, 3>;
      etl::vector<range_type, 4> v{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}};
      int                        pattern{111};

      auto result = v | etl::views::join_with(pattern);

      using result_type = etl::vector<int, 30>;
      result_type v_expected{1, 2, 3, 111, 4, 5, 6, 111, 7, 8, 9, 111, 10, 11, 12};

      auto expected_view = etl::views::all(v_expected);
      CHECK_EQUAL(result, expected_view);
    }

    TEST(test_ranges_join_with_iterate_single)
    {
      using range_type = etl::vector<int, 3>;
      etl::vector<range_type, 4> v{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}};
      int                        pattern{111};

      using result_type = etl::vector<int, 30>;
      auto result       = v | etl::views::join_with(pattern) | etl::ranges::to<result_type>();

      result_type result_v;
      for (auto i : result)
      {
        result_v.emplace_back(i);
      }
      result_type v_expected{1, 2, 3, 111, 4, 5, 6, 111, 7, 8, 9, 111, 10, 11, 12};
      CHECK_EQUAL(result_v, v_expected);
    }

    TEST(test_ranges_join_with_view_single)
    {
      using range_type = etl::vector<int, 3>;
      etl::vector<range_type, 4> v{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}};
      int                        pattern{111};

      using result_type = etl::vector<int, 30>;
      auto result       = v | etl::views::join_with(pattern) | etl::ranges::to<result_type>();

      result_type v_expected{1, 2, 3, 111, 4, 5, 6, 111, 7, 8, 9, 111, 10, 11, 12};
      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<int, 3> samples = {10, 20, 30};
      etl::array<int, 8> 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<iterator_type&>());
      static_assert(etl::is_same<declared_reference, actual_deref>::value, "join_with_iterator::reference must match operator*");

      etl::copy(stereo.begin(), stereo.end(), dst.begin());

      etl::array<int, 8> 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<etl::vector<int, 3>, 2> v{{1, 2, 3}, {4, 5, 6}};
      etl::vector<int, 2>                 pattern{0};

      auto jv = etl::views::join_with(v, pattern);

      using element_ref = decltype(*jv.begin());
      static_assert(etl::is_same<element_ref, int&>::value, "join_with element should be a non-const reference for a mutable range pattern");
      static_assert(!etl::is_const_v<etl::remove_reference_t<element_ref>>, "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<etl::vector<int, 3>, 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<element_ref, const int&>::value, "single-value join_with pattern should be read as const");

      etl::vector<int, 10> out;
      for (auto x : jv)
      {
        out.push_back(x);
      }

      etl::vector<int, 10> 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<etl::vector<int, 3>, 2> v{{1, 2, 3}, {4, 5, 6}};
      etl::vector<char, 1>                pattern{char(0)};

      auto jv = etl::views::join_with(v, pattern);

      using element_type = etl::remove_cvref_t<decltype(*jv.begin())>;
      static_assert(etl::is_same<int, element_type>::value, "join_with element type should be the common type of inner and pattern");

      etl::vector<int, 10> out;
      for (auto x : jv)
      {
        out.push_back(x);
      }

      etl::vector<int, 10> expected{1, 2, 3, 0, 4, 5, 6};
      CHECK_EQUAL(expected, out);
    }

    //*************************************************************************
    // split_view and views::split tests
    //*************************************************************************
    TEST(test_ranges_split_view_int_vector)
    {
      etl::vector<int, 10>          v_in{1, 2, 0, 3, 4, 0, 5};
      auto                          sv = etl::ranges::split_view(v_in, 0);
      std::vector<std::vector<int>> expected{{1, 2}, {3, 4}, {5}};
      size_t                        idx = 0;
      for (auto sub : sv)
      {
        std::vector<int> actual(sub.begin(), sub.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(idx, expected.size());
    }

    TEST(test_ranges_split_view_functional_single_delim)
    {
      // Split {1, 2, 0, 3, 4, 0, 5, 6} by 0 => {1,2}, {3,4}, {5,6}
      std::vector<int>              v_in{1, 2, 0, 3, 4, 0, 5, 6};
      auto                          sv = etl::ranges::split_view(v_in, 0);
      std::vector<std::vector<int>> expected{{1, 2}, {3, 4}, {5, 6}};
      size_t                        idx = 0;
      for (auto sub : sv)
      {
        std::vector<int> actual(sub.begin(), sub.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(idx, expected.size());
    }

    TEST(test_ranges_split_view_multi_pattern)
    {
      // Split {1, 2, 0, 0, 3, 4, 0, 0, 5} by pattern {0, 0} => {1,2}, {3,4},
      // {5}
      std::vector<int>              v_in{1, 2, 0, 0, 3, 4, 0, 0, 5};
      std::vector<int>              delim{0, 0};
      auto                          sv = etl::ranges::split_view(v_in, delim);
      std::vector<std::vector<int>> expected{{1, 2}, {3, 4}, {5}};
      size_t                        idx = 0;
      for (auto sub : sv)
      {
        std::vector<int> actual(sub.begin(), sub.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(idx, expected.size());
    }

    TEST(test_ranges_split_view_pipe)
    {
      // Pipe syntax: {1, 0, 2, 0, 3} | split(0) => {1}, {2}, {3}
      std::vector<int>              v_in{1, 0, 2, 0, 3};
      auto                          sv = v_in | etl::views::split(0);
      std::vector<std::vector<int>> expected{{1}, {2}, {3}};
      size_t                        idx = 0;
      for (auto sub : sv)
      {
        std::vector<int> actual(sub.begin(), sub.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(idx, expected.size());
    }

    TEST(test_ranges_split_view_empty_input)
    {
      // Split empty range => one empty subrange
      std::vector<int> v_in{};
      auto             sv    = etl::ranges::split_view(v_in, 0);
      size_t           count = 0;
      for (auto sub : sv)
      {
        std::vector<int> actual(sub.begin(), sub.end());
        CHECK_EQUAL(actual.size(), 0U);
        ++count;
      }
      CHECK_EQUAL(count, 1U);
    }

    TEST(test_ranges_split_view_no_delim)
    {
      // No delimiter found => one subrange with all elements
      std::vector<int> v_in{1, 2, 3};
      auto             sv    = etl::ranges::split_view(v_in, 0);
      size_t           count = 0;
      for (auto sub : sv)
      {
        std::vector<int> actual(sub.begin(), sub.end());
        std::vector<int> expected_sub{1, 2, 3};
        CHECK_EQUAL(expected_sub, actual);
        ++count;
      }
      CHECK_EQUAL(count, 1U);
    }

    TEST(test_ranges_split_view_delim_at_edges)
    {
      // Delimiter at start and end: {0, 1, 2, 0} => {}, {1,2}, {}
      std::vector<int>              v_in{0, 1, 2, 0};
      auto                          sv = etl::ranges::split_view(v_in, 0);
      std::vector<std::vector<int>> expected{{}, {1, 2}, {}};
      size_t                        idx = 0;
      for (auto sub : sv)
      {
        std::vector<int> actual(sub.begin(), sub.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(idx, expected.size());
    }

    TEST(test_ranges_split_view_consecutive_delim)
    {
      // Consecutive delimiters: {1, 0, 0, 2} => {1}, {}, {2}
      std::vector<int>              v_in{1, 0, 0, 2};
      auto                          sv = etl::ranges::split_view(v_in, 0);
      std::vector<std::vector<int>> expected{{1}, {}, {2}};
      size_t                        idx = 0;
      for (auto sub : sv)
      {
        std::vector<int> actual(sub.begin(), sub.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(idx, expected.size());
    }

    TEST(test_ranges_split_view_pipe_multi_pattern)
    {
      // Pipe with multi-element pattern: {1, 2, 9, 9, 3, 4, 9, 9, 5} |
      // split({9,9}) => {1,2}, {3,4}, {5}
      std::vector<int>              v_in{1, 2, 9, 9, 3, 4, 9, 9, 5};
      std::vector<int>              delim{9, 9};
      auto                          sv = v_in | etl::views::split(delim);
      std::vector<std::vector<int>> expected{{1, 2}, {3, 4}, {5}};
      size_t                        idx = 0;
      for (auto sub : sv)
      {
        std::vector<int> actual(sub.begin(), sub.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(idx, expected.size());
    }

    TEST(test_ranges_split_view_etl_vector)
    {
      // Works with etl::vector too
      etl::vector<int, 10>          v_in{1, 0, 2, 0, 3};
      auto                          sv = etl::ranges::split_view(v_in, 0);
      std::vector<std::vector<int>> expected{{1}, {2}, {3}};
      size_t                        idx = 0;
      for (auto sub : sv)
      {
        std::vector<int> actual(sub.begin(), sub.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      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<int> 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<element_ref, int&>::value, "split element should be a non-const reference");
      static_assert(!etl::is_const_v<etl::remove_reference_t<element_ref>>, "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<int> 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<int> expected{-1, -2, 0, -3, 0, -4, -5};
      CHECK_EQUAL(expected, v_in);
    }

    //*************************************************************************
    // lazy_split_view and views::lazy_split tests
    //*************************************************************************
    TEST(test_ranges_lazy_split_view_int_vector)
    {
      etl::vector<int, 10>          v_in{1, 2, 0, 3, 4, 0, 5};
      auto                          sv = etl::ranges::lazy_split_view(v_in, 0);
      std::vector<std::vector<int>> expected{{1, 2}, {3, 4}, {5}};
      size_t                        idx = 0;
      for (auto sub : sv)
      {
        std::vector<int> actual(sub.begin(), sub.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(idx, expected.size());
    }

    TEST(test_ranges_lazy_split_view_functional_single_delim)
    {
      // Split {1, 2, 0, 3, 4, 0, 5, 6} by 0 => {1,2}, {3,4}, {5,6}
      std::vector<int>              v_in{1, 2, 0, 3, 4, 0, 5, 6};
      auto                          sv = etl::ranges::lazy_split_view(v_in, 0);
      std::vector<std::vector<int>> expected{{1, 2}, {3, 4}, {5, 6}};
      size_t                        idx = 0;
      for (auto sub : sv)
      {
        std::vector<int> actual(sub.begin(), sub.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(idx, expected.size());
    }

    TEST(test_ranges_lazy_split_view_multi_pattern)
    {
      // Split {1, 2, 0, 0, 3, 4, 0, 0, 5} by pattern {0, 0} => {1,2}, {3,4},
      // {5}
      std::vector<int>              v_in{1, 2, 0, 0, 3, 4, 0, 0, 5};
      std::vector<int>              delim{0, 0};
      auto                          sv = etl::ranges::lazy_split_view(v_in, delim);
      std::vector<std::vector<int>> expected{{1, 2}, {3, 4}, {5}};
      size_t                        idx = 0;
      for (auto sub : sv)
      {
        std::vector<int> actual(sub.begin(), sub.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(idx, expected.size());
    }

    TEST(test_ranges_lazy_split_view_pipe)
    {
      // Pipe syntax: {1, 0, 2, 0, 3} | lazy_split(0) => {1}, {2}, {3}
      std::vector<int>              v_in{1, 0, 2, 0, 3};
      auto                          sv = v_in | etl::views::lazy_split(0);
      std::vector<std::vector<int>> expected{{1}, {2}, {3}};
      size_t                        idx = 0;
      for (auto sub : sv)
      {
        std::vector<int> actual(sub.begin(), sub.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(idx, expected.size());
    }

    TEST(test_ranges_lazy_split_view_empty_input)
    {
      // Split empty range => one empty subrange
      std::vector<int> v_in{};
      auto             sv    = etl::ranges::lazy_split_view(v_in, 0);
      size_t           count = 0;
      for (auto sub : sv)
      {
        std::vector<int> actual(sub.begin(), sub.end());
        CHECK_EQUAL(actual.size(), 0U);
        ++count;
      }
      CHECK_EQUAL(count, 1U);
    }

    TEST(test_ranges_lazy_split_view_no_delim)
    {
      // No delimiter found => one subrange with all elements
      std::vector<int> v_in{1, 2, 3};
      auto             sv    = etl::ranges::lazy_split_view(v_in, 0);
      size_t           count = 0;
      for (auto sub : sv)
      {
        std::vector<int> actual(sub.begin(), sub.end());
        std::vector<int> expected_sub{1, 2, 3};
        CHECK_EQUAL(expected_sub, actual);
        ++count;
      }
      CHECK_EQUAL(count, 1U);
    }

    TEST(test_ranges_lazy_split_view_delim_at_edges)
    {
      // Delimiter at start and end: {0, 1, 2, 0} => {}, {1,2}, {}
      std::vector<int>              v_in{0, 1, 2, 0};
      auto                          sv = etl::ranges::lazy_split_view(v_in, 0);
      std::vector<std::vector<int>> expected{{}, {1, 2}, {}};
      size_t                        idx = 0;
      for (auto sub : sv)
      {
        std::vector<int> actual(sub.begin(), sub.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(idx, expected.size());
    }

    TEST(test_ranges_lazy_split_view_consecutive_delim)
    {
      // Consecutive delimiters: {1, 0, 0, 2} => {1}, {}, {2}
      std::vector<int>              v_in{1, 0, 0, 2};
      auto                          sv = etl::ranges::lazy_split_view(v_in, 0);
      std::vector<std::vector<int>> expected{{1}, {}, {2}};
      size_t                        idx = 0;
      for (auto sub : sv)
      {
        std::vector<int> actual(sub.begin(), sub.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(idx, expected.size());
    }

    TEST(test_ranges_lazy_split_view_pipe_multi_pattern)
    {
      // Pipe with multi-element pattern: {1, 2, 9, 9, 3, 4, 9, 9, 5} |
      // lazy_split({9,9}) => {1,2}, {3,4}, {5}
      std::vector<int>              v_in{1, 2, 9, 9, 3, 4, 9, 9, 5};
      std::vector<int>              delim{9, 9};
      auto                          sv = v_in | etl::views::lazy_split(delim);
      std::vector<std::vector<int>> expected{{1, 2}, {3, 4}, {5}};
      size_t                        idx = 0;
      for (auto sub : sv)
      {
        std::vector<int> actual(sub.begin(), sub.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(idx, expected.size());
    }

    TEST(test_ranges_lazy_split_view_etl_vector)
    {
      // Works with etl::vector too
      etl::vector<int, 10>          v_in{1, 0, 2, 0, 3};
      auto                          sv = etl::ranges::lazy_split_view(v_in, 0);
      std::vector<std::vector<int>> expected{{1}, {2}, {3}};
      size_t                        idx = 0;
      for (auto sub : sv)
      {
        std::vector<int> actual(sub.begin(), sub.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(idx, expected.size());
    }

    TEST(test_ranges_lazy_split_view_inner_range_lazy_iteration)
    {
      // Verify that the inner range can be iterated element-by-element (lazily)
      std::vector<int> v_in{10, 20, 0, 30, 40, 50};
      auto             sv       = etl::ranges::lazy_split_view(v_in, 0);
      auto             outer_it = sv.begin();

      // First segment: {10, 20}
      auto inner1   = *outer_it;
      auto inner_it = inner1.begin();
      CHECK_EQUAL(*inner_it, 10);
      ++inner_it;
      CHECK_EQUAL(*inner_it, 20);
      ++inner_it;
      CHECK(inner_it == inner1.end());

      // Second segment: {30, 40, 50}
      ++outer_it;
      auto inner2    = *outer_it;
      auto inner_it2 = inner2.begin();
      CHECK_EQUAL(*inner_it2, 30);
      ++inner_it2;
      CHECK_EQUAL(*inner_it2, 40);
      ++inner_it2;
      CHECK_EQUAL(*inner_it2, 50);
      ++inner_it2;
      CHECK(inner_it2 == inner2.end());

      ++outer_it;
      CHECK(outer_it == sv.end());
    }

    TEST(test_ranges_lazy_split_view_inner_range_empty_check)
    {
      // Inner range empty() method
      std::vector<int> v_in{0, 1, 0};
      auto             sv = etl::ranges::lazy_split_view(v_in, 0);
      auto             it = sv.begin();

      // First segment is empty
      auto seg0 = *it;
      CHECK(seg0.empty());

      ++it;
      auto seg1 = *it;
      CHECK(!seg1.empty());

      ++it;
      auto seg2 = *it;
      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<int> 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<element_ref, int&>::value, "lazy_split element should be a non-const reference");
      static_assert(!etl::is_const_v<etl::remove_reference_t<element_ref>>, "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<int> 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<int> expected{2, 4, 0, 6, 0, 8};
      CHECK_EQUAL(expected, v_in);
    }

    TEST(test_counted)
    {
      {
        std::vector<int> vec{1, 2, 3, 4, 5};

        auto result = etl::views::counted(vec.begin(), 3);

        CHECK_EQUAL(result.size(), 3);
        CHECK_EQUAL(result[0], 1);
        CHECK_EQUAL(result[1], 2);
        CHECK_EQUAL(result[2], 3);
      }
      {
        std::list<int> list{1, 2, 3, 4, 5};

        auto result = etl::views::counted(list.begin(), 3);

        CHECK_EQUAL(result.size(), 3);
        CHECK_EQUAL(result[0], 1);
        CHECK_EQUAL(result[1], 2);
        CHECK_EQUAL(result[2], 3);
      }
    }

    TEST(test_ranges_concat_view_1)
    {
      std::vector<int> vec{1, 2, 3, 4};

      using result_type  = std::vector<int>;
      auto        cv     = etl::views::concat(vec);
      result_type result = etl::views::concat(vec) | etl::ranges::to<result_type>();

      result_type expected_result{1, 2, 3, 4};

      CHECK_EQUAL(cv.size(), 4);
      CHECK_EQUAL(result.size(), 4);

      CHECK_EQUAL(result, expected_result);
    }

    TEST(test_ranges_concat_view_2)
    {
      std::vector<int> vec{1, 2, 3, 4};
      std::list<int>   list{6, 7, 8, 9, 10};

      auto cv            = etl::views::concat(vec, list);
      using result_type  = std::vector<int>;
      result_type result = etl::views::concat(vec, list) | etl::ranges::to<result_type>();

      result_type expected_result{1, 2, 3, 4, 6, 7, 8, 9, 10};

      CHECK_EQUAL(cv.size(), 9);
      CHECK_EQUAL(result.size(), 9);

      CHECK_EQUAL(result, expected_result);
    }

    TEST(test_ranges_concat_view_2_same_type)
    {
      std::vector<int> vec0{1, 2, 3, 4};
      std::vector<int> vec1{6, 7, 8, 9, 10};

      auto cv = etl::views::concat(vec0, vec1);

      using result_type  = std::vector<int>;
      result_type result = etl::views::concat(vec0, vec1) | etl::ranges::to<result_type>();

      std::vector<int> expected_result{1, 2, 3, 4, 6, 7, 8, 9, 10};

      CHECK_EQUAL(cv.size(), 9);
      CHECK_EQUAL(result.size(), 9);

      CHECK_EQUAL(result, expected_result);
    }

    TEST(test_ranges_concat_view_3)
    {
      std::vector<int>   vec{1, 2, 3, 4};
      std::list<int>     list{6, 7, 8, 9, 10};
      std::array<int, 3> arr{20, 21, 22};

      auto cv = etl::views::concat(vec, list, arr);

      using result_type  = std::vector<int>;
      result_type result = etl::views::concat(vec, list, arr) | etl::ranges::to<result_type>();

      std::vector<int> expected_result{1, 2, 3, 4, 6, 7, 8, 9, 10, 20, 21, 22};

      CHECK_EQUAL(cv.size(), 12);
      CHECK_EQUAL(result.size(), 12);

      CHECK_EQUAL(result, expected_result);

      auto it = cv.begin();
      CHECK(it != cv.end());
      CHECK_EQUAL(*it, 1);

      ++it;
      CHECK_EQUAL(*it, 2);
      it++;
      CHECK_EQUAL(*it, 3);
      CHECK_EQUAL(it[0], 3);
      CHECK_EQUAL(it[1], 4);
      CHECK_EQUAL(it[2], 6);
      it += 3;
      CHECK_EQUAL(*it, 7);
      it -= 2;
      CHECK_EQUAL(*it, 4);
      --it;
      CHECK_EQUAL(*it, 3);
      it--;
      CHECK_EQUAL(*it, 2);
      CHECK(it != cv.end());
      it += 8;
      CHECK_EQUAL(*it, 20);
      it += 2;
      CHECK_EQUAL(*it, 22);
      CHECK(it != cv.end());
      ++it;
      CHECK(it == cv.end());
    }

    TEST(test_ranges_concat_view_3_same_type)
    {
      std::vector<int> vec0{1, 2, 3, 4};
      std::vector<int> vec1{6, 7, 8, 9, 10};
      std::vector<int> vec2{20, 21, 22};

      auto cv = etl::views::concat(vec0, vec1, vec2);

      using result_type  = std::vector<int>;
      result_type result = etl::views::concat(vec0, vec1, vec2) | etl::ranges::to<result_type>();

      std::vector<int> expected_result{1, 2, 3, 4, 6, 7, 8, 9, 10, 20, 21, 22};

      CHECK_EQUAL(cv.size(), 12);
      CHECK_EQUAL(result.size(), 12);

      CHECK_EQUAL(result, expected_result);

      auto it = cv.begin();
      CHECK(it != cv.end());
      CHECK_EQUAL(*it, 1);

      ++it;
      CHECK_EQUAL(*it, 2);
      it++;
      CHECK_EQUAL(*it, 3);
      CHECK_EQUAL(it[0], 3);
      CHECK_EQUAL(it[1], 4);
      CHECK_EQUAL(it[2], 6);
      it += 3;
      CHECK_EQUAL(*it, 7);
      it -= 2;
      CHECK_EQUAL(*it, 4);
      --it;
      CHECK_EQUAL(*it, 3);
      it--;
      CHECK_EQUAL(*it, 2);
      CHECK(it != cv.end());
      it += 8;
      CHECK_EQUAL(*it, 20);
      it += 2;
      CHECK_EQUAL(*it, 22);
      CHECK(it != cv.end());
      ++it;
      CHECK(it == cv.end());
    }

    TEST(test_ranges_concat_view_3_same_range)
    {
      std::vector<int> vec0{1, 2, 3, 4, 5};

      auto cv = etl::views::concat(vec0, vec0, vec0);

      using result_type  = std::vector<int>;
      result_type result = etl::views::concat(vec0, vec0, vec0) | etl::ranges::to<result_type>();

      std::vector<int> expected_result{1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5};

      CHECK_EQUAL(cv.size(), 15);
      CHECK_EQUAL(result.size(), 15);

      CHECK_EQUAL(result, expected_result);

      auto it = cv.begin();
      CHECK(it != cv.end());
      CHECK_EQUAL(*it, 1);

      ++it;
      CHECK_EQUAL(*it, 2);
      it++;
      CHECK_EQUAL(*it, 3);
      CHECK_EQUAL(it[0], 3);
      CHECK_EQUAL(it[1], 4);
      CHECK_EQUAL(it[2], 5);
      it += 3;
      CHECK_EQUAL(*it, 1);
      it -= 2;
      CHECK_EQUAL(*it, 4);
      --it;
      CHECK_EQUAL(*it, 3);
      it--;
      CHECK_EQUAL(*it, 2);
      CHECK(it != cv.end());
      it += 11;
      CHECK_EQUAL(*it, 3);
      it += 2;
      CHECK_EQUAL(*it, 5);
      CHECK(it != cv.end());
      ++it;
      CHECK(it == cv.end());
    }

    //*************************************************************************
    // common_view tests
    //*************************************************************************

    TEST(test_ranges_common_view_functional)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 1, 2, 3, 4};

      auto cv = etl::ranges::common_view(v_in);
      for (int i : cv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
      CHECK_EQUAL(*cv.begin(), 0);
      CHECK_EQUAL(cv.size(), 5);

      CHECK_EQUAL(*cv.cbegin(), 0);
      CHECK_EQUAL(cv.empty(), false);
      CHECK_EQUAL(cv, true);
      CHECK_EQUAL(cv.front(), 0);
      CHECK_EQUAL(cv.back(), 4);
      CHECK_EQUAL(cv[2], 2);
    }

    TEST(test_ranges_common_view_from_view)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{3, 4, 5, 6, 7, 8, 9};

      auto cv = etl::ranges::common_view(etl::views::drop(v_in, 3));
      for (int i : cv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
      CHECK_EQUAL(*cv.begin(), 3);
      CHECK_EQUAL(cv.size(), 7);
      CHECK_EQUAL(cv.empty(), false);
      CHECK_EQUAL(cv.front(), 3);
      CHECK_EQUAL(cv.back(), 9);
    }

    TEST(test_ranges_common_view_pipe)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 1, 2, 3, 4};

      auto cv = v_in | etl::views::common;
      for (int i : cv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
      CHECK_EQUAL(*cv.begin(), 0);
      CHECK_EQUAL(cv.size(), 5);
      CHECK_EQUAL(cv.empty(), false);
      CHECK_EQUAL(cv.front(), 0);
      CHECK_EQUAL(cv.back(), 4);
    }

    TEST(test_ranges_common_view_pipe_chain)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{4, 3, 2, 1, 0};

      auto cv = v_in | etl::views::take(5) | etl::views::reverse | etl::views::common;
      for (int i : cv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
      CHECK_EQUAL(cv.size(), 5);
      CHECK_EQUAL(cv.front(), 4);
      CHECK_EQUAL(cv.back(), 0);
    }

    TEST(test_ranges_common_view_empty)
    {
      etl::vector<int, 10> v_in{};

      auto cv = etl::ranges::common_view(v_in);

      CHECK_EQUAL(cv.size(), 0);
      CHECK_EQUAL(cv.empty(), true);
      CHECK_EQUAL(cv.begin(), cv.end());
    }

    TEST(test_ranges_common_view_same_begin_end_type)
    {
      // For a common range (begin/end same type), common_view should work as
      // pass-through
      etl::vector<int, 10> v_in{10, 20, 30};

      auto cv       = etl::views::common(v_in);
      auto it_begin = cv.begin();
      auto it_end   = cv.end();

      // Verify same type via compilation and correct values
      CHECK(it_begin != it_end);
      CHECK_EQUAL(*it_begin, 10);
      CHECK_EQUAL(cv.size(), 3);
      CHECK_EQUAL(cv.front(), 10);
      CHECK_EQUAL(cv.back(), 30);
    }

    TEST(test_ranges_common_view_iterate)
    {
      etl::vector<int, 10> v_in{1, 2, 3, 4, 5};

      using result_type  = etl::vector<int, 10>;
      result_type result = v_in | etl::views::common | etl::ranges::to<result_type>();

      result_type v_expected{1, 2, 3, 4, 5};
      CHECK_EQUAL(result, v_expected);
    }

    TEST(test_ranges_common_view_with_filter)
    {
      auto even = [](int i) -> bool
      {
        return 0 == i % 2;
      };

      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 2, 4, 6, 8};

      auto cv = v_in | etl::views::filter(even) | etl::views::common();
      for (int i : cv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_common_view_with_transform)
    {
      auto times_two = [](int i) -> int
      {
        return i * 2;
      };

      etl::vector<int, 10> v_in{1, 2, 3, 4, 5};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{2, 4, 6, 8, 10};

      auto cv = v_in | etl::views::transform(times_two) | etl::views::common;
      for (int i : cv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    TEST(test_ranges_common_view_std_vector)
    {
      std::vector<int> v_in{10, 20, 30, 40, 50};
      std::vector<int> v_out;
      std::vector<int> v_out_expected{10, 20, 30, 40, 50};

      auto cv = etl::ranges::common_view(etl::ranges::ref_view(v_in));
      for (int i : cv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
      CHECK_EQUAL(cv.size(), 5);
      CHECK_EQUAL(cv.front(), 10);
      CHECK_EQUAL(cv.back(), 50);
    }

    TEST(test_ranges_common_view_c_array)
    {
      int                  v_in[] = {1, 2, 3, 4, 5};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{1, 2, 3, 4, 5};

      auto cv = etl::ranges::common_view(etl::ranges::ref_view(v_in));
      for (int i : cv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
      CHECK_EQUAL(cv.size(), 5);
    }

    //*************************************************************************
    TEST(test_ranges_elements_of_from_lvalue)
    {
      std::vector<int> v{1, 2, 3, 4, 5};

      auto eo = etl::ranges::elements_of{v};

      // The wrapped range should refer to the same data
      CHECK_EQUAL(v.size(), eo.range.size());
      CHECK_EQUAL(v[0], eo.range[0]);
      CHECK_EQUAL(v[4], eo.range[4]);
    }

    //*************************************************************************
    TEST(test_ranges_elements_of_from_rvalue)
    {
      auto eo = etl::ranges::elements_of{std::vector<int>{10, 20, 30}};

      CHECK_EQUAL(3U, eo.range.size());
      CHECK_EQUAL(10, eo.range[0]);
      CHECK_EQUAL(20, eo.range[1]);
      CHECK_EQUAL(30, eo.range[2]);
    }

    //*************************************************************************
    TEST(test_ranges_elements_of_deduction_guide)
    {
      std::vector<int> v{1, 2, 3};

      // CTAD should deduce elements_of<std::vector<int>&>
      auto eo = etl::ranges::elements_of{v};
      (void)eo;

      // CTAD from rvalue should deduce elements_of<std::vector<int>&&>
      auto eo2 = etl::ranges::elements_of{std::vector<int>{4, 5, 6}};
      (void)eo2;
    }

    //*************************************************************************
    TEST(test_ranges_elements_of_c_array)
    {
      int arr[] = {10, 20, 30, 40, 50};

      auto eo = etl::ranges::elements_of{arr};

      CHECK_EQUAL(10, eo.range[0]);
      CHECK_EQUAL(50, eo.range[4]);
    }

    //*************************************************************************
    TEST(test_ranges_elements_view_pair_first)
    {
      std::vector<std::pair<int, double>> v = {{1, 1.1}, {2, 2.2}, {3, 3.3}};

      auto ev = etl::ranges::elements_view<etl::ranges::views::all_t<decltype(v)&>, 0>(etl::ranges::views::all(v));

      auto it = ev.begin();
      CHECK_EQUAL(1, *it);
      ++it;
      CHECK_EQUAL(2, *it);
      ++it;
      CHECK_EQUAL(3, *it);
      ++it;
      CHECK(it == ev.end());
    }

    //*************************************************************************
    TEST(test_ranges_elements_view_pair_second)
    {
      std::vector<std::pair<int, double>> v = {{1, 1.1}, {2, 2.2}, {3, 3.3}};

      auto ev = etl::ranges::elements_view<etl::ranges::views::all_t<decltype(v)&>, 1>(etl::ranges::views::all(v));

      auto it = ev.begin();
      CHECK_CLOSE(1.1, *it, 0.001);
      ++it;
      CHECK_CLOSE(2.2, *it, 0.001);
      ++it;
      CHECK_CLOSE(3.3, *it, 0.001);
      ++it;
      CHECK(it == ev.end());
    }

    //*************************************************************************
    TEST(test_ranges_elements_view_tuple)
    {
      std::vector<etl::tuple<int, double, char>> v = {etl::make_tuple(10, 1.5, 'a'), etl::make_tuple(20, 2.5, 'b'), etl::make_tuple(30, 3.5, 'c')};

      // Extract element 2 (char)
      auto ev = etl::ranges::elements_view<etl::ranges::views::all_t<decltype(v)&>, 2>(etl::ranges::views::all(v));

      auto it = ev.begin();
      CHECK_EQUAL('a', *it);
      ++it;
      CHECK_EQUAL('b', *it);
      ++it;
      CHECK_EQUAL('c', *it);
      ++it;
      CHECK(it == ev.end());
    }

    //*************************************************************************
    TEST(test_ranges_elements_view_size)
    {
      std::vector<std::pair<int, int>> v = {{1, 10}, {2, 20}, {3, 30}, {4, 40}};

      auto ev = etl::ranges::elements_view<etl::ranges::views::all_t<decltype(v)&>, 0>(etl::ranges::views::all(v));

      CHECK_EQUAL(4U, ev.size());
    }

    //*************************************************************************
    TEST(test_ranges_elements_view_empty)
    {
      std::vector<std::pair<int, int>> v;

      auto ev = etl::ranges::elements_view<etl::ranges::views::all_t<decltype(v)&>, 0>(etl::ranges::views::all(v));

      CHECK(ev.empty());
      CHECK(ev.begin() == ev.end());
    }

    //*************************************************************************
    TEST(test_ranges_views_elements_adaptor)
    {
      std::vector<std::pair<int, double>> v = {{1, 1.1}, {2, 2.2}, {3, 3.3}};

      auto ev = etl::views::elements<0>(v);

      auto it = ev.begin();
      CHECK_EQUAL(1, *it);
      ++it;
      CHECK_EQUAL(2, *it);
      ++it;
      CHECK_EQUAL(3, *it);
      ++it;
      CHECK(it == ev.end());
    }

    //*************************************************************************
    TEST(test_ranges_views_elements_pipe)
    {
      std::vector<std::pair<int, double>> v = {{1, 1.1}, {2, 2.2}, {3, 3.3}};

      auto ev = v | etl::views::elements<1>;

      auto it = ev.begin();
      CHECK_CLOSE(1.1, *it, 0.001);
      ++it;
      CHECK_CLOSE(2.2, *it, 0.001);
      ++it;
      CHECK_CLOSE(3.3, *it, 0.001);
      ++it;
      CHECK(it == ev.end());
    }

    //*************************************************************************
    TEST(test_ranges_keys_view_alias)
    {
      std::vector<std::pair<int, double>> v = {{10, 1.1}, {20, 2.2}, {30, 3.3}};

      using range_t = etl::ranges::views::all_t<decltype(v)&>;
      etl::ranges::keys_view<range_t> kv(etl::ranges::views::all(v));

      auto it = kv.begin();
      CHECK_EQUAL(10, *it);
      ++it;
      CHECK_EQUAL(20, *it);
      ++it;
      CHECK_EQUAL(30, *it);
      ++it;
      CHECK(it == kv.end());
    }

    //*************************************************************************
    TEST(test_ranges_values_view_alias)
    {
      std::vector<std::pair<int, double>> v = {{10, 1.1}, {20, 2.2}, {30, 3.3}};

      using range_t = etl::ranges::views::all_t<decltype(v)&>;
      etl::ranges::values_view<range_t> vv(etl::ranges::views::all(v));

      auto it = vv.begin();
      CHECK_CLOSE(1.1, *it, 0.001);
      ++it;
      CHECK_CLOSE(2.2, *it, 0.001);
      ++it;
      CHECK_CLOSE(3.3, *it, 0.001);
      ++it;
      CHECK(it == vv.end());
    }

    //*************************************************************************
    TEST(test_ranges_views_keys)
    {
      std::vector<std::pair<int, double>> v = {{10, 1.1}, {20, 2.2}, {30, 3.3}};

      auto ev = v | etl::views::keys;

      auto it = ev.begin();
      CHECK_EQUAL(10, *it);
      ++it;
      CHECK_EQUAL(20, *it);
      ++it;
      CHECK_EQUAL(30, *it);
      ++it;
      CHECK(it == ev.end());
    }

    //*************************************************************************
    TEST(test_ranges_views_values)
    {
      std::vector<std::pair<int, double>> v = {{10, 1.1}, {20, 2.2}, {30, 3.3}};

      auto ev = v | etl::views::values;

      auto it = ev.begin();
      CHECK_CLOSE(1.1, *it, 0.001);
      ++it;
      CHECK_CLOSE(2.2, *it, 0.001);
      ++it;
      CHECK_CLOSE(3.3, *it, 0.001);
      ++it;
      CHECK(it == ev.end());
    }

    //*************************************************************************
    TEST(test_ranges_views_keys_direct_call)
    {
      std::vector<std::pair<int, double>> v = {{10, 1.1}, {20, 2.2}, {30, 3.3}};

      auto ev = etl::views::keys(v);

      auto it = ev.begin();
      CHECK_EQUAL(10, *it);
      ++it;
      CHECK_EQUAL(20, *it);
      ++it;
      CHECK_EQUAL(30, *it);
      ++it;
      CHECK(it == ev.end());
    }

    //*************************************************************************
    TEST(test_ranges_views_values_direct_call)
    {
      std::vector<std::pair<int, double>> v = {{10, 1.1}, {20, 2.2}, {30, 3.3}};

      auto ev = etl::views::values(v);

      auto it = ev.begin();
      CHECK_CLOSE(1.1, *it, 0.001);
      ++it;
      CHECK_CLOSE(2.2, *it, 0.001);
      ++it;
      CHECK_CLOSE(3.3, *it, 0.001);
      ++it;
      CHECK(it == ev.end());
    }

    //*************************************************************************
    TEST(test_ranges_elements_view_with_etl_tuple)
    {
      std::vector<etl::tuple<int, double, char>> v = {etl::make_tuple(1, 10.0, 'x'), etl::make_tuple(2, 20.0, 'y'), etl::make_tuple(3, 30.0, 'z')};

      // Extract element 0 via pipe
      auto ev0 = v | etl::views::elements<0>;
      auto it0 = ev0.begin();
      CHECK_EQUAL(1, *it0);
      ++it0;
      CHECK_EQUAL(2, *it0);
      ++it0;
      CHECK_EQUAL(3, *it0);
      ++it0;
      CHECK(it0 == ev0.end());

      // Extract element 1 via pipe
      auto ev1 = v | etl::views::elements<1>;
      auto it1 = ev1.begin();
      CHECK_CLOSE(10.0, *it1, 0.001);
      ++it1;
      CHECK_CLOSE(20.0, *it1, 0.001);
      ++it1;
      CHECK_CLOSE(30.0, *it1, 0.001);
      ++it1;
      CHECK(it1 == ev1.end());

      // Extract element 2 via pipe
      auto ev2 = v | etl::views::elements<2>;
      auto it2 = ev2.begin();
      CHECK_EQUAL('x', *it2);
      ++it2;
      CHECK_EQUAL('y', *it2);
      ++it2;
      CHECK_EQUAL('z', *it2);
      ++it2;
      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<std::pair<int, double>> v  = {{1, 1.1}, {2, 2.2}};
      auto                                ev = v | etl::views::keys;

      using element_ref = decltype(*ev.begin());

      static_assert(etl::is_same<element_ref, int&>::value, "elements element should be a non-const reference");
      static_assert(!etl::is_const_v<etl::remove_reference_t<element_ref>>, "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<std::pair<int, int>> 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)
    {
      std::vector<int> v  = {10, 20, 30};
      auto             ev = etl::ranges::enumerate_view(etl::ranges::views::all(v));

      auto it = ev.begin();
      CHECK_EQUAL(0U, etl::get<0>(*it));
      CHECK_EQUAL(10, etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(1U, etl::get<0>(*it));
      CHECK_EQUAL(20, etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(2U, etl::get<0>(*it));
      CHECK_EQUAL(30, etl::get<1>(*it));
      ++it;
      CHECK(it == ev.end());
    }

    //*************************************************************************
    TEST(test_ranges_enumerate_view_pipe)
    {
      std::vector<std::string> v  = {"a", "b", "c"};
      auto                     ev = v | etl::views::enumerate;

      auto it = ev.begin();
      CHECK_EQUAL(0U, etl::get<0>(*it));
      CHECK_EQUAL(std::string("a"), etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(1U, etl::get<0>(*it));
      CHECK_EQUAL(std::string("b"), etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(2U, etl::get<0>(*it));
      CHECK_EQUAL(std::string("c"), etl::get<1>(*it));
      ++it;
      CHECK(it == ev.end());
    }

    //*************************************************************************
    TEST(test_ranges_enumerate_view_empty)
    {
      std::vector<int> v;
      auto             ev = v | etl::views::enumerate;

      CHECK(ev.begin() == ev.end());
      CHECK_EQUAL(0U, ev.size());
    }

    //*************************************************************************
    TEST(test_ranges_enumerate_view_size)
    {
      std::vector<int> v  = {1, 2, 3, 4, 5};
      auto             ev = v | etl::views::enumerate;

      CHECK_EQUAL(5U, ev.size());
    }

    //*************************************************************************
    TEST(test_ranges_enumerate_view_range_for)
    {
      std::vector<int> v  = {100, 200, 300};
      auto             ev = v | etl::views::enumerate;

      size_t expected_index    = 0;
      int    expected_values[] = {100, 200, 300};

      for (auto&& [idx, val] : ev)
      {
        CHECK_EQUAL(expected_index, idx);
        CHECK_EQUAL(expected_values[expected_index], val);
        ++expected_index;
      }

      CHECK_EQUAL(3U, expected_index);
    }

    //*************************************************************************
    TEST(test_ranges_enumerate_view_chained_with_take)
    {
      std::vector<int> v  = {10, 20, 30, 40, 50};
      auto             ev = v | etl::views::enumerate | etl::views::take(3);

      auto it = ev.begin();
      CHECK_EQUAL(0U, etl::get<0>(*it));
      CHECK_EQUAL(10, etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(1U, etl::get<0>(*it));
      CHECK_EQUAL(20, etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(2U, etl::get<0>(*it));
      CHECK_EQUAL(30, etl::get<1>(*it));
      ++it;
      CHECK(it == ev.end());
    }

    //*************************************************************************
    TEST(test_ranges_enumerate_view_single_element)
    {
      std::vector<int> v  = {42};
      auto             ev = v | etl::views::enumerate;

      auto it = ev.begin();
      CHECK_EQUAL(0U, etl::get<0>(*it));
      CHECK_EQUAL(42, etl::get<1>(*it));
      ++it;
      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<int> 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_ref, int&>::value, "enumerate value element should be a non-const reference");
      static_assert(!etl::is_const_v<etl::remove_reference_t<value_ref>>, "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<int> v = {10, 20, 30};

      for (auto&& [index, value] : v | etl::views::enumerate)
      {
        value += static_cast<int>(index);
      }

      std::vector<int> expected{10, 21, 32};
      CHECK_EQUAL(expected, v);
    }

    //*************************************************************************
    TEST(test_ranges_zip_view_basic)
    {
      std::vector<int>         v1 = {1, 2, 3};
      std::vector<std::string> v2 = {"a", "b", "c"};

      auto zv = etl::views::zip(v1, v2);

      auto it = zv.begin();
      CHECK_EQUAL(1, etl::get<0>(*it));
      CHECK_EQUAL(std::string("a"), etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(2, etl::get<0>(*it));
      CHECK_EQUAL(std::string("b"), etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(3, etl::get<0>(*it));
      CHECK_EQUAL(std::string("c"), etl::get<1>(*it));
      ++it;
      CHECK(it == zv.end());
    }

    //*************************************************************************
    TEST(test_ranges_zip_view_different_lengths)
    {
      std::vector<int>    v1 = {1, 2, 3, 4, 5};
      std::vector<double> v2 = {10.0, 20.0, 30.0};

      auto zv = etl::views::zip(v1, v2);

      CHECK_EQUAL(3U, zv.size());

      auto it = zv.begin();
      CHECK_EQUAL(1, etl::get<0>(*it));
      CHECK_CLOSE(10.0, etl::get<1>(*it), 0.01);
      ++it;
      CHECK_EQUAL(2, etl::get<0>(*it));
      CHECK_CLOSE(20.0, etl::get<1>(*it), 0.01);
      ++it;
      CHECK_EQUAL(3, etl::get<0>(*it));
      CHECK_CLOSE(30.0, etl::get<1>(*it), 0.01);
      ++it;
      CHECK(it == zv.end());
    }

    //*************************************************************************
    TEST(test_ranges_zip_view_three_ranges)
    {
      std::vector<int>    v1 = {1, 2, 3};
      std::vector<double> v2 = {1.5, 2.5, 3.5};
      std::array<char, 3> v3 = {'x', 'y', 'z'};

      auto zv = etl::views::zip(v1, v2, v3);

      CHECK_EQUAL(3U, zv.size());

      auto it = zv.begin();
      CHECK_EQUAL(1, etl::get<0>(*it));
      CHECK_CLOSE(1.5, etl::get<1>(*it), 0.01);
      CHECK_EQUAL('x', etl::get<2>(*it));
      ++it;
      CHECK_EQUAL(2, etl::get<0>(*it));
      CHECK_CLOSE(2.5, etl::get<1>(*it), 0.01);
      CHECK_EQUAL('y', etl::get<2>(*it));
      ++it;
      CHECK_EQUAL(3, etl::get<0>(*it));
      CHECK_CLOSE(3.5, etl::get<1>(*it), 0.01);
      CHECK_EQUAL('z', etl::get<2>(*it));
      ++it;
      CHECK(it == zv.end());
    }

    //*************************************************************************
    TEST(test_ranges_zip_view_empty)
    {
      std::vector<int>    v1;
      std::vector<double> v2 = {1.0, 2.0};

      auto zv = etl::views::zip(v1, v2);

      CHECK_EQUAL(0U, zv.size());
      CHECK(zv.begin() == zv.end());
    }

    //*************************************************************************
    TEST(test_ranges_zip_view_single_range)
    {
      std::vector<int> v1 = {10, 20, 30};

      auto zv = etl::views::zip(v1);

      CHECK_EQUAL(3U, zv.size());

      auto it = zv.begin();
      CHECK_EQUAL(10, etl::get<0>(*it));
      ++it;
      CHECK_EQUAL(20, etl::get<0>(*it));
      ++it;
      CHECK_EQUAL(30, etl::get<0>(*it));
      ++it;
      CHECK(it == zv.end());
    }

    //*************************************************************************
    TEST(test_ranges_zip_view_range_for)
    {
      std::vector<int> v1 = {1, 2, 3};
      std::vector<int> v2 = {10, 20, 30};

      auto zv = etl::views::zip(v1, v2);

      int    expected_first[]  = {1, 2, 3};
      int    expected_second[] = {10, 20, 30};
      size_t index             = 0;

      for (auto&& [a, b] : zv)
      {
        CHECK_EQUAL(expected_first[index], a);
        CHECK_EQUAL(expected_second[index], b);
        ++index;
      }

      CHECK_EQUAL(3U, index);
    }

    //*************************************************************************
    TEST(test_ranges_zip_view_with_list)
    {
      std::vector<int> v1 = {1, 2, 3, 4};
      std::list<int>   l1 = {100, 200, 300};

      auto zv = etl::views::zip(v1, l1);

      CHECK_EQUAL(3U, zv.size());

      auto it = zv.begin();
      CHECK_EQUAL(1, etl::get<0>(*it));
      CHECK_EQUAL(100, etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(2, etl::get<0>(*it));
      CHECK_EQUAL(200, etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(3, etl::get<0>(*it));
      CHECK_EQUAL(300, etl::get<1>(*it));
      ++it;
      CHECK(it == zv.end());
    }

    //*************************************************************************
    TEST(test_ranges_zip_view_chained_with_take)
    {
      std::vector<int> v1 = {1, 2, 3, 4, 5};
      std::vector<int> v2 = {10, 20, 30, 40, 50};

      auto zv = etl::views::zip(v1, v2) | etl::views::take(2);

      auto it = zv.begin();
      CHECK_EQUAL(1, etl::get<0>(*it));
      CHECK_EQUAL(10, etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(2, etl::get<0>(*it));
      CHECK_EQUAL(20, etl::get<1>(*it));
      ++it;
      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<int>  v1 = {1, 2, 3};
      std::vector<char> 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<first_ref, int&>::value, "zip element 0 should be a non-const reference");
      static_assert(etl::is_same<second_ref, char&>::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<int> v1 = {1, 2, 3};
      std::vector<int> v2 = {10, 20, 30};

      for (auto&& [a, b] : etl::views::zip(v1, v2))
      {
        a += 1;
        b *= 2;
      }

      std::vector<int> expected1{2, 3, 4};
      std::vector<int> expected2{20, 40, 60};
      CHECK_EQUAL(expected1, v1);
      CHECK_EQUAL(expected2, v2);
    }

    //*************************************************************************
    TEST(test_ranges_zip_transform_view_basic)
    {
      std::vector<int> v1 = {1, 2, 3};
      std::vector<int> v2 = {10, 20, 30};

      auto ztv = etl::views::zip_transform([](int a, int b) { return a + b; }, v1, v2);

      auto it = ztv.begin();
      CHECK_EQUAL(11, *it);
      ++it;
      CHECK_EQUAL(22, *it);
      ++it;
      CHECK_EQUAL(33, *it);
      ++it;
      CHECK(it == ztv.end());
    }

    //*************************************************************************
    TEST(test_ranges_zip_transform_view_different_lengths)
    {
      std::vector<int>    v1 = {1, 2, 3, 4, 5};
      std::vector<double> v2 = {10.5, 20.5, 30.5};

      auto ztv = etl::views::zip_transform([](int a, double b) { return a + b; }, v1, v2);

      CHECK_EQUAL(3U, ztv.size());

      auto it = ztv.begin();
      CHECK_CLOSE(11.5, *it, 0.01);
      ++it;
      CHECK_CLOSE(22.5, *it, 0.01);
      ++it;
      CHECK_CLOSE(33.5, *it, 0.01);
      ++it;
      CHECK(it == ztv.end());
    }

    //*************************************************************************
    TEST(test_ranges_zip_transform_view_three_ranges)
    {
      std::vector<int> v1 = {1, 2, 3};
      std::vector<int> v2 = {10, 20, 30};
      std::vector<int> v3 = {100, 200, 300};

      auto ztv = etl::views::zip_transform([](int a, int b, int c) { return a + b + c; }, v1, v2, v3);

      CHECK_EQUAL(3U, ztv.size());

      auto it = ztv.begin();
      CHECK_EQUAL(111, *it);
      ++it;
      CHECK_EQUAL(222, *it);
      ++it;
      CHECK_EQUAL(333, *it);
      ++it;
      CHECK(it == ztv.end());
    }

    //*************************************************************************
    TEST(test_ranges_zip_transform_view_empty)
    {
      std::vector<int> v1;
      std::vector<int> v2 = {1, 2, 3};

      auto ztv = etl::views::zip_transform([](int a, int b) { return a * b; }, v1, v2);

      CHECK_EQUAL(0U, ztv.size());
      CHECK(ztv.begin() == ztv.end());
    }

    //*************************************************************************
    TEST(test_ranges_zip_transform_view_single_range)
    {
      std::vector<int> v1 = {10, 20, 30};

      auto ztv = etl::views::zip_transform([](int a) { return a * 2; }, v1);

      CHECK_EQUAL(3U, ztv.size());

      auto it = ztv.begin();
      CHECK_EQUAL(20, *it);
      ++it;
      CHECK_EQUAL(40, *it);
      ++it;
      CHECK_EQUAL(60, *it);
      ++it;
      CHECK(it == ztv.end());
    }

    //*************************************************************************
    TEST(test_ranges_zip_transform_view_range_for)
    {
      std::vector<int> v1 = {1, 2, 3};
      std::vector<int> v2 = {10, 20, 30};

      auto ztv = etl::views::zip_transform([](int a, int b) { return a * b; }, v1, v2);

      int    expected[] = {10, 40, 90};
      size_t index      = 0;

      for (auto val : ztv)
      {
        CHECK_EQUAL(expected[index], val);
        ++index;
      }

      CHECK_EQUAL(3U, index);
    }

    //*************************************************************************
    TEST(test_ranges_zip_transform_view_with_list)
    {
      std::vector<int> v1 = {1, 2, 3, 4};
      std::list<int>   l1 = {100, 200, 300};

      auto ztv = etl::views::zip_transform([](int a, int b) { return a + b; }, v1, l1);

      CHECK_EQUAL(3U, ztv.size());

      auto it = ztv.begin();
      CHECK_EQUAL(101, *it);
      ++it;
      CHECK_EQUAL(202, *it);
      ++it;
      CHECK_EQUAL(303, *it);
      ++it;
      CHECK(it == ztv.end());
    }

    //*************************************************************************
    TEST(test_ranges_zip_transform_view_chained_with_take)
    {
      std::vector<int> v1 = {1, 2, 3, 4, 5};
      std::vector<int> v2 = {10, 20, 30, 40, 50};

      auto ztv = etl::views::zip_transform([](int a, int b) { return a + b; }, v1, v2) | etl::views::take(2);

      auto it = ztv.begin();
      CHECK_EQUAL(11, *it);
      ++it;
      CHECK_EQUAL(22, *it);
      ++it;
      CHECK(it == ztv.end());
    }

    //*************************************************************************
    TEST(test_ranges_zip_transform_view_string_concat)
    {
      std::vector<std::string> v1 = {"hello", "good", "nice"};
      std::vector<std::string> v2 = {" world", " morning", " day"};

      auto ztv = etl::views::zip_transform([](const std::string& a, const std::string& b) { return a + b; }, v1, v2);

      auto it = ztv.begin();
      CHECK_EQUAL(std::string("hello world"), *it);
      ++it;
      CHECK_EQUAL(std::string("good morning"), *it);
      ++it;
      CHECK_EQUAL(std::string("nice day"), *it);
      ++it;
      CHECK(it == ztv.end());
    }

    //*************************************************************************
    TEST(test_ranges_zip_transform_view_returns_different_type)
    {
      std::vector<int> v1 = {1, 2, 3};
      std::vector<int> v2 = {4, 5, 6};

      auto ztv = etl::views::zip_transform([](int a, int b) -> double { return static_cast<double>(a) / b; }, v1, v2);

      auto it = ztv.begin();
      CHECK_CLOSE(0.25, *it, 0.01);
      ++it;
      CHECK_CLOSE(0.4, *it, 0.01);
      ++it;
      CHECK_CLOSE(0.5, *it, 0.01);
      ++it;
      CHECK(it == ztv.end());
    }

    //*************************************************************************
    TEST(test_ranges_adjacent_view_basic)
    {
      std::vector<int> v = {1, 2, 3, 4, 5};

      auto av = etl::views::adjacent<2>(v);

      CHECK_EQUAL(4U, av.size());

      auto it = av.begin();
      CHECK_EQUAL(1, etl::get<0>(*it));
      CHECK_EQUAL(2, etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(2, etl::get<0>(*it));
      CHECK_EQUAL(3, etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(3, etl::get<0>(*it));
      CHECK_EQUAL(4, etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(4, etl::get<0>(*it));
      CHECK_EQUAL(5, etl::get<1>(*it));
      ++it;
      CHECK(it == av.end());
    }

    //*************************************************************************
    TEST(test_ranges_adjacent_view_triple)
    {
      std::vector<int> v = {10, 20, 30, 40, 50};

      auto av = etl::views::adjacent<3>(v);

      CHECK_EQUAL(3U, av.size());

      auto it = av.begin();
      CHECK_EQUAL(10, etl::get<0>(*it));
      CHECK_EQUAL(20, etl::get<1>(*it));
      CHECK_EQUAL(30, etl::get<2>(*it));
      ++it;
      CHECK_EQUAL(20, etl::get<0>(*it));
      CHECK_EQUAL(30, etl::get<1>(*it));
      CHECK_EQUAL(40, etl::get<2>(*it));
      ++it;
      CHECK_EQUAL(30, etl::get<0>(*it));
      CHECK_EQUAL(40, etl::get<1>(*it));
      CHECK_EQUAL(50, etl::get<2>(*it));
      ++it;
      CHECK(it == av.end());
    }

    //*************************************************************************
    TEST(test_ranges_adjacent_view_single_window)
    {
      std::vector<int> v = {1};

      auto av = etl::views::adjacent<1>(v);

      CHECK_EQUAL(1U, av.size());

      auto it = av.begin();
      CHECK_EQUAL(1, etl::get<0>(*it));
      ++it;
      CHECK(it == av.end());
    }

    //*************************************************************************
    TEST(test_ranges_adjacent_view_window_equals_range_size)
    {
      std::vector<int> v = {1, 2, 3};

      auto av = etl::views::adjacent<3>(v);

      CHECK_EQUAL(1U, av.size());

      auto it = av.begin();
      CHECK_EQUAL(1, etl::get<0>(*it));
      CHECK_EQUAL(2, etl::get<1>(*it));
      CHECK_EQUAL(3, etl::get<2>(*it));
      ++it;
      CHECK(it == av.end());
    }

    //*************************************************************************
    TEST(test_ranges_adjacent_view_window_larger_than_range)
    {
      std::vector<int> v = {1, 2};

      auto av = etl::views::adjacent<5>(v);

      CHECK_EQUAL(0U, av.size());
      CHECK(av.begin() == av.end());
    }

    //*************************************************************************
    TEST(test_ranges_adjacent_view_empty_range)
    {
      std::vector<int> v;

      auto av = etl::views::adjacent<2>(v);

      CHECK_EQUAL(0U, av.size());
      CHECK(av.begin() == av.end());
    }

    //*************************************************************************
    TEST(test_ranges_adjacent_view_range_for)
    {
      std::vector<int> v = {1, 2, 3, 4};

      auto av = etl::views::adjacent<2>(v);

      std::vector<int> firsts;
      std::vector<int> seconds;

      for (auto [a, b] : av)
      {
        firsts.push_back(a);
        seconds.push_back(b);
      }

      CHECK_EQUAL(3U, firsts.size());
      CHECK_EQUAL(1, firsts[0]);
      CHECK_EQUAL(2, firsts[1]);
      CHECK_EQUAL(3, firsts[2]);
      CHECK_EQUAL(2, seconds[0]);
      CHECK_EQUAL(3, seconds[1]);
      CHECK_EQUAL(4, seconds[2]);
    }

    //*************************************************************************
    TEST(test_ranges_adjacent_view_pairwise)
    {
      std::vector<int> v = {1, 2, 3, 4};

      auto av = etl::views::pairwise(v);

      CHECK_EQUAL(3U, av.size());

      auto it = av.begin();
      CHECK_EQUAL(1, etl::get<0>(*it));
      CHECK_EQUAL(2, etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(2, etl::get<0>(*it));
      CHECK_EQUAL(3, etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(3, etl::get<0>(*it));
      CHECK_EQUAL(4, etl::get<1>(*it));
      ++it;
      CHECK(it == av.end());
    }

    //*************************************************************************
    TEST(test_ranges_adjacent_view_pipe)
    {
      std::vector<int> v = {10, 20, 30, 40, 50};

      auto av = v | etl::views::adjacent<2>();

      CHECK_EQUAL(4U, av.size());

      auto it = av.begin();
      CHECK_EQUAL(10, etl::get<0>(*it));
      CHECK_EQUAL(20, etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(20, etl::get<0>(*it));
      CHECK_EQUAL(30, etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(30, etl::get<0>(*it));
      CHECK_EQUAL(40, etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(40, etl::get<0>(*it));
      CHECK_EQUAL(50, etl::get<1>(*it));
      ++it;
      CHECK(it == av.end());
    }

    //*************************************************************************
    TEST(test_ranges_adjacent_view_chained_with_take)
    {
      std::vector<int> v = {1, 2, 3, 4, 5};

      auto av = v | etl::views::adjacent<2>() | etl::views::take(2);

      auto it = av.begin();
      CHECK_EQUAL(1, etl::get<0>(*it));
      CHECK_EQUAL(2, etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(2, etl::get<0>(*it));
      CHECK_EQUAL(3, etl::get<1>(*it));
      ++it;
      CHECK(it == av.end());
    }

    //*************************************************************************
    TEST(test_ranges_adjacent_view_with_strings)
    {
      std::vector<std::string> v = {"a", "b", "c", "d"};

      auto av = etl::views::adjacent<2>(v);

      CHECK_EQUAL(3U, av.size());

      auto it = av.begin();
      CHECK_EQUAL(std::string("a"), etl::get<0>(*it));
      CHECK_EQUAL(std::string("b"), etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(std::string("b"), etl::get<0>(*it));
      CHECK_EQUAL(std::string("c"), etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(std::string("c"), etl::get<0>(*it));
      CHECK_EQUAL(std::string("d"), etl::get<1>(*it));
      ++it;
      CHECK(it == av.end());
    }

    //*************************************************************************
    TEST(test_ranges_adjacent_view_quad)
    {
      std::vector<int> v = {1, 2, 3, 4, 5, 6};

      auto av = etl::views::adjacent<4>(v);

      CHECK_EQUAL(3U, av.size());

      auto it = av.begin();
      CHECK_EQUAL(1, etl::get<0>(*it));
      CHECK_EQUAL(2, etl::get<1>(*it));
      CHECK_EQUAL(3, etl::get<2>(*it));
      CHECK_EQUAL(4, etl::get<3>(*it));
      ++it;
      CHECK_EQUAL(2, etl::get<0>(*it));
      CHECK_EQUAL(3, etl::get<1>(*it));
      CHECK_EQUAL(4, etl::get<2>(*it));
      CHECK_EQUAL(5, etl::get<3>(*it));
      ++it;
      CHECK_EQUAL(3, etl::get<0>(*it));
      CHECK_EQUAL(4, etl::get<1>(*it));
      CHECK_EQUAL(5, etl::get<2>(*it));
      CHECK_EQUAL(6, etl::get<3>(*it));
      ++it;
      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<int> 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<first_ref, int&>::value, "adjacent element 0 should be a non-const reference");
      static_assert(etl::is_same<second_ref, int&>::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<int> v = {1, 2, 3, 4};

      for (auto&& window : etl::views::adjacent<2>(v))
      {
        etl::get<0>(window) = -etl::get<0>(window);
      }

      std::vector<int> expected{-1, -2, -3, 4};
      CHECK_EQUAL(expected, v);
    }

    //*************************************************************************
    TEST(test_ranges_adjacent_transform_view_basic_sum)
    {
      std::vector<int> v = {1, 2, 3, 4, 5};

      auto atv = etl::views::adjacent_transform<2>(v, [](int a, int b) { return a + b; });

      CHECK_EQUAL(4U, atv.size());

      auto it = atv.begin();
      CHECK_EQUAL(3, *it); // 1+2
      ++it;
      CHECK_EQUAL(5, *it); // 2+3
      ++it;
      CHECK_EQUAL(7, *it); // 3+4
      ++it;
      CHECK_EQUAL(9, *it); // 4+5
      ++it;
      CHECK(it == atv.end());
    }

    //*************************************************************************
    TEST(test_ranges_adjacent_transform_view_triple_product)
    {
      std::vector<int> v = {1, 2, 3, 4, 5};

      auto atv = etl::views::adjacent_transform<3>(v, [](int a, int b, int c) { return a * b * c; });

      CHECK_EQUAL(3U, atv.size());

      auto it = atv.begin();
      CHECK_EQUAL(6, *it); // 1*2*3
      ++it;
      CHECK_EQUAL(24, *it); // 2*3*4
      ++it;
      CHECK_EQUAL(60, *it); // 3*4*5
      ++it;
      CHECK(it == atv.end());
    }

    //*************************************************************************
    TEST(test_ranges_adjacent_transform_view_single_window)
    {
      std::vector<int> v = {10, 20, 30};

      // Window size 1: function applied to each element individually
      auto atv = etl::views::adjacent_transform<1>(v, [](int a) { return a * 2; });

      CHECK_EQUAL(3U, atv.size());

      auto it = atv.begin();
      CHECK_EQUAL(20, *it);
      ++it;
      CHECK_EQUAL(40, *it);
      ++it;
      CHECK_EQUAL(60, *it);
      ++it;
      CHECK(it == atv.end());
    }

    //*************************************************************************
    TEST(test_ranges_adjacent_transform_view_window_equals_range_size)
    {
      std::vector<int> v = {1, 2, 3};

      auto atv = etl::views::adjacent_transform<3>(v, [](int a, int b, int c) { return a + b + c; });

      CHECK_EQUAL(1U, atv.size());

      auto it = atv.begin();
      CHECK_EQUAL(6, *it); // 1+2+3
      ++it;
      CHECK(it == atv.end());
    }

    //*************************************************************************
    TEST(test_ranges_adjacent_transform_view_window_larger_than_range)
    {
      std::vector<int> v = {1, 2};

      auto atv = etl::views::adjacent_transform<3>(v, [](int a, int b, int c) { return a + b + c; });

      CHECK_EQUAL(0U, atv.size());
      CHECK(atv.begin() == atv.end());
    }

    //*************************************************************************
    TEST(test_ranges_adjacent_transform_view_empty_range)
    {
      std::vector<int> v = {};

      auto atv = etl::views::adjacent_transform<2>(v, [](int a, int b) { return a + b; });

      CHECK_EQUAL(0U, atv.size());
      CHECK(atv.begin() == atv.end());
    }

    //*************************************************************************
    TEST(test_ranges_adjacent_transform_view_range_for)
    {
      std::vector<int> v        = {1, 2, 3, 4, 5};
      std::vector<int> expected = {3, 5, 7, 9};

      auto atv = etl::views::adjacent_transform<2>(v, [](int a, int b) { return a + b; });

      std::vector<int> result;
      for (auto val : atv)
      {
        result.push_back(val);
      }

      CHECK_EQUAL(expected.size(), result.size());
      for (size_t i = 0; i < expected.size(); ++i)
      {
        CHECK_EQUAL(expected[i], result[i]);
      }
    }

    //*************************************************************************
    TEST(test_ranges_adjacent_transform_view_pairwise_transform)
    {
      std::vector<int> v = {10, 20, 30, 40};

      auto atv = etl::views::pairwise_transform(v, [](int a, int b) { return b - a; });

      CHECK_EQUAL(3U, atv.size());

      auto it = atv.begin();
      CHECK_EQUAL(10, *it); // 20-10
      ++it;
      CHECK_EQUAL(10, *it); // 30-20
      ++it;
      CHECK_EQUAL(10, *it); // 40-30
      ++it;
      CHECK(it == atv.end());
    }

    //*************************************************************************
    TEST(test_ranges_adjacent_transform_view_pipe)
    {
      std::vector<int> v = {1, 2, 3, 4, 5};

      auto sum = [](int a, int b)
      {
        return a + b;
      };
      auto atv = v | etl::views::adjacent_transform<2>(sum);

      CHECK_EQUAL(4U, atv.size());

      auto it = atv.begin();
      CHECK_EQUAL(3, *it); // 1+2
      ++it;
      CHECK_EQUAL(5, *it); // 2+3
      ++it;
      CHECK_EQUAL(7, *it); // 3+4
      ++it;
      CHECK_EQUAL(9, *it); // 4+5
      ++it;
      CHECK(it == atv.end());
    }

    //*************************************************************************
    TEST(test_ranges_adjacent_transform_view_chained_with_take)
    {
      std::vector<int> v = {1, 2, 3, 4, 5};

      auto sum = [](int a, int b)
      {
        return a + b;
      };
      auto atv = v | etl::views::adjacent_transform<2>(sum) | etl::views::take(2);

      auto it = atv.begin();
      CHECK_EQUAL(3, *it); // 1+2
      ++it;
      CHECK_EQUAL(5, *it); // 2+3
      ++it;
      CHECK(it == atv.end());
    }

    //*************************************************************************
    TEST(test_ranges_adjacent_transform_view_different_return_type)
    {
      std::vector<int> v = {1, 2, 3, 4};

      // Returns double instead of int
      auto avg = [](int a, int b)
      {
        return (a + b) / 2.0;
      };
      auto atv = etl::views::adjacent_transform<2>(v, avg);

      CHECK_EQUAL(3U, atv.size());

      auto it = atv.begin();
      CHECK_CLOSE(1.5, *it, 0.001); // (1+2)/2.0
      ++it;
      CHECK_CLOSE(2.5, *it, 0.001); // (2+3)/2.0
      ++it;
      CHECK_CLOSE(3.5, *it, 0.001); // (3+4)/2.0
      ++it;
      CHECK(it == atv.end());
    }

    //*************************************************************************
    TEST(test_ranges_adjacent_transform_view_pairwise_transform_pipe)
    {
      std::vector<int> v = {5, 10, 15, 20};

      auto diff = [](int a, int b)
      {
        return b - a;
      };
      auto atv = v | etl::views::pairwise_transform(diff);

      CHECK_EQUAL(3U, atv.size());

      auto it = atv.begin();
      CHECK_EQUAL(5, *it);
      ++it;
      CHECK_EQUAL(5, *it);
      ++it;
      CHECK_EQUAL(5, *it);
      ++it;
      CHECK(it == atv.end());
    }

    //*************************************************************************
    TEST(test_ranges_chunk_view_basic)
    {
      std::vector<int> v  = {1, 2, 3, 4, 5, 6, 7, 8, 9};
      auto             cv = etl::ranges::chunk_view(v, 3);

      std::vector<std::vector<int>> expected{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
      size_t                        idx = 0;
      for (auto chunk : cv)
      {
        std::vector<int> actual(chunk.begin(), chunk.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      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<int>{1, 2, 3, 4, 5, 6, 7}, 3);

      std::vector<std::vector<int>> expected{{1, 2, 3}, {4, 5, 6}, {7}};
      size_t                        idx = 0;
      for (auto chunk : cv)
      {
        std::vector<int> actual(chunk.begin(), chunk.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(expected.size(), idx);
    }

    //*************************************************************************
    TEST(test_ranges_chunk_view_remainder)
    {
      // Range size not evenly divisible by chunk size
      std::vector<int> v  = {1, 2, 3, 4, 5, 6, 7};
      auto             cv = etl::ranges::chunk_view(v, 3);

      std::vector<std::vector<int>> expected{{1, 2, 3}, {4, 5, 6}, {7}};
      size_t                        idx = 0;
      for (auto chunk : cv)
      {
        std::vector<int> actual(chunk.begin(), chunk.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(expected.size(), idx);
    }

    //*************************************************************************
    TEST(test_ranges_chunk_view_single_element_chunks)
    {
      std::vector<int> v  = {10, 20, 30};
      auto             cv = etl::ranges::chunk_view(v, 1);

      std::vector<std::vector<int>> expected{{10}, {20}, {30}};
      size_t                        idx = 0;
      for (auto chunk : cv)
      {
        std::vector<int> actual(chunk.begin(), chunk.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(expected.size(), idx);
    }

    //*************************************************************************
    TEST(test_ranges_chunk_view_chunk_larger_than_range)
    {
      std::vector<int> v  = {1, 2, 3};
      auto             cv = etl::ranges::chunk_view(v, 10);

      size_t count = 0;
      for (auto chunk : cv)
      {
        std::vector<int> actual(chunk.begin(), chunk.end());
        std::vector<int> expected{1, 2, 3};
        CHECK_EQUAL(expected, actual);
        ++count;
      }
      CHECK_EQUAL(1U, count);
    }

    //*************************************************************************
    TEST(test_ranges_chunk_view_empty_range)
    {
      std::vector<int> v;
      auto             cv = etl::ranges::chunk_view(v, 3);

      size_t count = 0;
      for (auto chunk : cv)
      {
        (void)chunk;
        ++count;
      }
      CHECK_EQUAL(0U, count);
    }

    //*************************************************************************
    TEST(test_ranges_chunk_view_pipe)
    {
      std::vector<int> v  = {1, 2, 3, 4, 5, 6};
      auto             cv = v | etl::views::chunk(2);

      std::vector<std::vector<int>> expected{{1, 2}, {3, 4}, {5, 6}};
      size_t                        idx = 0;
      for (auto chunk : cv)
      {
        std::vector<int> actual(chunk.begin(), chunk.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(expected.size(), idx);
    }

    //*************************************************************************
    TEST(test_ranges_chunk_view_pipe_with_remainder)
    {
      std::vector<int> v  = {1, 2, 3, 4, 5};
      auto             cv = v | etl::views::chunk(2);

      std::vector<std::vector<int>> expected{{1, 2}, {3, 4}, {5}};
      size_t                        idx = 0;
      for (auto chunk : cv)
      {
        std::vector<int> actual(chunk.begin(), chunk.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(expected.size(), idx);
    }

    //*************************************************************************
    TEST(test_ranges_chunk_view_functional_call)
    {
      std::vector<int> v  = {1, 2, 3, 4, 5, 6};
      auto             cv = etl::views::chunk(v, 3);

      std::vector<std::vector<int>> expected{{1, 2, 3}, {4, 5, 6}};
      size_t                        idx = 0;
      for (auto chunk : cv)
      {
        std::vector<int> actual(chunk.begin(), chunk.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(expected.size(), idx);
    }

    //*************************************************************************
    TEST(test_ranges_chunk_view_etl_vector)
    {
      etl::vector<int, 10> v  = {1, 2, 3, 4, 5, 6, 7, 8};
      auto                 cv = etl::ranges::chunk_view(v, 3);

      std::vector<std::vector<int>> expected{{1, 2, 3}, {4, 5, 6}, {7, 8}};
      size_t                        idx = 0;
      for (auto chunk : cv)
      {
        std::vector<int> actual(chunk.begin(), chunk.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(expected.size(), idx);
    }

    //*************************************************************************
    TEST(test_ranges_chunk_view_array)
    {
      std::array<int, 6> a  = {10, 20, 30, 40, 50, 60};
      auto               cv = etl::ranges::chunk_view(a, 2);

      std::vector<std::vector<int>> expected{{10, 20}, {30, 40}, {50, 60}};
      size_t                        idx = 0;
      for (auto chunk : cv)
      {
        std::vector<int> actual(chunk.begin(), chunk.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(expected.size(), idx);
    }

    //*************************************************************************
    TEST(test_ranges_chunk_view_pipe_chain_take_chunk)
    {
      // Take first 6, then chunk by 2
      std::vector<int> v  = {1, 2, 3, 4, 5, 6, 7, 8, 9};
      auto             cv = v | etl::views::take(6) | etl::views::chunk(2);

      std::vector<std::vector<int>> expected{{1, 2}, {3, 4}, {5, 6}};
      size_t                        idx = 0;
      for (auto chunk : cv)
      {
        std::vector<int> actual(chunk.begin(), chunk.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(expected.size(), idx);
    }

    //*************************************************************************
    TEST(test_ranges_chunk_view_pipe_chain_drop_chunk)
    {
      // Drop first 2, then chunk by 3
      std::vector<int> v  = {1, 2, 3, 4, 5, 6, 7, 8};
      auto             cv = v | etl::views::drop(2) | etl::views::chunk(3);

      std::vector<std::vector<int>> expected{{3, 4, 5}, {6, 7, 8}};
      size_t                        idx = 0;
      for (auto chunk : cv)
      {
        std::vector<int> actual(chunk.begin(), chunk.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      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<int> 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<element_ref, int&>::value, "chunk element should be a non-const reference");
      static_assert(!etl::is_const_v<etl::remove_reference_t<element_ref>>, "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<int> 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<int> 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<int> v = {10, 20, 30, 40, 50};

      for (auto chunk : v | etl::views::chunk(2))
      {
        for (auto& element : chunk)
        {
          element += 1;
        }
      }

      std::vector<int> 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<int, 10> 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<int, 10> expected = {-1, -2, -3, -4, -5, -6};
      CHECK(expected == v);
    }

    //*************************************************************************
    // slide_view tests
    //*************************************************************************

    //*************************************************************************
    TEST(test_ranges_slide_view_basic)
    {
      std::vector<int> v  = {1, 2, 3, 4, 5};
      auto             sv = etl::ranges::slide_view(v, 3);

      std::vector<std::vector<int>> expected{{1, 2, 3}, {2, 3, 4}, {3, 4, 5}};
      size_t                        idx = 0;
      for (auto window : sv)
      {
        std::vector<int> actual(window.begin(), window.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(expected.size(), idx);
    }

    //*************************************************************************
    TEST(test_ranges_slide_view_window_size_1)
    {
      std::vector<int> v  = {10, 20, 30};
      auto             sv = etl::ranges::slide_view(v, 1);

      std::vector<std::vector<int>> expected{{10}, {20}, {30}};
      size_t                        idx = 0;
      for (auto window : sv)
      {
        std::vector<int> actual(window.begin(), window.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(expected.size(), idx);
    }

    //*************************************************************************
    TEST(test_ranges_slide_view_window_equals_range)
    {
      std::vector<int> v  = {1, 2, 3};
      auto             sv = etl::ranges::slide_view(v, 3);

      size_t count = 0;
      for (auto window : sv)
      {
        std::vector<int> actual(window.begin(), window.end());
        std::vector<int> expected{1, 2, 3};
        CHECK_EQUAL(expected, actual);
        ++count;
      }
      CHECK_EQUAL(1U, count);
    }

    //*************************************************************************
    TEST(test_ranges_slide_view_window_larger_than_range)
    {
      std::vector<int> v  = {1, 2, 3};
      auto             sv = etl::ranges::slide_view(v, 5);

      size_t count = 0;
      for (auto window : sv)
      {
        (void)window;
        ++count;
      }
      CHECK_EQUAL(0U, count);
    }

    //*************************************************************************
    TEST(test_ranges_slide_view_empty_range)
    {
      std::vector<int> v;
      auto             sv = etl::ranges::slide_view(v, 3);

      size_t count = 0;
      for (auto window : sv)
      {
        (void)window;
        ++count;
      }
      CHECK_EQUAL(0U, count);
    }

    //*************************************************************************
    TEST(test_ranges_slide_view_size)
    {
      std::vector<int> v = {1, 2, 3, 4, 5};

      CHECK_EQUAL(3U, etl::ranges::slide_view(v, 3).size());
      CHECK_EQUAL(5U, etl::ranges::slide_view(v, 1).size());
      CHECK_EQUAL(1U, etl::ranges::slide_view(v, 5).size());
      CHECK_EQUAL(0U, etl::ranges::slide_view(v, 6).size());
    }

    //*************************************************************************
    TEST(test_ranges_slide_view_pipe)
    {
      std::vector<int> v  = {1, 2, 3, 4, 5};
      auto             sv = v | etl::views::slide(3);

      std::vector<std::vector<int>> expected{{1, 2, 3}, {2, 3, 4}, {3, 4, 5}};
      size_t                        idx = 0;
      for (auto window : sv)
      {
        std::vector<int> actual(window.begin(), window.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(expected.size(), idx);
    }

    //*************************************************************************
    TEST(test_ranges_slide_view_functional_call)
    {
      std::vector<int> v  = {1, 2, 3, 4, 5};
      auto             sv = etl::views::slide(v, 2);

      std::vector<std::vector<int>> expected{{1, 2}, {2, 3}, {3, 4}, {4, 5}};
      size_t                        idx = 0;
      for (auto window : sv)
      {
        std::vector<int> actual(window.begin(), window.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(expected.size(), idx);
    }

    //*************************************************************************
    TEST(test_ranges_slide_view_etl_vector)
    {
      etl::vector<int, 10> v  = {1, 2, 3, 4, 5, 6};
      auto                 sv = etl::ranges::slide_view(v, 4);

      std::vector<std::vector<int>> expected{{1, 2, 3, 4}, {2, 3, 4, 5}, {3, 4, 5, 6}};
      size_t                        idx = 0;
      for (auto window : sv)
      {
        std::vector<int> actual(window.begin(), window.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(expected.size(), idx);
    }

    //*************************************************************************
    TEST(test_ranges_slide_view_array)
    {
      std::array<int, 5> a  = {10, 20, 30, 40, 50};
      auto               sv = etl::ranges::slide_view(a, 2);

      std::vector<std::vector<int>> expected{{10, 20}, {20, 30}, {30, 40}, {40, 50}};
      size_t                        idx = 0;
      for (auto window : sv)
      {
        std::vector<int> actual(window.begin(), window.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(expected.size(), idx);
    }

    //*************************************************************************
    TEST(test_ranges_slide_view_pipe_chain_take_slide)
    {
      // Take first 5, then slide with window 3
      std::vector<int> v  = {1, 2, 3, 4, 5, 6, 7, 8, 9};
      auto             sv = v | etl::views::take(5) | etl::views::slide(3);

      std::vector<std::vector<int>> expected{{1, 2, 3}, {2, 3, 4}, {3, 4, 5}};
      size_t                        idx = 0;
      for (auto window : sv)
      {
        std::vector<int> actual(window.begin(), window.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(expected.size(), idx);
    }

    //*************************************************************************
    TEST(test_ranges_slide_view_pipe_chain_drop_slide)
    {
      // Drop first 2, then slide with window 3
      std::vector<int> v  = {1, 2, 3, 4, 5, 6, 7};
      auto             sv = v | etl::views::drop(2) | etl::views::slide(3);

      std::vector<std::vector<int>> expected{{3, 4, 5}, {4, 5, 6}, {5, 6, 7}};
      size_t                        idx = 0;
      for (auto window : sv)
      {
        std::vector<int> actual(window.begin(), window.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      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<int> 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<element_ref, int&>::value, "slide window element should be a non-const reference");
      static_assert(!etl::is_const_v<etl::remove_reference_t<element_ref>>, "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<int> 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<int> expected{2, 4, 6, 8, 5};
      CHECK_EQUAL(expected, v);
    }

    //*************************************************************************
    // chunk_by_view tests
    //*************************************************************************

    //*************************************************************************
    TEST(test_ranges_chunk_by_view_basic)
    {
      // Group consecutive equal elements
      std::vector<int> v  = {1, 1, 2, 2, 2, 3, 3, 1};
      auto             cv = etl::ranges::chunk_by_view(v, [](int a, int b) { return a == b; });

      std::vector<std::vector<int>> expected{{1, 1}, {2, 2, 2}, {3, 3}, {1}};
      size_t                        idx = 0;
      for (auto chunk : cv)
      {
        std::vector<int> actual(chunk.begin(), chunk.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(expected.size(), idx);
    }

    //*************************************************************************
    TEST(test_ranges_chunk_by_view_less_than)
    {
      // Split where values stop being strictly increasing
      std::vector<int> v  = {1, 2, 3, 1, 2, 1};
      auto             cv = etl::ranges::chunk_by_view(v, [](int a, int b) { return a < b; });

      std::vector<std::vector<int>> expected{{1, 2, 3}, {1, 2}, {1}};
      size_t                        idx = 0;
      for (auto chunk : cv)
      {
        std::vector<int> actual(chunk.begin(), chunk.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(expected.size(), idx);
    }

    //*************************************************************************
    TEST(test_ranges_chunk_by_view_single_element)
    {
      std::vector<int> v  = {42};
      auto             cv = etl::ranges::chunk_by_view(v, [](int a, int b) { return a == b; });

      size_t count = 0;
      for (auto chunk : cv)
      {
        std::vector<int> actual(chunk.begin(), chunk.end());
        std::vector<int> expected{42};
        CHECK_EQUAL(expected, actual);
        ++count;
      }
      CHECK_EQUAL(1U, count);
    }

    //*************************************************************************
    TEST(test_ranges_chunk_by_view_empty_range)
    {
      std::vector<int> v;
      auto             cv = etl::ranges::chunk_by_view(v, [](int a, int b) { return a == b; });

      size_t count = 0;
      for (auto chunk : cv)
      {
        (void)chunk;
        ++count;
      }
      CHECK_EQUAL(0U, count);
    }

    //*************************************************************************
    TEST(test_ranges_chunk_by_view_all_same)
    {
      // All elements satisfy predicate => single chunk
      std::vector<int> v  = {5, 5, 5, 5};
      auto             cv = etl::ranges::chunk_by_view(v, [](int a, int b) { return a == b; });

      size_t count = 0;
      for (auto chunk : cv)
      {
        std::vector<int> actual(chunk.begin(), chunk.end());
        std::vector<int> expected{5, 5, 5, 5};
        CHECK_EQUAL(expected, actual);
        ++count;
      }
      CHECK_EQUAL(1U, count);
    }

    //*************************************************************************
    TEST(test_ranges_chunk_by_view_all_different)
    {
      // No adjacent pair satisfies predicate => each element is its own chunk
      std::vector<int> v  = {1, 2, 1, 2, 1};
      auto             cv = etl::ranges::chunk_by_view(v, [](int a, int b) { return a == b; });

      std::vector<std::vector<int>> expected{{1}, {2}, {1}, {2}, {1}};
      size_t                        idx = 0;
      for (auto chunk : cv)
      {
        std::vector<int> actual(chunk.begin(), chunk.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(expected.size(), idx);
    }

    //*************************************************************************
    TEST(test_ranges_chunk_by_view_pipe)
    {
      std::vector<int> v  = {1, 1, 2, 2, 3};
      auto             cv = v | etl::views::chunk_by([](int a, int b) { return a == b; });

      std::vector<std::vector<int>> expected{{1, 1}, {2, 2}, {3}};
      size_t                        idx = 0;
      for (auto chunk : cv)
      {
        std::vector<int> actual(chunk.begin(), chunk.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(expected.size(), idx);
    }

    //*************************************************************************
    TEST(test_ranges_chunk_by_view_functional_call)
    {
      std::vector<int> v  = {1, 2, 3, 1, 2};
      auto             cv = etl::views::chunk_by(v, [](int a, int b) { return a < b; });

      std::vector<std::vector<int>> expected{{1, 2, 3}, {1, 2}};
      size_t                        idx = 0;
      for (auto chunk : cv)
      {
        std::vector<int> actual(chunk.begin(), chunk.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(expected.size(), idx);
    }

    //*************************************************************************
    TEST(test_ranges_chunk_by_view_etl_vector)
    {
      etl::vector<int, 10> v  = {1, 1, 2, 3, 3};
      auto                 cv = etl::ranges::chunk_by_view(v, [](int a, int b) { return a == b; });

      std::vector<std::vector<int>> expected{{1, 1}, {2}, {3, 3}};
      size_t                        idx = 0;
      for (auto chunk : cv)
      {
        std::vector<int> actual(chunk.begin(), chunk.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(expected.size(), idx);
    }

    //*************************************************************************
    TEST(test_ranges_chunk_by_view_array)
    {
      std::array<int, 6> a  = {1, 1, 2, 2, 3, 3};
      auto               cv = etl::ranges::chunk_by_view(a, [](int x, int y) { return x == y; });

      std::vector<std::vector<int>> expected{{1, 1}, {2, 2}, {3, 3}};
      size_t                        idx = 0;
      for (auto chunk : cv)
      {
        std::vector<int> actual(chunk.begin(), chunk.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(expected.size(), idx);
    }

    //*************************************************************************
    TEST(test_ranges_chunk_by_view_pipe_chain_take_chunk_by)
    {
      // Take first 5, then chunk_by equal
      std::vector<int> v  = {1, 1, 2, 2, 3, 3, 4};
      auto             cv = v | etl::views::take(5) | etl::views::chunk_by([](int a, int b) { return a == b; });

      std::vector<std::vector<int>> expected{{1, 1}, {2, 2}, {3}};
      size_t                        idx = 0;
      for (auto chunk : cv)
      {
        std::vector<int> actual(chunk.begin(), chunk.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      CHECK_EQUAL(expected.size(), idx);
    }

    //*************************************************************************
    TEST(test_ranges_chunk_by_view_pipe_chain_drop_chunk_by)
    {
      // Drop first 2, then chunk_by strictly increasing
      std::vector<int> v  = {1, 1, 2, 3, 1, 2};
      auto             cv = v | etl::views::drop(2) | etl::views::chunk_by([](int a, int b) { return a < b; });

      std::vector<std::vector<int>> expected{{2, 3}, {1, 2}};
      size_t                        idx = 0;
      for (auto chunk : cv)
      {
        std::vector<int> actual(chunk.begin(), chunk.end());
        CHECK_EQUAL(expected[idx], actual);
        ++idx;
      }
      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<int> 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<element_ref, int&>::value, "chunk_by element should be a non-const reference");
      static_assert(!etl::is_const_v<etl::remove_reference_t<element_ref>>, "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<int> 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<int> expected{-1, -1, -2, -2, -2, -3};
      CHECK_EQUAL(expected, v);
    }

    //*************************************************************************
    // stride_view tests
    //*************************************************************************

    //*************************************************************************
    TEST(test_ranges_stride_view_basic)
    {
      std::vector<int> v  = {1, 2, 3, 4, 5, 6, 7, 8, 9};
      auto             sv = etl::ranges::stride_view(v, 3);

      std::vector<int> expected{1, 4, 7};
      std::vector<int> actual;
      for (auto val : sv)
      {
        actual.push_back(val);
      }
      CHECK_EQUAL(expected, actual);
    }

    //*************************************************************************
    TEST(test_ranges_stride_view_stride_of_one)
    {
      std::vector<int> v  = {10, 20, 30};
      auto             sv = etl::ranges::stride_view(v, 1);

      std::vector<int> expected{10, 20, 30};
      std::vector<int> actual;
      for (auto val : sv)
      {
        actual.push_back(val);
      }
      CHECK_EQUAL(expected, actual);
    }

    //*************************************************************************
    TEST(test_ranges_stride_view_stride_of_two)
    {
      std::vector<int> v  = {1, 2, 3, 4, 5, 6};
      auto             sv = etl::ranges::stride_view(v, 2);

      std::vector<int> expected{1, 3, 5};
      std::vector<int> actual;
      for (auto val : sv)
      {
        actual.push_back(val);
      }
      CHECK_EQUAL(expected, actual);
    }

    //*************************************************************************
    TEST(test_ranges_stride_view_stride_larger_than_range)
    {
      std::vector<int> v  = {1, 2, 3};
      auto             sv = etl::ranges::stride_view(v, 10);

      std::vector<int> expected{1};
      std::vector<int> actual;
      for (auto val : sv)
      {
        actual.push_back(val);
      }
      CHECK_EQUAL(expected, actual);
    }

    //*************************************************************************
    TEST(test_ranges_stride_view_stride_equals_range_size)
    {
      std::vector<int> v  = {1, 2, 3};
      auto             sv = etl::ranges::stride_view(v, 3);

      std::vector<int> expected{1};
      std::vector<int> actual;
      for (auto val : sv)
      {
        actual.push_back(val);
      }
      CHECK_EQUAL(expected, actual);
    }

    //*************************************************************************
    TEST(test_ranges_stride_view_empty_range)
    {
      std::vector<int> v;
      auto             sv = etl::ranges::stride_view(v, 3);

      size_t count = 0;
      for (auto val : sv)
      {
        (void)val;
        ++count;
      }
      CHECK_EQUAL(0U, count);
    }

    //*************************************************************************
    TEST(test_ranges_stride_view_single_element)
    {
      std::vector<int> v  = {42};
      auto             sv = etl::ranges::stride_view(v, 5);

      std::vector<int> expected{42};
      std::vector<int> actual;
      for (auto val : sv)
      {
        actual.push_back(val);
      }
      CHECK_EQUAL(expected, actual);
    }

    //*************************************************************************
    TEST(test_ranges_stride_view_pipe)
    {
      std::vector<int> v  = {1, 2, 3, 4, 5, 6, 7, 8, 9};
      auto             sv = v | etl::views::stride(3);

      std::vector<int> expected{1, 4, 7};
      std::vector<int> actual;
      for (auto val : sv)
      {
        actual.push_back(val);
      }
      CHECK_EQUAL(expected, actual);
    }

    //*************************************************************************
    TEST(test_ranges_stride_view_pipe_stride_of_two)
    {
      std::vector<int> v  = {10, 20, 30, 40, 50};
      auto             sv = v | etl::views::stride(2);

      std::vector<int> expected{10, 30, 50};
      std::vector<int> actual;
      for (auto val : sv)
      {
        actual.push_back(val);
      }
      CHECK_EQUAL(expected, actual);
    }

    //*************************************************************************
    TEST(test_ranges_stride_view_functional_call)
    {
      std::vector<int> v  = {1, 2, 3, 4, 5, 6};
      auto             sv = etl::views::stride(v, 2);

      std::vector<int> expected{1, 3, 5};
      std::vector<int> actual;
      for (auto val : sv)
      {
        actual.push_back(val);
      }
      CHECK_EQUAL(expected, actual);
    }

    //*************************************************************************
    TEST(test_ranges_stride_view_etl_vector)
    {
      etl::vector<int, 10> v  = {1, 2, 3, 4, 5, 6, 7, 8};
      auto                 sv = etl::ranges::stride_view(v, 3);

      std::vector<int> expected{1, 4, 7};
      std::vector<int> actual;
      for (auto val : sv)
      {
        actual.push_back(val);
      }
      CHECK_EQUAL(expected, actual);
    }

    //*************************************************************************
    TEST(test_ranges_stride_view_array)
    {
      std::array<int, 6> a  = {10, 20, 30, 40, 50, 60};
      auto               sv = etl::ranges::stride_view(a, 2);

      std::vector<int> expected{10, 30, 50};
      std::vector<int> actual;
      for (auto val : sv)
      {
        actual.push_back(val);
      }
      CHECK_EQUAL(expected, actual);
    }

    //*************************************************************************
    TEST(test_ranges_stride_view_not_evenly_divisible)
    {
      std::vector<int> v  = {1, 2, 3, 4, 5, 6, 7};
      auto             sv = etl::ranges::stride_view(v, 3);

      std::vector<int> expected{1, 4, 7};
      std::vector<int> actual;
      for (auto val : sv)
      {
        actual.push_back(val);
      }
      CHECK_EQUAL(expected, actual);
    }

    //*************************************************************************
    TEST(test_ranges_stride_view_pipe_chain_take_stride)
    {
      // Take first 6, then stride by 2
      std::vector<int> v  = {1, 2, 3, 4, 5, 6, 7, 8, 9};
      auto             sv = v | etl::views::take(6) | etl::views::stride(2);

      std::vector<int> expected{1, 3, 5};
      std::vector<int> actual;
      for (auto val : sv)
      {
        actual.push_back(val);
      }
      CHECK_EQUAL(expected, actual);
    }

    //*************************************************************************
    TEST(test_ranges_stride_view_pipe_chain_drop_stride)
    {
      // Drop first 2, then stride by 3
      std::vector<int> v  = {1, 2, 3, 4, 5, 6, 7, 8, 9};
      auto             sv = v | etl::views::drop(2) | etl::views::stride(3);

      std::vector<int> expected{3, 6, 9};
      std::vector<int> actual;
      for (auto val : sv)
      {
        actual.push_back(val);
      }
      CHECK_EQUAL(expected, actual);
    }

    //*************************************************************************
    TEST(test_ranges_stride_view_pipe_chain_stride_transform)
    {
      // Stride by 2, then transform (multiply by 10)
      std::vector<int> v     = {1, 2, 3, 4, 5};
      auto             mul10 = [](int x)
      {
        return x * 10;
      };
      auto sv = v | etl::views::stride(2) | etl::views::transform(mul10);

      std::vector<int> expected{10, 30, 50};
      std::vector<int> actual;
      for (auto val : sv)
      {
        actual.push_back(val);
      }
      CHECK_EQUAL(expected, actual);
    }

    //*************************************************************************
    TEST(test_ranges_stride_view_pipe_chain_filter_stride)
    {
      // Filter even, then stride by 2
      std::vector<int> v       = {1, 2, 3, 4, 5, 6, 7, 8};
      auto             is_even = [](int x)
      {
        return x % 2 == 0;
      };
      auto sv = v | etl::views::filter(is_even) | etl::views::stride(2);

      std::vector<int> expected{2, 6};
      std::vector<int> actual;
      for (auto val : sv)
      {
        actual.push_back(val);
      }
      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<int> v  = {1, 2, 3, 4};
      auto             sv = etl::ranges::stride_view(v, 2);

      using element_ref = decltype(*sv.begin());

      static_assert(etl::is_same<element_ref, int&>::value, "stride element should be a non-const reference");
      static_assert(!etl::is_const_v<etl::remove_reference_t<element_ref>>, "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<int> 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<int> expected{2, 2, 6, 4, 10, 6};
      CHECK_EQUAL(expected, v);
    }

    //*************************************************************************
    TEST(test_ranges_cartesian_product_view_basic)
    {
      std::vector<int>         v1 = {1, 2};
      std::vector<std::string> v2 = {"a", "b", "c"};

      auto cp = etl::views::cartesian_product(v1, v2);

      auto it = cp.begin();
      CHECK_EQUAL(1, etl::get<0>(*it));
      CHECK_EQUAL(std::string("a"), etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(1, etl::get<0>(*it));
      CHECK_EQUAL(std::string("b"), etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(1, etl::get<0>(*it));
      CHECK_EQUAL(std::string("c"), etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(2, etl::get<0>(*it));
      CHECK_EQUAL(std::string("a"), etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(2, etl::get<0>(*it));
      CHECK_EQUAL(std::string("b"), etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(2, etl::get<0>(*it));
      CHECK_EQUAL(std::string("c"), etl::get<1>(*it));
      ++it;
      CHECK(it == cp.end());
    }

    //*************************************************************************
    TEST(test_ranges_cartesian_product_view_size)
    {
      std::vector<int> v1 = {1, 2, 3};
      std::vector<int> v2 = {10, 20};

      auto cp = etl::views::cartesian_product(v1, v2);

      CHECK_EQUAL(6U, cp.size());
    }

    //*************************************************************************
    TEST(test_ranges_cartesian_product_view_three_ranges)
    {
      std::vector<int>    v1 = {1, 2};
      std::vector<char>   v2 = {'a', 'b'};
      std::vector<double> v3 = {0.5};

      auto cp = etl::views::cartesian_product(v1, v2, v3);

      CHECK_EQUAL(4U, cp.size());

      std::vector<etl::tuple<int, char, double>> expected = {{1, 'a', 0.5}, {1, 'b', 0.5}, {2, 'a', 0.5}, {2, 'b', 0.5}};

      size_t idx = 0;
      for (auto val : cp)
      {
        CHECK_EQUAL(etl::get<0>(expected[idx]), etl::get<0>(val));
        CHECK_EQUAL(etl::get<1>(expected[idx]), etl::get<1>(val));
        CHECK_CLOSE(etl::get<2>(expected[idx]), etl::get<2>(val), 0.01);
        ++idx;
      }
      CHECK_EQUAL(4U, idx);
    }

    //*************************************************************************
    TEST(test_ranges_cartesian_product_view_empty_range)
    {
      std::vector<int> v1 = {1, 2, 3};
      std::vector<int> v2;

      auto cp = etl::views::cartesian_product(v1, v2);

      CHECK_EQUAL(0U, cp.size());
      CHECK(cp.begin() == cp.end());
    }

    //*************************************************************************
    TEST(test_ranges_cartesian_product_view_single_range)
    {
      std::vector<int> v1 = {10, 20, 30};

      auto cp = etl::views::cartesian_product(v1);

      CHECK_EQUAL(3U, cp.size());

      auto it = cp.begin();
      CHECK_EQUAL(10, etl::get<0>(*it));
      ++it;
      CHECK_EQUAL(20, etl::get<0>(*it));
      ++it;
      CHECK_EQUAL(30, etl::get<0>(*it));
      ++it;
      CHECK(it == cp.end());
    }

    //*************************************************************************
    TEST(test_ranges_cartesian_product_view_range_for)
    {
      std::vector<int> v1 = {1, 2};
      std::vector<int> v2 = {10, 20};

      auto cp = etl::views::cartesian_product(v1, v2);

      std::vector<etl::tuple<int, int>> result;
      for (auto val : cp)
      {
        result.push_back(val);
      }

      CHECK_EQUAL(4U, result.size());
      CHECK_EQUAL(1, etl::get<0>(result[0]));
      CHECK_EQUAL(10, etl::get<1>(result[0]));
      CHECK_EQUAL(1, etl::get<0>(result[1]));
      CHECK_EQUAL(20, etl::get<1>(result[1]));
      CHECK_EQUAL(2, etl::get<0>(result[2]));
      CHECK_EQUAL(10, etl::get<1>(result[2]));
      CHECK_EQUAL(2, etl::get<0>(result[3]));
      CHECK_EQUAL(20, etl::get<1>(result[3]));
    }

    //*************************************************************************
    TEST(test_ranges_cartesian_product_view_with_array)
    {
      std::array<int, 2>  a1 = {1, 2};
      std::array<char, 3> a2 = {'x', 'y', 'z'};

      auto cp = etl::views::cartesian_product(a1, a2);

      CHECK_EQUAL(6U, cp.size());

      auto it = cp.begin();
      CHECK_EQUAL(1, etl::get<0>(*it));
      CHECK_EQUAL('x', etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(1, etl::get<0>(*it));
      CHECK_EQUAL('y', etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(1, etl::get<0>(*it));
      CHECK_EQUAL('z', etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(2, etl::get<0>(*it));
      CHECK_EQUAL('x', etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(2, etl::get<0>(*it));
      CHECK_EQUAL('y', etl::get<1>(*it));
      ++it;
      CHECK_EQUAL(2, etl::get<0>(*it));
      CHECK_EQUAL('z', etl::get<1>(*it));
      ++it;
      CHECK(it == cp.end());
    }

    //*************************************************************************
    TEST(test_ranges_cartesian_product_view_chained_with_take)
    {
      std::vector<int> v1 = {1, 2, 3};
      std::vector<int> v2 = {10, 20, 30};

      auto cp = etl::views::cartesian_product(v1, v2) | etl::views::take(4);

      std::vector<etl::tuple<int, int>> result;
      for (auto val : cp)
      {
        result.push_back(val);
      }

      CHECK_EQUAL(4U, result.size());
      CHECK_EQUAL(1, etl::get<0>(result[0]));
      CHECK_EQUAL(10, etl::get<1>(result[0]));
      CHECK_EQUAL(1, etl::get<0>(result[1]));
      CHECK_EQUAL(20, etl::get<1>(result[1]));
      CHECK_EQUAL(1, etl::get<0>(result[2]));
      CHECK_EQUAL(30, etl::get<1>(result[2]));
      CHECK_EQUAL(2, etl::get<0>(result[3]));
      CHECK_EQUAL(10, etl::get<1>(result[3]));
    }

    //*************************************************************************
    TEST(test_ranges_cartesian_product_view_single_elements)
    {
      std::vector<int>  v1 = {42};
      std::vector<char> v2 = {'z'};

      auto cp = etl::views::cartesian_product(v1, v2);

      CHECK_EQUAL(1U, cp.size());

      auto it = cp.begin();
      CHECK_EQUAL(42, etl::get<0>(*it));
      CHECK_EQUAL('z', etl::get<1>(*it));
      ++it;
      CHECK(it == cp.end());
    }

    //*************************************************************************
    TEST(test_ranges_cartesian_product_view_first_range_empty)
    {
      std::vector<int> v1;
      std::vector<int> v2 = {1, 2, 3};

      auto cp = etl::views::cartesian_product(v1, v2);

      CHECK_EQUAL(0U, cp.size());
      CHECK(cp.begin() == cp.end());
    }

    //*************************************************************************
    /// to_input_view tests
    //*************************************************************************
    TEST(test_ranges_to_input_view_functional)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

      auto iv = etl::ranges::to_input_view(v_in);

      // Iterator category should be input_iterator_tag
      using iv_iterator = decltype(iv.begin());
      using iv_category = typename etl::iterator_traits<iv_iterator>::iterator_category;
      static_assert(etl::is_same_v<iv_category, ETL_OR_STD::input_iterator_tag>, "to_input_view iterator should have input_iterator_tag");

      for (auto i : iv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
      CHECK_EQUAL(iv.size(), 10u);
      CHECK_EQUAL(iv.empty(), false);
    }

    //*************************************************************************
    TEST(test_ranges_to_input_view_empty_range)
    {
      etl::vector<int, 10> v_in;

      auto iv = etl::ranges::to_input_view(v_in);

      CHECK_EQUAL(iv.size(), 0u);
      CHECK_EQUAL(iv.empty(), true);
      CHECK(iv.begin() == iv.end());
    }

    //*************************************************************************
    TEST(test_ranges_to_input_view_reflects_base_changes)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4};

      auto iv = etl::ranges::to_input_view(v_in);

      CHECK_EQUAL(iv.front(), 0);
      CHECK_EQUAL(iv.size(), 5u);

      v_in[0] = 99;
      CHECK_EQUAL(iv.front(), 99);
    }

    //*************************************************************************
    TEST(test_ranges_to_input_view_pipe)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

      auto iv = v_in | etl::views::to_input();

      using iv_iterator = decltype(iv.begin());
      using iv_category = typename etl::iterator_traits<iv_iterator>::iterator_category;
      static_assert(etl::is_same_v<iv_category, ETL_OR_STD::input_iterator_tag>, "piped to_input view iterator should have input_iterator_tag");

      for (auto i : iv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
      CHECK_EQUAL(iv.size(), 10u);
    }

    //*************************************************************************
    TEST(test_ranges_views_to_input_functional)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

      auto iv = etl::views::to_input(v_in);

      using iv_iterator = decltype(iv.begin());
      using iv_category = typename etl::iterator_traits<iv_iterator>::iterator_category;
      static_assert(etl::is_same_v<iv_category, ETL_OR_STD::input_iterator_tag>, "views::to_input iterator should have input_iterator_tag");

      for (auto i : iv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    //*************************************************************************
    TEST(test_ranges_to_input_view_pipe_chained_with_take)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 1, 2};

      auto iv = v_in | etl::views::to_input() | etl::views::take(3);

      for (auto i : iv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    //*************************************************************************
    TEST(test_ranges_to_input_view_pipe_chained_with_drop)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{7, 8, 9};

      auto iv = v_in | etl::views::to_input() | etl::views::drop(7);

      for (auto i : iv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    //*************************************************************************
    TEST(test_ranges_to_input_view_pipe_chained_with_transform)
    {
      etl::vector<int, 10> v_in{0, 1, 2, 3, 4};
      etl::vector<int, 10> v_out;
      etl::vector<int, 10> v_out_expected{0, 2, 4, 6, 8};

      auto doubler = [](int i)
      {
        return i * 2;
      };
      auto iv = v_in | etl::views::to_input() | etl::views::transform(doubler);

      for (auto i : iv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected, v_out);
    }

    //*************************************************************************
    TEST(test_ranges_to_input_view_with_std_vector)
    {
      std::vector<int> v_in{0, 1, 2, 3, 4};
      std::vector<int> v_out;
      std::vector<int> v_out_expected{0, 1, 2, 3, 4};

      auto iv = etl::ranges::to_input_view(v_in);

      using iv_iterator = decltype(iv.begin());
      using iv_category = typename etl::iterator_traits<iv_iterator>::iterator_category;
      static_assert(etl::is_same_v<iv_category, ETL_OR_STD::input_iterator_tag>, "to_input_view over std::vector should have input_iterator_tag");

      for (auto i : iv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(v_out_expected.size(), v_out.size());
      for (size_t idx = 0; idx < v_out_expected.size(); ++idx)
      {
        CHECK_EQUAL(v_out_expected[idx], v_out[idx]);
      }
    }

    //*************************************************************************
    TEST(test_ranges_to_input_view_downgrades_from_random_access)
    {
      // std::vector has random_access_iterator; verify to_input downgrades it
      std::vector<int> v_in{10, 20, 30};

      using original_category = typename etl::iterator_traits< std::vector<int>::iterator>::iterator_category;
      static_assert(etl::is_same_v<original_category, ETL_OR_STD::random_access_iterator_tag>, "std::vector iterator should be random_access");

      auto iv = etl::ranges::to_input_view(v_in);

      using iv_iterator = decltype(iv.begin());
      using iv_category = typename etl::iterator_traits<iv_iterator>::iterator_category;
      static_assert(etl::is_same_v<iv_category, ETL_OR_STD::input_iterator_tag>, "to_input_view should downgrade to input_iterator_tag");

      etl::vector<int, 10> v_out;
      for (auto i : iv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(3u, v_out.size());
      CHECK_EQUAL(10, v_out[0]);
      CHECK_EQUAL(20, v_out[1]);
      CHECK_EQUAL(30, v_out[2]);
    }

    //*************************************************************************
    TEST(test_ranges_to_input_view_downgrades_from_bidirectional)
    {
      // std::list has bidirectional_iterator; verify to_input downgrades it
      std::list<int> l_in{10, 20, 30};

      using original_category = typename etl::iterator_traits< std::list<int>::iterator>::iterator_category;
      static_assert(etl::is_same_v<original_category, ETL_OR_STD::bidirectional_iterator_tag>, "std::list iterator should be bidirectional");

      auto iv = etl::ranges::to_input_view(l_in);

      using iv_iterator = decltype(iv.begin());
      using iv_category = typename etl::iterator_traits<iv_iterator>::iterator_category;
      static_assert(etl::is_same_v<iv_category, ETL_OR_STD::input_iterator_tag>, "to_input_view should downgrade to input_iterator_tag");

      etl::vector<int, 10> v_out;
      for (auto i : iv)
      {
        v_out.push_back(i);
      }

      CHECK_EQUAL(3u, v_out.size());
      CHECK_EQUAL(10, v_out[0]);
      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<int> 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<element_ref, int&>::value, "to_input element should be a non-const reference");
      static_assert(!etl::is_const_v<etl::remove_reference_t<element_ref>>, "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<int> v_in{1, 2, 3, 4};
      auto             iv = etl::ranges::to_input_view(v_in);

      for (auto& element : iv)
      {
        element *= 10;
      }

      std::vector<int> expected{10, 20, 30, 40};
      CHECK_EQUAL(expected, v_in);
    }
  }
    #include "etl/private/diagnostic_pop.h"
} // namespace

  #endif
#endif
