Browse Source

Merge branch 'feat/cascade_iterator'

* feat/cascade_iterator:
  test-refactor: use views in test cases
  feat: implement cascade_iterator
  refactor: extract some of the impl of recursive_iterator to support cascade_iterator
Sam Jaffe 2 months ago
parent
commit
e33c3b4ace

+ 80 - 0
include/iterator/cascade_iterator.h

@@ -0,0 +1,80 @@
+#pragma once
+
+#include <cstddef>
+
+#include <iterator/concepts.h>
+#include <iterator/detail/projection_tuple.h>
+#include <iterator/detail/recursive_expander.h>
+#include <iterator/end_aware_iterator.h>
+#include <iterator/forwards.h>
+#include <iterator/recursive_iterator.h>
+
+namespace iterator {
+template <typename It, typename Projs, typename MaxDepth>
+class cascade_iterator;
+
+template <typename It, typename... Projs, typename MaxDepth>
+class cascade_iterator<It, detail::Projections<Projs...>, MaxDepth>
+    : public recursive_iterator_helper<It, MaxDepth,
+                                       detail::Projections<Projs...>>::type,
+      public facade<
+          cascade_iterator<It, detail::Projections<Projs...>, MaxDepth>> {
+public:
+  using sentinel_type = sentinel_t;
+
+public:
+  cascade_iterator() = default;
+
+  explicit cascade_iterator(Range auto & range, Projs... projs)
+      : cascade_iterator(end_aware_iterator(range), projs...) {}
+
+  explicit cascade_iterator(Range auto & range, MaxDepth, Projs... projs)
+      : cascade_iterator(end_aware_iterator(range), projs...) {}
+
+  explicit cascade_iterator(end_aware_iterator<It> iter, MaxDepth,
+                            Projs... projs)
+      : cascade_iterator::recursive_iterator_base(iter, projs...) {}
+
+  explicit cascade_iterator(end_aware_iterator<It> iter, Projs... projs)
+      : cascade_iterator::recursive_iterator_base(iter, projs...) {}
+
+  template <typename Ot>
+  explicit cascade_iterator(end_aware_iterator<Ot> other, Projs... projs)
+      : cascade_iterator(end_aware_iterator<It>(other), projs...) {}
+
+  template <typename Ot>
+  explicit cascade_iterator(end_aware_iterator<Ot> other, MaxDepth,
+                            Projs... projs)
+      : cascade_iterator(end_aware_iterator<It>(other), projs...) {}
+};
+
+template <Range R, typename... Projs>
+cascade_iterator(R, Projs...)
+    -> cascade_iterator<iterator_t<R>, detail::Projections<Projs...>,
+                        bounded<sizeof...(Projs) + 1>>;
+
+template <typename It, typename... Projs>
+cascade_iterator(end_aware_iterator<It>, Projs...)
+    -> cascade_iterator<It, detail::Projections<Projs...>,
+                        bounded<sizeof...(Projs) + 1>>;
+
+template <Range R, typename... Projs, size_t N>
+  requires(N > sizeof...(Projs))
+cascade_iterator(R, bounded<N>, Projs...)
+    -> cascade_iterator<iterator_t<R>, detail::Projections<Projs...>,
+                        bounded<N>>;
+
+template <typename It, typename... Projs, size_t N>
+  requires(N > sizeof...(Projs))
+cascade_iterator(end_aware_iterator<It>, bounded<N>, Projs...)
+    -> cascade_iterator<It, detail::Projections<Projs...>, bounded<N>>;
+
+template <Range R, typename... Projs>
+cascade_iterator(R, unbounded, Projs...)
+    -> cascade_iterator<iterator_t<R>, detail::Projections<Projs...>,
+                        unbounded>;
+
+template <typename It, typename... Projs>
+cascade_iterator(end_aware_iterator<It>, unbounded, Projs...)
+    -> cascade_iterator<It, detail::Projections<Projs...>, unbounded>;
+}

+ 33 - 0
include/iterator/detail/capture_fn.h

@@ -0,0 +1,33 @@
+//
+//  capture.h
+//  iterator
+//
+//  Created by Sam Jaffe on 9/23/25.
+//  Copyright © 2025 Sam Jaffe. All rights reserved.
+//
+
+#pragma once
+
+#include <functional>
+
+namespace iterator::detail {
+template <typename P, typename T> class CaptureFn {
+private:
+  using R = std::invoke_result_t<P, T>;
+
+private:
+  P fn_;
+  void const * key_;
+  mutable R cache_;
+
+public:
+  CaptureFn(P fn) : fn_(fn) {} // NOLINT
+
+  auto const & operator()(T & arg) const {
+    if (key_ != &arg) {
+      new (&cache_) R(std::invoke(fn_, std::forward<T>(arg)));
+    }
+    return cache_;
+  }
+};
+}

+ 72 - 0
include/iterator/detail/projection_tuple.h

@@ -0,0 +1,72 @@
+//
+//  projection_tuple.h
+//  iterator
+//
+//  Created by Sam Jaffe on 9/23/25.
+//  Copyright © 2025 Sam Jaffe. All rights reserved.
+//
+
+#pragma once
+
+#include <functional>
+#include <tuple>
+#include <type_traits>
+
+#include <iterator/concepts.h>
+#include <iterator/detail/capture_fn.h>
+
+namespace iterator::detail {
+template <typename... Projs> class Projections {
+private:
+  std::tuple<Projs...> fns_;
+
+public:
+  Projections() = default;
+  Projections(Projs... fns)
+    requires(sizeof...(Projs) > 0)
+      : fns_(fns...) {}
+  Projections(std::tuple<Projs...> const & fns) : fns_(fns) {}
+
+  template <size_t I, typename T>
+  decltype(auto) operator()(T && arg,
+                            std::integral_constant<size_t, I> = {}) const {
+    if constexpr (I >= sizeof...(Projs)) {
+      return std::forward<T>(arg);
+    } else if constexpr (Assoc<std::decay_t<T>>) {
+      return std::tie(arg.first, std::invoke(std::get<I>(fns_),
+                                             std::forward<T>(arg).second));
+    } else {
+      return std::invoke(std::get<I>(fns_), std::forward<T>(arg));
+    }
+  }
+};
+
+template <typename It, typename Projs, size_t I = 0> struct ProjectionExpander {
+  using type = std::tuple<>;
+};
+
+template <typename It, typename... Projs, size_t I>
+  requires(I < sizeof...(Projs))
+struct ProjectionExpander<It, Projections<Projs...>, I> {
+  using Fn = std::tuple_element_t<I, std::tuple<Projs...>>;
+
+  template <typename T> static decltype(auto) get(T && ref) {
+    if constexpr (Assoc<std::decay_t<T>>) {
+      return std::forward<T>(ref).second;
+    } else {
+      return std::forward<T>(ref);
+    }
+  }
+
+  static std::iter_reference_t<It> _ref;
+
+  using value_type = decltype(get(_ref));
+  using result_type = std::invoke_result_t<Fn, value_type>;
+
+  using type = tuple_cat_t<
+      std::tuple<std::conditional_t<std::is_reference_v<result_type>, Fn,
+                                    CaptureFn<Fn, value_type>>>,
+      typename ProjectionExpander<iterator_t<result_type>,
+                                  Projections<Projs...>, I + 1>::type>;
+};
+}

+ 57 - 0
include/iterator/detail/recursive_expander.h

@@ -0,0 +1,57 @@
+//
+//  RecursiveExpander.h
+//  iterator
+//
+//  Created by Sam Jaffe on 9/23/25.
+//  Copyright © 2025 Sam Jaffe. All rights reserved.
+//
+
+#pragma once
+#include <iterator>
+#include <type_traits>
+
+#include <iterator/concepts.h>
+#include <iterator/detail/projection_tuple.h>
+#include <iterator/end_aware_iterator.h>
+
+namespace iterator::detail {
+template <typename Proj, typename It, size_t I>
+using projection_t = std::invoke_result_t<Proj, std::iter_reference_t<It>,
+                                          std::integral_constant<size_t, I>>;
+
+template <typename It, typename Projs, typename MaxDepth, size_t N = 0,
+          typename V = std::decay_t<projection_t<Projs, It, N>>>
+struct RecursiveExpander {
+  using type = std::tuple<end_aware_iterator<It>>;
+};
+
+template <typename It, typename Projs, size_t N>
+struct RecursiveExpander<It, Projs, bounded<N + 1>, N> {
+  using type = std::tuple<end_aware_iterator<It>>;
+};
+
+template <typename It, typename Projs, typename MaxDepth, size_t N, Range V>
+struct RecursiveExpander<It, Projs, MaxDepth, N, V> {
+  using projected_value_type = projection_t<Projs, It, N>;
+
+  using expand_next = RecursiveExpander<iterator_t<projected_value_type>, Projs,
+                                        MaxDepth, N + 1>;
+
+  using type = tuple_cat_t<std::tuple<end_aware_iterator<It>>,
+                           typename expand_next::type>;
+};
+
+template <typename It, typename Projs, typename MaxDepth, size_t N,
+          AssocRange V>
+struct RecursiveExpander<It, Projs, MaxDepth, N, V> {
+  using projected_value_type =
+      std::tuple_element_t<1,
+                           std::remove_reference_t<projection_t<Projs, It, N>>>;
+
+  using expand_next = RecursiveExpander<iterator_t<projected_value_type>, Projs,
+                                        MaxDepth, N + 1>;
+
+  using type = tuple_cat_t<std::tuple<end_aware_iterator<It>>,
+                           typename expand_next::type>;
+};
+}

+ 34 - 49
include/iterator/recursive_iterator.h

@@ -8,10 +8,12 @@
 #pragma once
 
 #include <ranges>
-#include <string>
 #include <tuple>
 #include <utility>
 
+#include <iterator/concepts.h>
+#include <iterator/detail/projection_tuple.h>
+#include <iterator/detail/recursive_expander.h>
 #include <iterator/end_aware_iterator.h>
 #include <iterator/facade.h>
 #include <iterator/forwards.h>
@@ -19,42 +21,12 @@
 #include <iterator/detail/macro.h>
 
 namespace iterator {
-template <typename It, typename MaxDepth, size_t N = 0,
-          typename V = std::iter_value_t<It>>
-struct tuple_expander {
-  using iterator_tuple = std::tuple<end_aware_iterator<It>>;
-};
-
-template <typename It, size_t N> struct tuple_expander<It, bounded<N + 1>, N> {
-  using iterator_tuple = std::tuple<end_aware_iterator<It>>;
-};
-
-template <typename It, typename MaxDepth, size_t N, Range V>
-struct tuple_expander<It, MaxDepth, N, V> {
-  static It & _it;
-
-  using next_iterator_t = decltype(std::begin(*_it));
-  using expand_next = tuple_expander<next_iterator_t, MaxDepth, N + 1>;
-
-  using iterator_tuple = tuple_cat_t<std::tuple<end_aware_iterator<It>>,
-                                     typename expand_next::iterator_tuple>;
-};
-
-template <typename It, typename MaxDepth, size_t N, AssocRange V>
-struct tuple_expander<It, MaxDepth, N, V> {
-  static It & _it;
-
-  using next_iterator_t = decltype(std::begin(_it->second));
-  using expand_next = tuple_expander<next_iterator_t, MaxDepth, N + 1>;
-
-  using iterator_tuple = tuple_cat_t<std::tuple<end_aware_iterator<It>>,
-                                     typename expand_next::iterator_tuple>;
-};
-
-template <typename Tuple, typename Indices> class recursive_iterator_base;
-template <typename... It, size_t... Is>
-class recursive_iterator_base<std::tuple<It...>, std::index_sequence<Is...>>
-    : public std::tuple<It...> {
+template <typename Tuple, typename Projs, typename Indices>
+class recursive_iterator_base;
+template <typename... It, typename... Projs, size_t... Is>
+class recursive_iterator_base<std::tuple<It...>, detail::Projections<Projs...>,
+                              std::index_sequence<Is...>>
+    : public std::tuple<It...>, private detail::Projections<Projs...> {
 public:
   static constexpr size_t LastIndex = sizeof...(It) - 1;
   template <size_t I>
@@ -88,7 +60,10 @@ public:
 
 protected:
   recursive_iterator_base() = default;
-  recursive_iterator_base(iterator_type<0> iter) { assign<0>(iter); }
+  recursive_iterator_base(iterator_type<0> iter, Projs... projs)
+      : detail::Projections<Projs...>(projs...) {
+    assign<0>(iter);
+  }
 
 private:
   template <size_t I = LastIndex> bool increment() {
@@ -106,10 +81,12 @@ private:
   template <size_t I> decltype(auto) get() const {
     auto iter = std::get<I>(*this);
     if constexpr (I + 1 == sizeof...(It)) {
-      if constexpr (Assoc<value_type<I>>) {
-        return std::tie(iter->first, iter->second);
+      auto && rval = (*this)(*iter, std::integral_constant<size_t, I>{});
+      if constexpr (Assoc<std::decay_t<decltype(rval)>>) {
+        // Needed for tuple-cat to preserve references
+        return std::tie(rval.first, rval.second);
       } else {
-        return std::tie(*iter);
+        return std::tie(rval); // Cannot wrap tie(pair) and expect it to work
       }
     } else if constexpr (Assoc<value_type<I>>) {
       return std::tie(iter->first);
@@ -121,7 +98,9 @@ private:
   template <size_t I, typename T> void assign(end_aware_iterator<T> it) {
     std::get<I>(*this) = it;
     if constexpr (I < LastIndex) {
-      if (!it.at_end()) { assign<I + 1>(*it); }
+      if (!it.at_end()) {
+        assign<I + 1>((*this)(*it, std::integral_constant<size_t, I>{}));
+      }
     }
   }
 
@@ -129,18 +108,24 @@ private:
     if constexpr (Range<T>) {
       assign<I>(end_aware_iterator(std::forward<T>(value)));
     } else {
-      assign<I>(value.second);
+      assign<I>(std::get<1>(value));
     }
   }
 };
 
-template <typename It, typename MaxDepth> struct recursive_iterator_helper {
-  using iterator_tuple = typename tuple_expander<It, MaxDepth>::iterator_tuple;
+template <typename It, typename MaxDepth,
+          typename Projs = detail::Projections<>>
+struct recursive_iterator_helper {
+  static typename detail::ProjectionExpander<It, Projs>::type const & _projs;
+  using Projections = decltype(detail::Projections(_projs));
+
+  using iterator_tuple =
+      typename detail::RecursiveExpander<It, Projections, MaxDepth>::type;
 
   static constexpr auto extent = std::tuple_size_v<iterator_tuple>;
   using indices = decltype(std::make_index_sequence<extent>());
 
-  using type = recursive_iterator_base<iterator_tuple, indices>;
+  using type = recursive_iterator_base<iterator_tuple, Projections, indices>;
 };
 
 /**
@@ -186,7 +171,7 @@ recursive_iterator(Range &, MaxDepth)
     -> recursive_iterator<iterator_t<Range>, MaxDepth>;
 template <typename Range>
 recursive_iterator(Range &) -> recursive_iterator<iterator_t<Range>, unbounded>;
-}
+} // namespace iterator
 
 namespace std {
 template <size_t I, typename It, typename MaxDepth>
@@ -194,7 +179,7 @@ auto get(::iterator::recursive_iterator<It, MaxDepth> const & iter) {
   using return_type = std::decay_t<decltype(iter)>::template iterator_type<I>;
   return static_cast<return_type>(iter);
 }
-}
+} // namespace std
 
 namespace iterator::views {
 template <size_t N>
@@ -214,6 +199,6 @@ struct recursive_fn : std::ranges::range_adaptor_closure<recursive_fn> {
 
 template <size_t N> constexpr recursive_n_fn<N> recursive_n{};
 constexpr recursive_fn recursive;
-}
+} // namespace iterator::views
 
 #include <iterator/detail/undef.h>

+ 20 - 0
iterator.xcodeproj/project.pbxproj

@@ -7,6 +7,11 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		CD0AED842E836E1900E8B05D /* cascade_iterator.h in Headers */ = {isa = PBXBuildFile; fileRef = CD0AED832E836E1900E8B05D /* cascade_iterator.h */; };
+		CD0AED892E836E4600E8B05D /* recursive_expander.h in Headers */ = {isa = PBXBuildFile; fileRef = CD0AED882E836E4600E8B05D /* recursive_expander.h */; };
+		CD0AED8B2E836E9F00E8B05D /* projection_tuple.h in Headers */ = {isa = PBXBuildFile; fileRef = CD0AED8A2E836E9F00E8B05D /* projection_tuple.h */; };
+		CD0AED8D2E836EF200E8B05D /* capture_fn.h in Headers */ = {isa = PBXBuildFile; fileRef = CD0AED8C2E836EF200E8B05D /* capture_fn.h */; };
+		CD0AED8F2E83FFC400E8B05D /* cascade_iterator_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD0AED8E2E83FFC400E8B05D /* cascade_iterator_test.cxx */; };
 		CD41AFE32E7F13E4004F3E51 /* concepts.h in Headers */ = {isa = PBXBuildFile; fileRef = CD41AFE22E7F0434004F3E51 /* concepts.h */; };
 		CD41AFE42E7F13E4004F3E51 /* forwards.h in Headers */ = {isa = PBXBuildFile; fileRef = CDA2B6122858128C004D5353 /* forwards.h */; };
 		CD41AFE52E7F13E4004F3E51 /* facade.h in Headers */ = {isa = PBXBuildFile; fileRef = CDA2B6132858128C004D5353 /* facade.h */; };
@@ -78,6 +83,11 @@
 /* End PBXContainerItemProxy section */
 
 /* Begin PBXFileReference section */
+		CD0AED832E836E1900E8B05D /* cascade_iterator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cascade_iterator.h; sourceTree = "<group>"; };
+		CD0AED882E836E4600E8B05D /* recursive_expander.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = recursive_expander.h; sourceTree = "<group>"; };
+		CD0AED8A2E836E9F00E8B05D /* projection_tuple.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = projection_tuple.h; sourceTree = "<group>"; };
+		CD0AED8C2E836EF200E8B05D /* capture_fn.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = capture_fn.h; sourceTree = "<group>"; };
+		CD0AED8E2E83FFC400E8B05D /* cascade_iterator_test.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = cascade_iterator_test.cxx; sourceTree = "<group>"; };
 		CD3C6DDB26238F8F00548B64 /* xcode_gtest_helper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = xcode_gtest_helper.h; sourceTree = "<group>"; };
 		CD41AFE22E7F0434004F3E51 /* concepts.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = concepts.h; sourceTree = "<group>"; };
 		CD5A8E7329D7910D008C2A4F /* sentinel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = sentinel.h; sourceTree = "<group>"; };
@@ -162,6 +172,7 @@
 				CD3C6DDB26238F8F00548B64 /* xcode_gtest_helper.h */,
 				CD5AEAE729D86D8100A390A4 /* ranges.h */,
 				CD5AEB3429D897DD00A390A4 /* capture_iterator_test.cxx */,
+				CD0AED8E2E83FFC400E8B05D /* cascade_iterator_test.cxx */,
 				CDCB3BD224E1D5320029B771 /* end_aware_iterator_test.cxx */,
 				CDCB3BD324E1D5320029B771 /* filter_iterator_test.cxx */,
 				CDCB3BD424E1D5320029B771 /* indexed_iterator_test.cxx */,
@@ -195,6 +206,7 @@
 				CD5A8E7329D7910D008C2A4F /* sentinel.h */,
 				CDA2B6172858128C004D5353 /* detail */,
 				CD5AEB3329D8956600A390A4 /* capture_iterator.h */,
+				CD0AED832E836E1900E8B05D /* cascade_iterator.h */,
 				CDA2B61B2858128C004D5353 /* end_aware_iterator.h */,
 				CDA2B61E2858128C004D5353 /* filter_iterator.h */,
 				CDA2B6142858128C004D5353 /* indexed_iterator.h */,
@@ -212,6 +224,9 @@
 				CDA2B61A2858128C004D5353 /* arrow_proxy.h */,
 				CD5AEB3129D8885400A390A4 /* macro.h */,
 				CD5AEB3229D8886200A390A4 /* undef.h */,
+				CD0AED8A2E836E9F00E8B05D /* projection_tuple.h */,
+				CD0AED8C2E836EF200E8B05D /* capture_fn.h */,
+				CD0AED882E836E4600E8B05D /* recursive_expander.h */,
 			);
 			path = detail;
 			sourceTree = "<group>";
@@ -254,13 +269,17 @@
 				CD41AFE42E7F13E4004F3E51 /* forwards.h in Headers */,
 				CD41AFE52E7F13E4004F3E51 /* facade.h in Headers */,
 				CD41AFE62E7F13E4004F3E51 /* proxy.h in Headers */,
+				CD0AED892E836E4600E8B05D /* recursive_expander.h in Headers */,
 				CD41AFE72E7F13E4004F3E51 /* sentinel.h in Headers */,
 				CD41AFE82E7F13E4004F3E51 /* arrow_proxy.h in Headers */,
 				CD41AFE92E7F13E4004F3E51 /* macro.h in Headers */,
 				CD41AFEC2E7F13E4004F3E51 /* undef.h in Headers */,
+				CD0AED8D2E836EF200E8B05D /* capture_fn.h in Headers */,
 				CD41AFED2E7F13E4004F3E51 /* capture_iterator.h in Headers */,
 				CD41AFEE2E7F13E4004F3E51 /* end_aware_iterator.h in Headers */,
+				CD0AED842E836E1900E8B05D /* cascade_iterator.h in Headers */,
 				CD41AFEF2E7F13E4004F3E51 /* filter_iterator.h in Headers */,
+				CD0AED8B2E836E9F00E8B05D /* projection_tuple.h in Headers */,
 				CD41AFF02E7F13E4004F3E51 /* indexed_iterator.h in Headers */,
 				CD41AFF12E7F13E4004F3E51 /* join_iterator.h in Headers */,
 				CD41AFF22E7F13E4004F3E51 /* recursive_iterator.h in Headers */,
@@ -409,6 +428,7 @@
 				CDCB3BDB24E1D5320029B771 /* end_aware_iterator_test.cxx in Sources */,
 				CDCB3BD824E1D5320029B771 /* recursive_iterator_vector_test.cxx in Sources */,
 				CDCB3BDC24E1D5320029B771 /* filter_iterator_test.cxx in Sources */,
+				CD0AED8F2E83FFC400E8B05D /* cascade_iterator_test.cxx in Sources */,
 				CDCB3BDE24E1D5320029B771 /* recursive_iterator_map_test.cxx in Sources */,
 				CDCB3BDD24E1D5320029B771 /* indexed_iterator_test.cxx in Sources */,
 				CDCB3BD624E1D5320029B771 /* join_iterator_test.cxx in Sources */,

+ 72 - 0
test/cascade_iterator_test.cxx

@@ -0,0 +1,72 @@
+//
+//  cascade_iterator_test.cpp
+//  iterator-test
+//
+//  Created by Sam Jaffe on 9/24/25.
+//  Copyright © 2025 Sam Jaffe. All rights reserved.
+//
+
+#include "iterator/cascade_iterator.h"
+
+#include "ranges.h"
+#include "xcode_gtest_helper.h"
+
+using iterator::cascade_iterator;
+
+struct Baz {
+  std::vector<int> ints;
+  std::map<int, std::vector<std::string>> tags;
+};
+
+struct Bar {
+  std::vector<double> doubles;
+  std::map<int, Baz> bazes;
+};
+
+struct Foo {
+  std::map<int, std::map<std::string, int>> example;
+
+  std::vector<Bar> bars_;
+  std::vector<Bar> const & bars() const { return bars_; }
+  std::vector<Bar> bars_copy() { return bars_; }
+};
+
+TEST(CascadeIteratorTest, OneProjectorIsTwoLevels) {
+  std::vector<Foo> foos;
+  auto iter = cascade_iterator(foos, &Foo::bars);
+  testing::StaticAssertTypeEq<decltype(*iter), Bar const &>();
+}
+
+TEST(CascadeIteratorTest, TwoProjectorIsThreeLevels) {
+  std::vector<Foo> foos;
+  auto iter = cascade_iterator(foos, &Foo::bars, &Bar::bazes);
+  testing::StaticAssertTypeEq<decltype(*iter),
+                              std::tuple<int const &, Baz const &>>();
+}
+
+TEST(CascadeIteratorTest, IsRvalueSafe) {
+  std::vector<Foo> foos;
+  auto iter = cascade_iterator(foos, &Foo::bars_copy, &Bar::bazes);
+  testing::StaticAssertTypeEq<decltype(*iter),
+                              std::tuple<int const &, Baz const &>>();
+}
+
+TEST(CascadeIteratorTest, CanProjectUnboundedTail) {
+  std::vector<Foo> foos;
+  auto iter = cascade_iterator(foos, iterator::unbounded{}, &Foo::example);
+  static_assert(
+      std::same_as<decltype(*iter),
+                   std::tuple<int const &, std::string const &, int &>>);
+  testing::StaticAssertTypeEq<
+      decltype(*iter), std::tuple<int const &, std::string const &, int &>>();
+}
+
+TEST(CascadeIteratorTest, CanProjectBoundedTail) {
+  std::vector<Foo> foos;
+  auto iter = cascade_iterator(foos, iterator::bounded<3>{}, &Foo::example);
+  static_assert(
+      std::same_as<decltype(*iter),
+                   std::tuple<int const &, std::string const &, int &>>);
+  testing::StaticAssertTypeEq<
+      decltype(*iter), std::tuple<int const &, std::string const &, int &>>();
+}

+ 9 - 12
test/ranges.h

@@ -8,7 +8,11 @@
 
 #pragma once
 
+#include <ranges>
+
 namespace ranges {
+using namespace std::ranges;
+
 template <typename It, typename S, typename F>
 auto for_each(It it, S end, F consumer) {
   for (; it != end; ++it) {
@@ -22,19 +26,12 @@ template <typename It, typename S> auto distance(It it, S end) {
   for_each(it, end, [&diff](auto const &) { ++diff; });
   return diff;
 }
-
-template <template <typename...> class C, typename It, typename S>
-auto to(It it, S end) {
-  decltype(C(it, it)) rval;
-  for_each(it, end,
-           [&rval](auto const & elem) { rval.insert(rval.end(), elem); });
-  return rval;
 }
 
-template <typename C, typename It, typename S> auto to(It it, S end) {
-  C rval;
-  for_each(it, end,
-           [&rval](auto const & elem) { rval.insert(rval.end(), elem); });
-  return rval;
+namespace iterator::views {
 }
+
+namespace views {
+using namespace std::views;
+using namespace iterator::views;
 }

+ 27 - 21
test/recursive_iterator_accessors_test.cxx

@@ -12,33 +12,32 @@ using iterator::bounded;
 using iterator::end_aware_iterator;
 using iterator::recursive_iterator;
 
+using testing::ElementsAreArray;
+using testing::Not;
 using testing::StaticAssertTypeEq;
 
 TEST(RecursiveIteratorTest, DoesNotUnwrapString) {
   std::vector<std::string> obj{"A", "B", "C", "D"};
   auto rit = recursive_iterator(obj);
   StaticAssertTypeEq<decltype(rit.operator->()), std::string *>();
+
+  EXPECT_THAT(obj | views::recursive | ranges::to<std::vector>(),
+              ElementsAreArray(obj));
 }
 
 TEST(RecursiveIteratorTest, CanArrowMultiVector) {
   std::vector<std::vector<int>> obj{{{0, 1}}, {{2, 3}}};
   auto rit = recursive_iterator(obj);
   StaticAssertTypeEq<decltype(rit.operator->()), int *>();
-  EXPECT_EQ(rit.operator->(), &obj[0][0]);
+  EXPECT_THAT(rit.operator->(), &obj[0][0]);
 }
 
-// TEST(RecursiveIteratorTest, CannotArrowMap) {
-//  std::map<int, std::vector<int>> obj{{1, {{0, 1}}}, {2, {{2, 3}}}};
-//  auto rit = recursive_iterator(obj);
-//  StaticAssertTypeEq<decltype(rit.operator->()), void>();
-//}
-
 TEST(RecursiveIteratorTest, CanAccessOuterterator) {
   std::map<int, std::vector<int>> obj{{1, {{0, 1}}}, {2, {{2, 3}}}};
   auto rit = recursive_iterator(obj);
 
   end_aware_iterator<decltype(obj)::iterator> inner = rit;
-  EXPECT_EQ(&std::get<0>(*rit), &(inner->first));
+  EXPECT_THAT(&std::get<0>(*rit), &(inner->first));
 }
 
 TEST(RecursiveIteratorTest, CanAccessInnerIterator) {
@@ -46,7 +45,7 @@ TEST(RecursiveIteratorTest, CanAccessInnerIterator) {
   auto rit = recursive_iterator(obj);
 
   end_aware_iterator<std::vector<int>::iterator> inner = rit;
-  EXPECT_EQ(&std::get<1>(*rit), &*inner);
+  EXPECT_THAT(&std::get<1>(*rit), &*inner);
 }
 
 TEST(RecursiveIteratorTest, CanStdGetToAllLayersOfInternalIteration) {
@@ -55,15 +54,19 @@ TEST(RecursiveIteratorTest, CanStdGetToAllLayersOfInternalIteration) {
       {2, {{{3, 3}, {4, 4}}}}    // 1 2-element map
   };
   auto rit = recursive_iterator(obj);
+
   using mvm_iterator = std::map<int, std::vector<std::map<int, int>>>::iterator;
   StaticAssertTypeEq<decltype(std::get<0>(rit)),
                      end_aware_iterator<mvm_iterator>>();
+
   using vm_iterator = std::vector<std::map<int, int>>::iterator;
   StaticAssertTypeEq<decltype(std::get<1>(rit)),
                      end_aware_iterator<vm_iterator>>();
+
   using m_iterator = std::map<int, int>::iterator;
   StaticAssertTypeEq<decltype(std::get<2>(rit)),
                      end_aware_iterator<m_iterator>>();
+
   using tup_i_i_i = std::tuple<int const &, int const &, int &>;
   StaticAssertTypeEq<decltype(*rit), tup_i_i_i>();
 }
@@ -74,9 +77,9 @@ TEST(RecursiveIteratorTest, CanAccessInternalIteratorsWithGet) {
       {2, {{{3, 3}, {4, 4}}}}    // 1 2-element map
   };
   auto rit = recursive_iterator(obj);
-  EXPECT_EQ(std::get<0>(rit), end_aware_iterator(obj));
-  EXPECT_EQ(std::get<1>(rit), end_aware_iterator(obj[1]));
-  EXPECT_EQ(std::get<2>(rit), end_aware_iterator(obj[1][0]));
+  EXPECT_THAT(std::get<0>(rit), end_aware_iterator(obj));
+  EXPECT_THAT(std::get<1>(rit), end_aware_iterator(obj[1]));
+  EXPECT_THAT(std::get<2>(rit), end_aware_iterator(obj[1][0]));
 }
 
 // TODO: This ought to be implemented as a compiles-test
@@ -96,11 +99,11 @@ TEST(RecursiveIteratorTest, EmptyCtorIsEnd) {
   };
   auto rit = recursive_iterator(obj);
 
-  EXPECT_NE(rit, iterator::sentinel);
-  EXPECT_EQ(ranges::distance(rit, iterator::sentinel), 4);
+  EXPECT_THAT(rit, Not(iterator::sentinel));
+  EXPECT_THAT(ranges::distance(rit, iterator::sentinel), 4);
 
   std::advance(rit, 4);
-  EXPECT_EQ(rit, iterator::sentinel);
+  EXPECT_THAT(rit, iterator::sentinel);
 }
 
 TEST(BoundedRecursiveIteratorTest, CanStdGetToNLayersOfInternalIteration) {
@@ -109,12 +112,15 @@ TEST(BoundedRecursiveIteratorTest, CanStdGetToNLayersOfInternalIteration) {
       {2, {{{3, 3}, {4, 4}}}}    // 1 2-element map
   };
   auto rit = recursive_iterator(obj, bounded<2>{});
+
   using mvm_iterator = std::map<int, std::vector<std::map<int, int>>>::iterator;
   StaticAssertTypeEq<decltype(std::get<0>(rit)),
                      end_aware_iterator<mvm_iterator>>();
+
   using vm_iterator = std::vector<std::map<int, int>>::iterator;
   StaticAssertTypeEq<decltype(std::get<1>(rit)),
                      end_aware_iterator<vm_iterator>>();
+
   using tup_i_mii = std::tuple<int const &, std::map<int, int> &>;
   StaticAssertTypeEq<decltype(*rit), tup_i_mii>();
 }
@@ -125,8 +131,8 @@ TEST(BoundedRecursiveIteratorTest, CanAccessInternalIteratorsWithGet) {
       {2, {{{3, 3}, {4, 4}}}}    // 1 2-element map
   };
   auto rit = recursive_iterator(obj, bounded<2>{});
-  EXPECT_EQ(std::get<0>(rit), end_aware_iterator(obj));
-  EXPECT_EQ(std::get<1>(rit), end_aware_iterator(obj[1]));
+  EXPECT_THAT(std::get<0>(rit), end_aware_iterator(obj));
+  EXPECT_THAT(std::get<1>(rit), end_aware_iterator(obj[1]));
 }
 
 // TODO: This ought to be implemented as a compiles-test
@@ -145,11 +151,11 @@ TEST(BoundedRecursiveIteratorTest, EmptyCtorIsEnd) {
       {2, {{{3, 3}, {4, 4}}}}    // 1 2-element map
   };
   auto rit = recursive_iterator(obj, bounded<3>{});
-  EXPECT_NE(rit, iterator::sentinel);
-  EXPECT_EQ(ranges::distance(rit, iterator::sentinel), 4);
+  EXPECT_THAT(rit, Not(iterator::sentinel));
+  EXPECT_THAT(ranges::distance(rit, iterator::sentinel), 4);
 
   std::advance(rit, 4);
-  EXPECT_EQ(rit, iterator::sentinel);
+  EXPECT_THAT(rit, iterator::sentinel);
 }
 
 TEST(BoundedRecursiveIteratorTest, CanFetchInnerCollections) {
@@ -158,5 +164,5 @@ TEST(BoundedRecursiveIteratorTest, CanFetchInnerCollections) {
       {{{{3, 3}, {4, 4}}}}    // 1 2-element map
   };
   auto rit = recursive_iterator(obj, bounded<2>{});
-  EXPECT_EQ(*rit, obj[0][0]);
+  EXPECT_THAT(*rit, obj[0][0]);
 }

+ 20 - 22
test/recursive_iterator_map_test.cxx

@@ -13,24 +13,25 @@
 using iterator::bounded;
 using iterator::recursive_iterator;
 
+using testing::ElementsAreArray;
 using testing::IsEmpty;
 
 TEST(RecursiveIteratorMapTest, PreIncrementAdvancesIterator) {
   std::map<int, std::map<int, std::map<int, int>>> const map{
       {1, {{1, {{1, 1}}}}}, {2, {{2, {{2, 2}}}, {3, {{3, 3}, {4, 4}}}}}};
   auto rit = recursive_iterator(map);
-  EXPECT_EQ(std::get<3>(*rit), 1);
-  EXPECT_EQ(std::get<3>(*++rit), 2);
-  EXPECT_EQ(std::get<3>(*rit), 2);
+  EXPECT_THAT(std::get<3>(*rit), 1);
+  EXPECT_THAT(std::get<3>(*++rit), 2);
+  EXPECT_THAT(std::get<3>(*rit), 2);
 }
 
 TEST(RecursiveIteratorMapTest, PostIncrementReturnsCopyOfPrev) {
   std::map<int, std::map<int, std::map<int, int>>> const map{
       {1, {{1, {{1, 1}}}}}, {2, {{2, {{2, 2}}}, {3, {{3, 3}, {4, 4}}}}}};
   auto rit = recursive_iterator(map);
-  EXPECT_EQ(std::get<3>(*rit), 1);
-  EXPECT_EQ(std::get<3>(*rit++), 1);
-  EXPECT_EQ(std::get<3>(*rit), 2);
+  EXPECT_THAT(std::get<3>(*rit), 1);
+  EXPECT_THAT(std::get<3>(*rit++), 1);
+  EXPECT_THAT(std::get<3>(*rit), 2);
 }
 
 TEST(RecursiveIteratorMapTest, IterDistanceIsSumOfInnerContainerSizes) {
@@ -38,7 +39,7 @@ TEST(RecursiveIteratorMapTest, IterDistanceIsSumOfInnerContainerSizes) {
       {1, {{1, {{1, 1}}}}}, {2, {{2, {{2, 2}}}, {3, {{3, 3}, {4, 4}}}}}};
   auto rit = recursive_iterator(map);
 
-  EXPECT_EQ(ranges::distance(rit, iterator::sentinel), 4);
+  EXPECT_THAT(ranges::distance(rit, iterator::sentinel), 4);
 }
 
 TEST(RecursiveIteratorMapTest, ElementsAreUnwrappedAsATuple) {
@@ -46,11 +47,9 @@ TEST(RecursiveIteratorMapTest, ElementsAreUnwrappedAsATuple) {
       {1, {{1, {{1, 1}}}}}, {2, {{2, {{2, 2}}}, {3, {{3, 3}, {4, 4}}}}}};
   std::vector<std::tuple<int, int, int, int>> const expected{
       {1, 1, 1, 1}, {2, 2, 2, 2}, {2, 3, 3, 3}, {2, 3, 4, 4}};
-  auto rit = recursive_iterator(map);
 
-  EXPECT_EQ((ranges::to<std::vector<std::tuple<int, int, int, int>>>(
-                rit, iterator::sentinel)),
-            expected);
+  EXPECT_THAT(map | views::recursive | ranges::to<std::vector>(),
+              ElementsAreArray(expected));
 }
 
 TEST(RecursiveIteratorMapTest, CanMutatePointedToData) {
@@ -58,25 +57,25 @@ TEST(RecursiveIteratorMapTest, CanMutatePointedToData) {
       {1, {{1, {{1, 1}}}}}, {2, {{2, {{2, 2}}}, {3, {{3, 3}, {4, 4}}}}}};
   auto rit = recursive_iterator(map);
   std::get<3>(*rit) = 4;
-  EXPECT_EQ(map[1][1][1], 4);
+  EXPECT_THAT(map[1][1][1], 4);
 }
 
 TEST(BoundRecursiveIteratorMapTest, PreIncrementAdvancesIterator) {
   std::map<int, std::map<int, std::map<int, int>>> map{
       {1, {{1, {{1, 1}}}}}, {2, {{2, {{2, 2}}}, {3, {{3, 3}, {4, 4}}}}}};
   auto rit = recursive_iterator(map, bounded<2>{});
-  EXPECT_EQ(std::get<2>(*rit), map[1][1]);
-  EXPECT_EQ(std::get<2>(*++rit), map[2][2]);
-  EXPECT_EQ(std::get<2>(*rit), map[2][2]);
+  EXPECT_THAT(std::get<2>(*rit), map[1][1]);
+  EXPECT_THAT(std::get<2>(*++rit), map[2][2]);
+  EXPECT_THAT(std::get<2>(*rit), map[2][2]);
 }
 
 TEST(BoundRecursiveIteratorMapTest, PostIncrementReturnsCopyOfPrev) {
   std::map<int, std::map<int, std::map<int, int>>> map{
       {1, {{1, {{1, 1}}}}}, {2, {{2, {{2, 2}}}, {3, {{3, 3}, {4, 4}}}}}};
   auto rit = recursive_iterator(map, bounded<2>{});
-  EXPECT_EQ(std::get<2>(*rit), map[1][1]);
-  EXPECT_EQ(std::get<2>(*rit++), map[1][1]);
-  EXPECT_EQ(std::get<2>(*rit), map[2][2]);
+  EXPECT_THAT(std::get<2>(*rit), map[1][1]);
+  EXPECT_THAT(std::get<2>(*rit++), map[1][1]);
+  EXPECT_THAT(std::get<2>(*rit), map[2][2]);
 }
 
 TEST(BoundRecursiveIteratorMapTest, IterDistanceSumOnNLayersSize) {
@@ -84,7 +83,7 @@ TEST(BoundRecursiveIteratorMapTest, IterDistanceSumOnNLayersSize) {
       {1, {{1, {{1, 1}}}}}, {2, {{2, {{2, 2}}}, {3, {{3, 3}, {4, 4}}}}}};
   auto rit = recursive_iterator(map, bounded<2>{});
 
-  EXPECT_EQ(ranges::distance(rit, iterator::sentinel), 3);
+  EXPECT_THAT(ranges::distance(rit, iterator::sentinel), 3);
 }
 
 TEST(BoundRecursiveIteratorMapTest, ElementsAreUnwrappedAsATuple) {
@@ -93,9 +92,8 @@ TEST(BoundRecursiveIteratorMapTest, ElementsAreUnwrappedAsATuple) {
   std::vector<std::tuple<int, int, std::map<int, int>>> const expected{
       {1, 1, {{1, 1}}}, {2, 2, {{2, 2}}}, {2, 3, {{3, 3}, {4, 4}}}};
 
-  EXPECT_THAT(map | iterator::views::recursive_n<2> |
-                  std::ranges::to<std::vector>(),
-              testing::ElementsAreArray(expected));
+  EXPECT_THAT(map | views::recursive_n<2> | ranges::to<std::vector>(),
+              ElementsAreArray(expected));
 }
 
 TEST(BoundedRecursiveIteratorMapTest, CanMutatePointedToData) {

+ 20 - 24
test/recursive_iterator_mixed_container_test.cxx

@@ -8,29 +8,29 @@
 
 using iterator::recursive_iterator;
 
+using testing::ElementsAreArray;
+
 TEST(RecursiveIteratorMapVectorTest, IterDistanceIsSumOfInnerContainerSizes) {
   std::map<int, std::vector<int>> const obj{{1, {1, 2}}, {2, {3, 4, 5}}};
   auto rit = recursive_iterator(obj);
 
-  EXPECT_EQ(ranges::distance(rit, iterator::sentinel), 5);
+  EXPECT_THAT(ranges::distance(rit, iterator::sentinel), 5);
 }
 
 TEST(RecursiveIteratorMapVectorTest, ElementsAreUnwrappedAsATuple) {
   std::map<int, std::vector<int>> const obj{{1, {1, 2}}, {2, {3, 4, 5}}};
   std::vector<std::tuple<int, int>> const expected{
       {1, 1}, {1, 2}, {2, 3}, {2, 4}, {2, 5}};
-  auto rit = recursive_iterator(obj);
 
-  EXPECT_EQ(
-      (ranges::to<std::vector<std::tuple<int, int>>>(rit, iterator::sentinel)),
-      expected);
+  EXPECT_THAT(obj | views::recursive | ranges::to<std::vector>(),
+              ElementsAreArray(expected));
 }
 
 TEST(RecursiveIteratorMapVectorTest, CanMutatePointedToData) {
   std::map<int, std::vector<int>> obj{{1, {1, 2}}, {2, {3, 4, 5}}};
   auto rit = recursive_iterator(obj);
   std::get<1>(*rit) = 6;
-  EXPECT_EQ(obj[1][0], 6);
+  EXPECT_THAT(obj[1][0], 6);
 }
 
 TEST(RecursiveIteratorMapMapVectorTest, CanMutatePointedToData) {
@@ -38,7 +38,7 @@ TEST(RecursiveIteratorMapMapVectorTest, CanMutatePointedToData) {
                                                      {2, {{1, {3, 4, 5}}}}};
   auto rit = recursive_iterator(obj);
   std::get<2>(*rit) = 6;
-  EXPECT_EQ(obj[1][1][0], 6);
+  EXPECT_THAT(obj[1][1][0], 6);
 }
 
 TEST(RecursiveIteratorVectorMapTest, IterDistanceIsSumOfInnerContainerSizes) {
@@ -46,7 +46,7 @@ TEST(RecursiveIteratorVectorMapTest, IterDistanceIsSumOfInnerContainerSizes) {
                                             {{3, 3}, {4, 4}, {5, 5}}};
   auto rit = recursive_iterator(obj);
 
-  EXPECT_EQ(ranges::distance(rit, iterator::sentinel), 5);
+  EXPECT_THAT(ranges::distance(rit, iterator::sentinel), 5);
 }
 
 TEST(RecursiveIteratorVectorMapTest, ElementsAreUnwrappedAsATuple) {
@@ -54,11 +54,9 @@ TEST(RecursiveIteratorVectorMapTest, ElementsAreUnwrappedAsATuple) {
                                             {{3, 3}, {4, 4}, {5, 5}}};
   std::vector<std::pair<int, int>> const expected{
       {1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}};
-  auto rit = recursive_iterator(obj);
 
-  EXPECT_EQ(
-      (ranges::to<std::vector<std::pair<int, int>>>(rit, iterator::sentinel)),
-      expected);
+  EXPECT_THAT(obj | views::recursive | ranges::to<std::vector>(),
+              ElementsAreArray(expected));
 }
 
 TEST(RecursiveIteratorVectorMapTest, CanMutatePointedToData) {
@@ -66,7 +64,7 @@ TEST(RecursiveIteratorVectorMapTest, CanMutatePointedToData) {
                                       {{3, 3}, {4, 4}, {5, 5}}};
   auto rit = recursive_iterator(obj);
   std::get<1>(*rit) = 6;
-  EXPECT_EQ(obj[0][1], 6);
+  EXPECT_THAT(obj[0][1], 6);
 }
 
 TEST(RecursiveIteratorMapVecMapTest, IterDistanceIsSumOfInnerContainerSizes) {
@@ -75,7 +73,7 @@ TEST(RecursiveIteratorMapVecMapTest, IterDistanceIsSumOfInnerContainerSizes) {
   std::vector<std::tuple<int, int, int>> const expected{{1, 1, 1}, {1, 2, 2}};
   auto rit = recursive_iterator(obj);
 
-  EXPECT_EQ(ranges::distance(rit, iterator::sentinel), expected.size());
+  EXPECT_THAT(ranges::distance(rit, iterator::sentinel), expected.size());
 }
 
 TEST(RecursiveIteratorMapVecMapTest, ElementsAreUnwrappedAsATuple) {
@@ -84,16 +82,15 @@ TEST(RecursiveIteratorMapVecMapTest, ElementsAreUnwrappedAsATuple) {
   std::vector<std::tuple<int, int, int>> const expected{{1, 1, 1}, {1, 2, 2}};
   auto rit = recursive_iterator(obj);
 
-  EXPECT_EQ((ranges::to<std::vector<std::tuple<int, int, int>>>(
-                rit, iterator::sentinel)),
-            expected);
+  EXPECT_THAT(obj | views::recursive | ranges::to<std::vector>(),
+              ElementsAreArray(expected));
 }
 
 TEST(RecursiveIteratorMapVecMapTest, CanMutatePointedToData) {
   std::map<int, std::vector<std::map<int, int>>> obj{{1, {{{1, 1}, {2, 2}}}}};
   auto rit = recursive_iterator(obj);
   std::get<2>(*rit) = 4;
-  EXPECT_EQ(obj[1][0][1], 4);
+  EXPECT_THAT(obj[1][0][1], 4);
 }
 
 TEST(RecursiveIteratorVecMapVecTest, IterDistanceIsSumOfInnerContainerSizes) {
@@ -101,7 +98,7 @@ TEST(RecursiveIteratorVecMapVecTest, IterDistanceIsSumOfInnerContainerSizes) {
       {{1, {1, 2}}, {2, {3, 4, 5}}}, {{1, {3, 4}}}};
   auto rit = recursive_iterator(obj);
 
-  EXPECT_EQ(ranges::distance(rit, iterator::sentinel), 7);
+  EXPECT_THAT(ranges::distance(rit, iterator::sentinel), 7);
 }
 
 TEST(RecursiveIteratorVecMapVecTest, ElementsAreUnwrappedAsATuple) {
@@ -109,11 +106,9 @@ TEST(RecursiveIteratorVecMapVecTest, ElementsAreUnwrappedAsATuple) {
       {{1, {1, 2}}, {2, {3, 4, 5}}}, {{1, {3, 4}}}};
   std::vector<std::tuple<int, int>> const expected{
       {1, 1}, {1, 2}, {2, 3}, {2, 4}, {2, 5}, {1, 3}, {1, 4}};
-  auto rit = recursive_iterator(obj);
 
-  EXPECT_EQ(
-      (ranges::to<std::vector<std::tuple<int, int>>>(rit, iterator::sentinel)),
-      expected);
+  EXPECT_THAT(obj | views::recursive | ranges::to<std::vector>(),
+              ElementsAreArray(expected));
 }
 
 TEST(RecursiveIteratorVecMapVecTest, CanMutatePointedToData) {
@@ -121,5 +116,6 @@ TEST(RecursiveIteratorVecMapVecTest, CanMutatePointedToData) {
       {{1, {1, 2}}, {2, {3, 4, 5}}}, {{1, {3, 4}}}};
   auto rit = recursive_iterator(obj);
   std::get<1>(*rit) = 6;
-  EXPECT_EQ(obj[0][1][0], 6);
+
+  EXPECT_THAT(obj[0][1][0], 6);
 }

+ 5 - 4
test/recursive_iterator_single_level_test.cxx

@@ -16,6 +16,8 @@
 
 using iterator::recursive_iterator;
 
+using testing::ElementsAreArray;
+
 TEST(RecursiveIteratorSingleVectorTest, IterDistanceIsContainerSize) {
   std::vector<int> const vec{1, 2, 3, 4, 5};
   auto rit = recursive_iterator(vec);
@@ -25,9 +27,9 @@ TEST(RecursiveIteratorSingleVectorTest, IterDistanceIsContainerSize) {
 
 TEST(RecursiveIteratorSingleVectorTest, DataMatchesContainerIterator) {
   std::vector<int> const vec{1, 2, 3, 4, 5};
-  auto rit = recursive_iterator(vec);
 
-  EXPECT_THAT(ranges::to<std::vector<int>>(rit, iterator::sentinel), vec);
+  EXPECT_THAT(vec | views::recursive | ranges::to<std::vector>(),
+              ElementsAreArray(vec));
 }
 
 TEST(RecursiveIteratorSingleVectorTest, CanMutatePointedToData) {
@@ -46,9 +48,8 @@ TEST(RecursiveIteratorSingleMapTest, IterDistanceIsContainerSize) {
 
 TEST(RecursiveIteratorSingleMapTest, DataMatchesContainerIterator) {
   std::map<int, int> const map{{1, 1}, {2, 2}, {3, 3}};
-  auto rit = recursive_iterator(map);
 
-  EXPECT_THAT((ranges::to<std::map<int, int>>(rit, iterator::sentinel)), map);
+  EXPECT_THAT((map | views::recursive | ranges::to<std::map<int, int>>()), map);
 }
 
 TEST(RecursiveIteratorSingleMapTest, CanMutatePointedToData) {

+ 5 - 6
test/recursive_iterator_vector_test.cxx

@@ -10,6 +10,7 @@
 using iterator::bounded;
 using iterator::recursive_iterator;
 
+using testing::ElementsAreArray;
 using testing::IsEmpty;
 
 TEST(RecursiveIteratorVecTest, PreIncrementAdvancesIterator) {
@@ -37,10 +38,10 @@ TEST(RecursiveIteratorVecTest, IterDistanceIsSumOfInnerContainerSizes) {
 
 TEST(RecursiveIteratorVecTest, FlattensVectorDataLikeJoinIterator) {
   std::vector<std::vector<std::vector<int>>> const vec{{{1, 2}}, {{3}, {4, 5}}};
-  auto rit = recursive_iterator(vec);
 
   std::vector<int> const expected{1, 2, 3, 4, 5};
-  EXPECT_THAT(ranges::to<std::vector<int>>(rit, iterator::sentinel), expected);
+  EXPECT_THAT(vec | views::recursive | ranges::to<std::vector>(),
+              ElementsAreArray(expected));
 }
 
 TEST(RecursiveIteratorVecTest, CanMutatePointedToData) {
@@ -75,12 +76,10 @@ TEST(BoundedRecursiveIteratorVecTest, IterDistanceSumOnNLayersSize) {
 
 TEST(BoundedRecursiveIteratorVecTest, ElementsAreUnwrappedAsATuple) {
   std::vector<std::vector<std::vector<int>>> const vec{{{1, 2}}, {{3}, {4, 5}}};
-  auto rit = recursive_iterator(vec, bounded<2>{});
 
   std::vector<std::vector<int>> const expected{{1, 2}, {3}, {4, 5}};
-  EXPECT_THAT(
-      ranges::to<std::vector<std::vector<int>>>(rit, iterator::sentinel),
-      expected);
+  EXPECT_THAT(vec | views::recursive_n<2> | ranges::to<std::vector>(),
+              ElementsAreArray(expected));
 }
 
 TEST(BoundedRecursiveIteratorVecTest, CanMutatePointedToData) {