Bläddra i källkod

feat: implement cascade_iterator

Sam Jaffe 2 månader sedan
förälder
incheckning
ed3570273a

+ 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>;
+}

+ 4 - 1
include/iterator/detail/projection_tuple.h

@@ -21,7 +21,10 @@ private:
   std::tuple<Projs...> fns_;
 
 public:
-  Projections(Projs... fns) : fns_(fns...) {}
+  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>

+ 23 - 14
include/iterator/recursive_iterator.h

@@ -21,10 +21,12 @@
 #include <iterator/detail/macro.h>
 
 namespace iterator {
-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>
@@ -58,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() {
@@ -76,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);
@@ -91,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>{}));
+      }
     }
   }
 
@@ -99,7 +108,7 @@ 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));
     }
   }
 };
@@ -116,7 +125,7 @@ struct recursive_iterator_helper {
   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>;
 };
 
 /**
@@ -162,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>
@@ -170,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>
@@ -190,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>

+ 8 - 0
iterator.xcodeproj/project.pbxproj

@@ -7,9 +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 */; };
@@ -81,9 +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>"; };
@@ -168,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 */,
@@ -201,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 */,
@@ -271,6 +277,7 @@
 				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 */,
@@ -421,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 &>>();
+}