فهرست منبع

Merge branch 'refactor/concepts'

* refactor/concepts:
  refactor: move tuple_cat_t to concepts.h
  refactor: loosen some concepts
  refactor: allow turning an end_aware_iterator into a range
  refactor: rebuild recursive_iterator and traits
  refactor: rename iterator::recursive::{tuple => tuple_expander}
  chore: cleanup
  fix: at_end checks when possible chore: remove commented out code
  chore: clang-format
  refactor: eliminate traits.h in favor of concepts
  refactor: convert facade et al to use concepts
Sam Jaffe 2 ماه پیش
والد
کامیت
8d8217a15b

+ 9 - 5
include/iterator/capture_iterator.h

@@ -31,19 +31,23 @@ public:
 
   value_type const & dereference() const { return cache_; }
 
-  SFINAE(super_t::category_enum >= category::forward) void increment() {
+  void increment()
+    requires(forward<super_t>)
+  {
     super_t::increment();
     cache_ = super_t::dereference();
   }
 
-  SFINAE(super_t::category_enum >= category::bidirectional)
-  void decrement() {
+  void decrement()
+    requires(bidirectional<super_t>)
+  {
     super_t::decrement();
     cache_ = super_t::dereference();
   }
 
-  SFINAE(super_t::category_enum >= category::random_access)
-  void advance(difference_type off) {
+  void advance(difference_type off)
+    requires(random_access<super_t>)
+  {
     super_t::advance(off);
     cache_ = super_t::dereference();
   }

+ 123 - 0
include/iterator/concepts.h

@@ -0,0 +1,123 @@
+//
+//  concepts.h
+//  iterator
+//
+//  Created by Sam Jaffe on 9/20/25.
+//  Copyright © 2025 Sam Jaffe. All rights reserved.
+//
+
+#pragma once
+
+#include <concepts>
+#include <ranges>
+#include <string_view>
+#include <tuple>
+#include <type_traits>
+
+namespace iterator {
+template <typename It>
+concept implements_distance_to = requires(It const & it) {
+  { it.distance_to(it) } -> std::integral;
+};
+
+template <typename> struct infer_difference_type {
+  using type = std::ptrdiff_t;
+};
+
+template <implements_distance_to It> struct infer_difference_type<It> {
+  static const It & _it;
+  using type = decltype(_it.distance_to(_it));
+};
+
+template <typename It>
+using infer_difference_type_t = typename infer_difference_type<It>::type;
+
+template <typename D, typename It>
+concept difference_type_arg =
+    std::convertible_to<D, infer_difference_type_t<It>>;
+
+template <typename It> struct infer_value_type {
+  static const It & _it;
+  using type = std::remove_cvref_t<decltype(*_it)>;
+};
+
+template <typename It>
+  requires requires { typename It::value_type; }
+struct infer_value_type<It> {
+  using type = typename It::value_type;
+};
+
+template <typename It> using infer_value_type_t = infer_value_type<It>::type;
+
+template <typename T, typename U>
+concept not_same_as = not std::same_as<T, U>;
+
+template <typename It>
+concept has_sentinel = requires(It const & it) {
+  typename It::sentinel_type;
+  { it.at_end() } -> std::same_as<bool>;
+};
+
+template <typename It, typename S>
+concept sentinel_for = has_sentinel<It> && requires(It const & it, S s) {
+  { s - it } -> std::integral;
+};
+
+template <typename It>
+concept single_pass = bool(It::single_pass_iterator);
+
+template <typename It>
+concept forward = requires(It & it) {
+  { it.dereference() } -> not_same_as<void>;
+  { it.equal_to(it) } -> std::same_as<bool>;
+  { it.increment() } -> std::same_as<void>;
+};
+
+template <typename It>
+concept bidirectional = forward<It> && requires(It & it) {
+  { it.decrement() } -> std::same_as<void>;
+};
+
+template <typename It>
+concept random_access = requires(It & it, infer_difference_type_t<It> offset) {
+  { it.dereference() } -> not_same_as<void>;
+  { it.equal_to(it) } -> std::same_as<bool>;
+  { it.advance(offset) } -> std::same_as<void>;
+};
+
+template <typename C>
+concept Char = std::same_as<C, char> || std::same_as<C, wchar_t> ||
+               std::same_as<C, char16_t> || std::same_as<C, char32_t> ||
+               std::same_as<C, char8_t>;
+
+template <typename S>
+concept LegacyString = requires(S const & t) {
+  { t.c_str() };
+};
+
+template <typename S>
+concept StringViewCompat =
+    Char<typename S::value_type> && requires(S const & t) {
+      { std::basic_string_view{t} };
+    };
+
+template <typename S>
+concept String = LegacyString<S> || StringViewCompat<S>;
+
+template <typename C>
+concept Range = (not String<C>) && std::ranges::range<C>;
+
+template <typename V>
+concept Assoc =
+    std::is_const_v<std::remove_reference_t<std::tuple_element_t<0, V>>>;
+
+template <typename V>
+concept AssocRange = Assoc<V> && (std::tuple_size_v<V> == 2) &&
+                     Range<std::decay_t<std::tuple_element_t<1, V>>>;
+
+template <typename C>
+using iterator_t = decltype(std::begin(std::declval<C &>()));
+
+template <typename... Ts>
+using tuple_cat_t = decltype(std::tuple_cat(std::declval<Ts>()...));
+}

+ 0 - 51
include/iterator/detail/recursive_traits.h

@@ -1,51 +0,0 @@
-#pragma once
-
-#include <iterator>
-
-#include <iterator/detail/traits.h>
-
-namespace iterator::recursive {
-// Type deduction guides for constructing recursive iterators.
-enum class recursion_type { END, THRU, ASSOC };
-
-// Helpers for condensing type deductions
-template <typename It> using value = decltype(std::begin(*std::declval<It>()));
-
-template <typename It>
-using mapped = decltype(std::begin(std::declval<It>()->second));
-
-template <typename It> using key_type = decltype(std::declval<It>()->first);
-
-// Type trait to identify value_type ~~ std::pair<K const, V>, which is
-// a safe bet towards 'this is an associative container type'
-template <typename T, typename = void>
-struct is_associative : std::false_type {};
-template <typename T>
-struct is_associative<T, std::enable_if_t<std::is_const_v<key_type<T>>>>
-    : std::true_type {};
-
-template <typename T, typename = void> struct typeclass_t {
-  static constexpr recursion_type const value{recursion_type::END};
-};
-
-template <typename T> struct is_string_iter : std::false_type {};
-template <> struct is_string_iter<std::string::iterator> : std::true_type {};
-template <>
-struct is_string_iter<std::string::const_iterator> : std::true_type {};
-
-template <typename T> constexpr bool is_string_iter_v = is_string_iter<T>{};
-
-template <typename T>
-struct typeclass_t<T, std::enable_if_t<!is_string_iter_v<value<T>>>> {
-  constexpr static recursion_type value{recursion_type::THRU};
-};
-
-template <typename T>
-struct typeclass_t<T, std::enable_if_t<is_string_iter_v<value<T>>>> {
-  constexpr static recursion_type value{recursion_type::END};
-};
-
-template <typename T> struct typeclass_t<T, std::void_t<mapped<T>>> {
-  constexpr static recursion_type value{recursion_type::ASSOC};
-};
-}

+ 0 - 96
include/iterator/detail/traits.h

@@ -1,96 +0,0 @@
-#pragma once
-
-#include <algorithm>
-#include <iterator>
-#include <type_traits>
-
-#include <iterator/forwards.h>
-
-#include <iterator/detail/macro.h>
-
-// Section: Non-Dependant Typedef Helpers
-namespace iterator::detail {
-template <typename Iter>
-using category_t = typename std::iterator_traits<Iter>::iterator_category;
-template <typename C> using iter = decltype(std::begin(std::declval<C>()));
-}
-
-namespace iterator::detail {
-// Type Helper for identifying container-like objects
-template <typename C, typename = void> struct is_container : std::false_type {};
-template <typename C>
-struct is_container<C, std::void_t<iter<C>>> : std::true_type {};
-
-template <typename It, typename = void>
-struct sentinel_type {
-  using type = void;
-};
-template <typename It>
-struct sentinel_type<It, std::void_t<typename It::sentinel_type>> {
-  using type = typename It::sentinel_type;
-};
-}
-
-// Mappings between iterator::category enum and iterator_category tags
-namespace iterator::detail {
-template <typename Tag, category Limit = static_cast<category>(~0)>
-struct category_for {
-  constexpr static category value = category::single_pass;
-};
-
-template <category Limit>
-struct category_for<std::forward_iterator_tag, Limit> {
-  constexpr static category value = std::min(category::forward, Limit);
-};
-
-template <category Limit>
-struct category_for<std::bidirectional_iterator_tag, Limit> {
-  constexpr static category value = std::min(category::bidirectional, Limit);
-};
-
-template <category Limit>
-struct category_for<std::random_access_iterator_tag, Limit> {
-  constexpr static category value = std::min(category::random_access, Limit);
-};
-
-template <category> struct tag_for;
-template <> struct tag_for<category::single_pass> {
-  using type = std::input_iterator_tag;
-};
-
-template <category> struct tag_for;
-template <> struct tag_for<category::forward> {
-  using type = std::forward_iterator_tag;
-};
-
-template <category> struct tag_for;
-template <> struct tag_for<category::bidirectional> {
-  using type = std::bidirectional_iterator_tag;
-};
-
-template <category> struct tag_for;
-template <> struct tag_for<category::random_access> {
-  using type = std::random_access_iterator_tag;
-};
-}
-
-namespace iterator::detail {
-template <typename C> constexpr bool is_container_v = is_container<C>{};
-
-template <typename Iter>
-constexpr bool is_rvalue_iterator_v = !std::is_reference_v<DEREF_TYPE(Iter)>;
-
-template <typename It>
-constexpr bool has_sentinel_type_v = !std::is_void_v<typename sentinel_type<It>::type>;
-
-template <typename It, typename S>
-constexpr bool is_sentinel_v = std::is_same_v<typename sentinel_type<It>::type, S>;
-
-template <typename It, category Limit = static_cast<category>(~0)>
-constexpr auto category_for_v = category_for<category_t<It>, Limit>::value;
-
-template <typename It>
-using tag_for_t = typename tag_for<It::category_enum>::type;
-}
-
-#include <iterator/detail/undef.h>

+ 13 - 14
include/iterator/end_aware_iterator.h

@@ -6,8 +6,8 @@
 //
 
 #pragma once
+#include <ranges>
 
-#include <iterator/detail/traits.h>
 #include <iterator/forwards.h>
 #include <iterator/proxy.h>
 #include <iterator/sentinel.h>
@@ -31,27 +31,28 @@ public:
   end_aware_iterator() = default;
   end_aware_iterator(It it, It end) : super_t(it), end_(end) {}
 
-  template <typename C, REQUIRES(detail::is_container_v<C>)>
-  end_aware_iterator(C && container)
-      : super_t(std::begin(container)), end_(std::end(container)) {
-    static_assert(std::is_reference_v<C>,
-                  "Cannot access iterator of a temporary");
-  }
+  end_aware_iterator(Range auto & container)
+      : super_t(std::begin(container)), end_(std::end(container)) {}
 
   template <typename Ot>
   end_aware_iterator(end_aware_iterator<Ot> const & other)
       : super_t(other.impl()), end_(other.end_) {}
 
+  operator std::ranges::subrange<It>() const {
+    return {super_t::impl(), end()};
+  }
+
   bool at_end() const {
-    if constexpr (super_t::category_enum == category::random_access) {
+    if constexpr (std::random_access_iterator<It>) {
       return super_t::impl() >= end_;
     } else {
       return super_t::impl() == end_;
     }
   }
-  
-  SFINAE(super_t::category_enum == category::random_access)
-  friend auto operator-(sentinel_type, end_aware_iterator const &self) {
+
+  friend auto operator-(sentinel_type, end_aware_iterator const & self)
+    requires(std::random_access_iterator<It>)
+  {
     return self.end() - self.impl();
   }
 
@@ -64,13 +65,11 @@ private:
 };
 
 template <typename C>
-end_aware_iterator(C &&) -> end_aware_iterator<detail::iter<C>>;
+end_aware_iterator(C &&) -> end_aware_iterator<iterator_t<C>>;
 template <typename It> end_aware_iterator(It, It) -> end_aware_iterator<It>;
 template <typename It>
 end_aware_iterator(end_aware_iterator<It>, end_aware_iterator<It>)
     -> end_aware_iterator<It>;
 }
 
-MAKE_ITERATOR_FACADE_TYPEDEFS_T(::iterator::end_aware_iterator);
-
 #include <iterator/detail/undef.h>

+ 49 - 80
include/iterator/facade.h

@@ -3,36 +3,16 @@
 #include <iterator>
 #include <type_traits>
 
+#include <iterator/concepts.h>
 #include <iterator/detail/arrow_proxy.h>
-#include <iterator/detail/traits.h>
 
 #include <iterator/detail/macro.h>
 
-namespace iterator::detail {
-template <typename, typename = void> struct distance_to {
-  using type = std::ptrdiff_t;
-};
-
-template <typename T>
-struct distance_to<T, EXISTS(VAL(T).distance_to(VAL(T)))> {
-  using type = decltype(VAL(T).distance_to(VAL(T)));
-};
-
-template <typename T> using distance_to_t = typename distance_to<T>::type;
-}
-
 namespace iterator {
-template <typename D, typename T>
-using difference_type_arg_t =
-    std::enable_if_t<std::is_convertible_v<D, detail::distance_to_t<T>>>;
-
-template <typename CRTP, category C> class facade {
+template <typename CRTP> class facade {
 private:
   using self_type = CRTP;
 
-public:
-  constexpr static auto category_enum = C;
-
 public:
   decltype(auto) operator*() const { return self().dereference(); }
 
@@ -44,23 +24,25 @@ public:
     }
   }
 
-  template <typename D, typename = difference_type_arg_t<D, self_type>,
-            REQUIRES(DEFER(D) && C == category::random_access)>
+  template <typename D>
+    requires(difference_type_arg<D, self_type> && random_access<self_type>)
   decltype(auto) operator[](D off) const {
     return *(self() + off);
   }
 
   self_type & operator++() {
-    if constexpr (C == category::random_access) {
-      self() += 1;
-    } else {
+    if constexpr (forward<self_type>) {
       self().increment();
+    } else {
+      static_assert(random_access<self_type>,
+                    "requires .increment() or .advance()");
+      self() += 1;
     }
     return self();
   }
 
   auto operator++(int) {
-    if constexpr (C == category::single_pass) {
+    if constexpr (single_pass<self_type>) {
       ++*this;
     } else {
       auto tmp = self();
@@ -69,90 +51,73 @@ public:
     }
   }
 
-  SFINAE(C >= category::bidirectional)
   self_type & operator--() {
-    if constexpr (C == category::random_access) {
-      self() -= 1;
-    } else {
+    if constexpr (bidirectional<self_type>) {
       self().decrement();
+    } else {
+      static_assert(random_access<self_type>,
+                    "requires .decrement() or .advance()");
+      self() -= 1;
     }
     return self();
   }
 
-  SFINAE(C >= category::bidirectional)
   self_type operator--(int) {
     auto tmp = self();
     --*this;
     return tmp;
   }
 
-  template <typename D, typename = difference_type_arg_t<D, self_type>,
-            REQUIRES(DEFER(D) && C == category::random_access)>
+  template <typename D>
+    requires(difference_type_arg<D, self_type> && random_access<self_type>)
   friend self_type & operator+=(self_type & self, D off) {
     self.advance(off);
     return self;
   }
 
-  template <typename D, typename = difference_type_arg_t<D, self_type>,
-            REQUIRES(DEFER(D) && C == category::random_access)>
+  template <typename D>
+    requires(difference_type_arg<D, self_type> && random_access<self_type>)
   friend self_type & operator-=(self_type & self, D off) {
     self.advance(-off);
     return self;
   }
 
-  template <typename D, typename = difference_type_arg_t<D, self_type>,
-            REQUIRES(DEFER(D) && C == category::random_access)>
+  template <typename D>
+    requires(difference_type_arg<D, self_type> && random_access<self_type>)
   friend auto operator+(self_type self, D off) {
     return self += off;
   }
 
-  template <typename D, typename = difference_type_arg_t<D, self_type>,
-            REQUIRES(DEFER(D) && C == category::random_access)>
+  template <typename D>
+    requires(difference_type_arg<D, self_type> && random_access<self_type>)
   friend auto operator+(D off, self_type self) {
     return self += off;
   }
 
-  template <typename D, typename = difference_type_arg_t<D, self_type>,
-            REQUIRES(DEFER(D) && C == category::random_access)>
+  template <typename D>
+    requires(difference_type_arg<D, self_type> && random_access<self_type>)
   friend auto operator-(self_type self, D off) {
     return self -= off;
   }
 
-  SFINAE(C == category::random_access)
-  friend auto operator-(self_type const & left, self_type const & right) {
+  friend auto operator-(self_type const & left, self_type const & right)
+    requires(random_access<self_type>)
+  {
     return right.distance_to(left);
   }
 
   friend bool operator==(self_type const & left, self_type const & right) {
-    if constexpr (C == category::random_access) {
-      return (left - right) == 0;
+    if constexpr (has_sentinel<self_type>) {
+      return (left.at_end() && right.at_end()) || left.equal_to(right);
     } else {
       return left.equal_to(right);
     }
   }
 
-  friend bool operator!=(self_type const & left, self_type const & right) {
-    return !(left == right);
-  }
-
-  SFINAE(C == category::random_access)
-  friend bool operator<(self_type const & left, self_type const & right) {
-    return (left - right) < 0;
-  }
-
-  SFINAE(C == category::random_access)
-  friend bool operator<=(self_type const & left, self_type const & right) {
-    return (left - right) <= 0;
-  }
-
-  SFINAE(C == category::random_access)
-  friend bool operator>(self_type const & left, self_type const & right) {
-    return (left - right) > 0;
-  }
-
-  SFINAE(C == category::random_access)
-  friend bool operator>=(self_type const & left, self_type const & right) {
-    return (left - right) >= 0;
+  friend auto operator<=>(self_type const & left, self_type const & right)
+    requires(random_access<self_type>)
+  {
+    return (left - right) <=> 0;
   }
 
 protected:
@@ -163,16 +128,20 @@ protected:
 };
 }
 
-// In C++20, a concept/requires could be used to eschew the need for the below
-// macros.
-#define MAKE_ITERATOR_FACADE_TYPEDEFS_T(Iter)                                  \
-  template <typename... T> struct std::iterator_traits<Iter<T...>> {           \
-    using type = Iter<T...>;                                                   \
-    using reference = decltype(*std::declval<type>());                         \
-    using value_type = std::decay_t<reference>;                                \
-    using pointer = decltype(std::declval<type>().operator->());               \
-    using difference_type = ::iterator::detail::distance_to_t<type>;           \
-    using iterator_category = ::iterator::detail::tag_for_t<type>;             \
-  }
+template <typename It>
+  requires std::is_base_of_v<iterator::facade<It>, It>
+struct std::iterator_traits<It> {
+  static const It & _it;
+  using reference = decltype(*_it);
+  using pointer = decltype(_it.operator->());
+  using value_type = ::iterator::infer_value_type_t<It>;
+  using difference_type = ::iterator::infer_difference_type_t<It>;
+  using iterator_category = std::conditional_t<
+      ::iterator::random_access<It>, random_access_iterator_tag,
+      std::conditional_t<
+          ::iterator::bidirectional<It>, bidirectional_iterator_tag,
+          std::conditional_t<::iterator::single_pass<It>, input_iterator_tag,
+                             forward_iterator_tag>>>;
+};
 
 #include <iterator/detail/undef.h>

+ 9 - 15
include/iterator/filter_iterator.h

@@ -9,7 +9,6 @@
 
 #include <functional>
 
-#include <iterator/detail/traits.h>
 #include <iterator/end_aware_iterator.h>
 #include <iterator/facade.h>
 #include <iterator/forwards.h>
@@ -18,21 +17,16 @@
 
 namespace iterator {
 template <typename Iter, typename Pred>
-class filter_iterator
-    : public facade<filter_iterator<Iter, Pred>,
-                    detail::category_for_v<Iter, category::bidirectional>> {
+class filter_iterator : public facade<filter_iterator<Iter, Pred>> {
 public:
   using sentinel_type = sentinel_t;
-
-  using super_t = facade<filter_iterator<Iter, Pred>,
-                         detail::category_for_v<Iter, category::bidirectional>>;
+  using super_t = filter_iterator::facade;
 
 public:
   filter_iterator() = default;
 
-  template <typename C, REQUIRES(detail::is_container_v<C>)>
-  filter_iterator(C && c, Pred pred)
-      : base_(std::forward<C>(c)), pred_(std::move(pred)) {
+  filter_iterator(Range auto & c, Pred pred)
+      : base_(c), pred_(std::move(pred)) {
     if (should_advance()) { increment(); }
   }
 
@@ -54,7 +48,9 @@ public:
     } while (should_advance());
   }
 
-  SFINAE(super_t::category_enum == category::bidirectional) void decrement() {
+  void decrement()
+    requires(std::bidirectional_iterator<Iter>)
+  {
     do {
       --base_;
     } while (should_advance());
@@ -75,13 +71,11 @@ public:
 };
 
 template <typename C, typename P>
-filter_iterator(C &&, P) -> filter_iterator<detail::iter<C>, P>;
+filter_iterator(C &, P) -> filter_iterator<iterator_t<C>, P>;
 template <typename It, typename P>
-filter_iterator(P, end_aware_iterator<It>, P) -> filter_iterator<It, P>;
+filter_iterator(end_aware_iterator<It>, P) -> filter_iterator<It, P>;
 template <typename It, typename P>
 filter_iterator(It, It, P) -> filter_iterator<It, P>;
 }
 
-MAKE_ITERATOR_FACADE_TYPEDEFS_T(::iterator::filter_iterator);
-
 #include <iterator/detail/undef.h>

+ 16 - 25
include/iterator/forwards.h

@@ -10,37 +10,28 @@
 #include <cstdlib>
 #include <iterator>
 
-namespace iterator::recursive {
-struct unbounded;
-template <size_t N, size_t I = 1> struct bounded;
-template <typename, typename = unbounded> class rimpl;
-}
-
 namespace iterator {
-enum class category : unsigned char {
-  single_pass, // input/output iterator, for example
-  forward,
-  bidirectional,
-  random_access
-};
-
 struct sentinel_t;
 
 // Iterator types
-template <typename Iterator> class end_aware_iterator;
-template <typename Iterator, typename Predicate> class filter_iterator;
-template <typename OuterIterator> class joining_iterator;
-template <typename Iterator> class unkeyed_iterator;
-template <typename... Iterators> class zip_iterator;
+template <typename It> class end_aware_iterator;
+template <typename It, typename Predicate> class filter_iterator;
+template <typename OIt> class joining_iterator;
+template <typename It> class unkeyed_iterator;
+template <typename... Its> class zip_iterator;
+
+struct unbounded {};
+template <size_t N>
+  requires(N > 0)
+struct bounded {};
 
-template <typename Iterator>
-using recursive_iterator = recursive::rimpl<Iterator>;
-template <typename Iterator, std::size_t N>
-using recursive_iterator_n = recursive::rimpl<Iterator, recursive::bounded<N>>;
+template <typename It, typename MaxDepth = unbounded> class recursive_iterator;
+template <typename It, size_t N>
+using recursive_iterator_n = recursive_iterator<It, bounded<N>>;
 
-template <typename CRTP, category C> class facade;
+template <typename CRTP> class facade;
 
-template <typename Iterator, typename CRTP,
-          typename = typename std::iterator_traits<Iterator>::iterator_category>
+template <typename It, typename CRTP,
+          typename = typename std::iterator_traits<It>::iterator_category>
 class proxy;
 }

+ 17 - 13
include/iterator/indexed_iterator.h

@@ -16,14 +16,13 @@
 
 namespace iterator {
 template <typename It>
-class indexed_iterator
-    : public facade<indexed_iterator<It>, detail::category_for_v<It>> {
+class indexed_iterator : public facade<indexed_iterator<It>> {
 public:
   using reference = std::pair<size_t, DEREF_TYPE(It)>;
   using difference_type = typename std::iterator_traits<It>::difference_type;
 
 private:
-  using super_t = facade<indexed_iterator<It>, detail::category_for_v<It>>;
+  using super_t = facade<indexed_iterator<It>>;
 
 public:
   indexed_iterator() = default;
@@ -36,33 +35,40 @@ public:
 
   reference dereference() const { return {index_, *base_}; }
 
-  SFINAE(super_t::category_enum >= category::forward) void decrement() {
+  void increment()
+    requires(std::forward_iterator<It>)
+  {
     ++base_;
     ++index_;
   }
 
-  SFINAE(super_t::category_enum >= category::bidirectional) void increment() {
+  void decrement()
+    requires(std::bidirectional_iterator<It>)
+  {
     --base_;
     --index_;
   }
 
-  SFINAE(super_t::category_enum >= category::random_access)
-  void advance(difference_type off) {
+  void advance(difference_type off)
+    requires(std::random_access_iterator<It>)
+  {
     base_ += off;
     index_ += off;
   }
 
-  SFINAE(super_t::category_enum < category::random_access)
   bool equal_to(indexed_iterator const & other) const {
     return base_ == other.base_;
   }
 
-  SFINAE(super_t::category_enum >= category::random_access)
-  difference_type distance_to(indexed_iterator const & other) const {
+  difference_type distance_to(indexed_iterator const & other) const
+    requires(std::random_access_iterator<It>)
+  {
     return other.base_ - base_;
   }
 
-  SFINAE(detail::has_sentinel_type_v<It>) bool at_end() const {
+  bool at_end() const
+    requires(has_sentinel<It>)
+  {
     return base_ == typename It::sentinel_type();
   }
 
@@ -75,5 +81,3 @@ private:
 template <typename It> indexed_iterator(It) -> indexed_iterator<It>;
 template <typename It> indexed_iterator(It, size_t) -> indexed_iterator<It>;
 }
-
-MAKE_ITERATOR_FACADE_TYPEDEFS_T(::iterator::indexed_iterator);

+ 5 - 12
include/iterator/join_iterator.h

@@ -8,8 +8,6 @@
 #pragma once
 
 #include <iterator>
-#include <memory>
-#include <utility>
 
 #include <iterator/capture_iterator.h>
 #include <iterator/end_aware_iterator.h>
@@ -20,11 +18,10 @@
 
 namespace iterator {
 template <typename It>
-class joining_iterator
-    : public facade<joining_iterator<It>, category::forward> {
+class joining_iterator : public facade<joining_iterator<It>> {
 private:
   template <typename Ot> friend class joining_iterator;
-  constexpr static bool requires_caching = detail::is_rvalue_iterator_v<It>;
+  constexpr static bool requires_caching = !std::is_reference_v<DEREF_TYPE(It)>;
 
 public:
   using sentinel_type = sentinel_t;
@@ -33,7 +30,7 @@ public:
                          capture_iterator<end_aware_iterator<It>>,
                          end_aware_iterator<It>>;
   using inner_iterator_t =
-      end_aware_iterator<detail::iter<DEREF_TYPE(outer_iterator_t)>>;
+      end_aware_iterator<iterator_t<DEREF_TYPE(outer_iterator_t)>>;
 
 private:
   outer_iterator_t outer_;
@@ -47,8 +44,7 @@ public:
     safely_init_inner_iterator(other.outer_, other.inner_);
   }
 
-  template <typename C, REQUIRES(detail::is_container_v<C>)>
-  joining_iterator(C && container) : outer_(FWD(container)) {
+  joining_iterator(Range auto & container) : outer_(FWD(container)) {
     update_iterator();
   }
 
@@ -98,12 +94,9 @@ private:
   }
 };
 
-template <typename C>
-joining_iterator(C &&) -> joining_iterator<detail::iter<C>>;
+template <typename C> joining_iterator(C &) -> joining_iterator<iterator_t<C>>;
 template <typename JI>
 joining_iterator(end_aware_iterator<JI>) -> joining_iterator<JI>;
 }
 
-MAKE_ITERATOR_FACADE_TYPEDEFS_T(::iterator::joining_iterator);
-
 #include <iterator/detail/undef.h>

+ 21 - 16
include/iterator/proxy.h

@@ -7,9 +7,9 @@
 
 namespace iterator {
 template <typename It, typename Self, typename Cat>
-class proxy : public facade<Self, category::single_pass> {
+class proxy : public facade<Self> {
 public:
-  using single_pass_iterator = void;
+  static constexpr bool single_pass_iterator = true;
 
 private:
   It impl_;
@@ -22,7 +22,9 @@ public:
   decltype(auto) dereference() const { return *impl_; }
   void increment() { ++impl_; }
   bool equal_to(Self const & other) const { return impl_ == other.impl_; }
-  SFINAE(detail::has_sentinel_type_v<It>) bool at_end() const {
+  bool at_end() const
+    requires(has_sentinel<It>)
+  {
     return impl() == typename It::sentinel_type();
   }
 
@@ -31,8 +33,7 @@ protected:
 };
 
 template <typename It, typename Self>
-class proxy<It, Self, std::forward_iterator_tag>
-    : public facade<Self, category::forward> {
+class proxy<It, Self, std::forward_iterator_tag> : public facade<Self> {
 private:
   It impl_;
 
@@ -44,7 +45,9 @@ public:
   decltype(auto) dereference() const { return *impl_; }
   void increment() { ++impl_; }
   bool equal_to(Self const & other) const { return impl_ == other.impl_; }
-  SFINAE(detail::has_sentinel_type_v<It>) bool at_end() const {
+  bool at_end() const
+    requires(has_sentinel<It>)
+  {
     return impl() == typename It::sentinel_type();
   }
 
@@ -53,8 +56,7 @@ protected:
 };
 
 template <typename It, typename Self>
-class proxy<It, Self, std::bidirectional_iterator_tag>
-    : public facade<Self, category::bidirectional> {
+class proxy<It, Self, std::bidirectional_iterator_tag> : public facade<Self> {
 private:
   It impl_;
 
@@ -67,7 +69,9 @@ public:
   void increment() { ++impl_; }
   void decrement() { --impl_; }
   bool equal_to(Self const & other) const { return impl_ == other.impl_; }
-  SFINAE(detail::has_sentinel_type_v<It>) bool at_end() const {
+  bool at_end() const
+    requires(has_sentinel<It>)
+  {
     return impl() == typename It::sentinel_type();
   }
 
@@ -76,8 +80,7 @@ protected:
 };
 
 template <typename It, typename Self>
-class proxy<It, Self, std::random_access_iterator_tag>
-    : public facade<Self, category::random_access> {
+class proxy<It, Self, std::random_access_iterator_tag> : public facade<Self> {
 public:
   using difference_type = typename std::iterator_traits<It>::difference_type;
 
@@ -91,16 +94,18 @@ public:
 
   decltype(auto) dereference() const { return *impl_; }
   void advance(difference_type off) { impl_ += off; }
+  bool equal_to(Self const & other) const { return impl_ == other.impl_; }
   difference_type distance_to(Self const & other) const {
     return other.impl_ - impl_;
   }
-  
-  template <typename S, REQUIRES((detail::is_sentinel_v<It, S>))>
-  friend auto operator-(S sentinel, Self const &self) {
+
+  friend auto operator-(sentinel_for<It> auto sentinel, Self const & self) {
     return sentinel - self.impl();
   }
-  
-  SFINAE(detail::has_sentinel_type_v<It>) bool at_end() const {
+
+  bool at_end() const
+    requires(has_sentinel<It>)
+  {
     return (typename It::sentinel_type() - impl()) <= 0;
   }
 

+ 142 - 132
include/iterator/recursive_iterator.h

@@ -7,203 +7,213 @@
 
 #pragma once
 
+#include <ranges>
 #include <string>
 #include <tuple>
 #include <utility>
 
-#include <iterator/detail/recursive_traits.h>
 #include <iterator/end_aware_iterator.h>
 #include <iterator/facade.h>
 #include <iterator/forwards.h>
 
 #include <iterator/detail/macro.h>
 
-namespace iterator::recursive {
-template <size_t N, size_t I> struct bounded {
-  template <typename It>
-  static constexpr recursion_type const value =
-      I == N ? recursion_type::END : typeclass_t<It>::value;
-  using next = std::conditional_t<I == N, void, bounded<N, I + 1>>;
-  static constexpr size_t size = N;
+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>>;
 };
 
-struct unbounded {
-  template <typename It>
-  static constexpr recursion_type const value = typeclass_t<It>::value;
-  using next = unbounded;
-  static constexpr size_t size = std::numeric_limits<size_t>::max();
+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 Bnd = unbounded,
-          recursion_type = Bnd::template value<It>>
-struct tuple;
+template <typename It, typename MaxDepth, size_t N, Range V>
+struct tuple_expander<It, MaxDepth, N, V> {
+  static It & _it;
 
-template <typename It, typename Bnd>
-struct tuple<It, Bnd, recursion_type::END> {
-  using iter = std::tuple<end_aware_iterator<It>>;
-  decltype(auto) get(It iter) const {
-    if constexpr (is_associative<It>{}) {
-      return std::tie(iter->first, iter->second);
-    } else {
-      return std::tie(*iter);
-    }
-  }
+  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... Ts>
-using tuple_cat_t = decltype(std::tuple_cat(VAL(Ts)...));
+template <typename It, typename MaxDepth, size_t N, AssocRange V>
+struct tuple_expander<It, MaxDepth, N, V> {
+  static It & _it;
 
-template <typename It, typename Bnd>
-struct tuple<It, Bnd, recursion_type::THRU> {
-  using next = decltype(std::begin(*VAL(It)));
-  using iter = tuple_cat_t<std::tuple<end_aware_iterator<It>>,
-                           typename tuple<next, typename Bnd::next>::iter>;
-  auto get(It) const { return std::make_tuple(); }
-};
+  using next_iterator_t = decltype(std::begin(_it->second));
+  using expand_next = tuple_expander<next_iterator_t, MaxDepth, N + 1>;
 
-template <typename It, typename Bnd>
-struct tuple<It, Bnd, recursion_type::ASSOC> {
-  using next = decltype(std::begin(VAL(It)->second));
-  using iter = tuple_cat_t<std::tuple<end_aware_iterator<It>>,
-                           typename tuple<next, typename Bnd::next>::iter>;
-  auto get(It iter) const { return std::tie(iter->first); };
+  using iterator_tuple = tuple_cat_t<std::tuple<end_aware_iterator<It>>,
+                                     typename expand_next::iterator_tuple>;
 };
 
-/**
- * @brief An iterator type for nested collections, allowing you to treat it as
- * a single-layer collection.
- *
- * In order to provide a simple interface, if an associative container is used
- * in the chain, the type returned by operator*() is a tuple. If multiple
- * associative containers are nested, then the tuple will be of the form
- * std::tuple<key1, key2, ..., keyN, value>. To avoid copies, and allow
- * editting of underlying values, the tuple contains references.
- *
- * @tparam It The iterator type of the top-level collection.
- * @tparam Bnd The bounding type, representing how many layers this iterator
- * is willing to delve in the parent object.
- */
-template <typename It, typename Bnd>
-class rimpl : public facade<rimpl<It, Bnd>, category::forward> {
+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...> {
 public:
-  using sentinel_type = sentinel_t;
-  using iters_t = typename tuple<It, Bnd>::iter;
-  static constexpr size_t n_iters =
-      std::tuple_size_v<typename tuple<It, Bnd>::iter>;
-  static constexpr size_t size = std::min(n_iters, Bnd::size);
-
-private:
-  iters_t impl_;
+  static constexpr size_t LastIndex = sizeof...(It) - 1;
+  template <size_t I>
+  using iterator_type = std::tuple_element_t<I, std::tuple<It...>>;
+  template <size_t I> using value_type = std::iter_value_t<iterator_type<I>>;
 
 public:
-  rimpl() = default;
-  rimpl(end_aware_iterator<It> iter) { assign<0>(iter); }
-  template <typename Ot>
-  rimpl(end_aware_iterator<Ot> other) : rimpl(end_aware_iterator<It>(other)) {}
-  template <typename Ot>
-  rimpl(rimpl<Ot, Bnd> other) : rimpl(end_aware_iterator<Ot>(other)) {}
-
   template <typename T> operator end_aware_iterator<T>() const {
-    return std::get<end_aware_iterator<T>>(impl_);
+    return std::get<end_aware_iterator<T>>(*this);
   }
 
   decltype(auto) dereference() const {
+    auto rval = std::tuple_cat(get<Is>()...);
     // Special Case Handling for circumstances where at least everything up to
     // the deepest nested container is non-associative. In this case, we don't
     // want to transmute our single element/association into a tuple, since
     // there's no benefit from that.
-    if constexpr (std::tuple_size_v<decltype(this->build_tuple())> == 1) {
-      return *std::get<size - 1>(impl_);
+    if constexpr (std::tuple_size_v<decltype(rval)> == 1) {
+      return std::get<0>(rval); // May be a reference
     } else {
-      return build_tuple();
+      return rval; // Tuple-of-references
     }
   }
 
-  template <size_t I = size - 1> bool increment() {
-    auto & iter = std::get<I>(impl_);
+  void increment() { increment<>(); }
+
+  bool at_end() const { return std::get<0>(*this).at_end(); }
+  bool equal_to(recursive_iterator_base const & other) const {
+    return *this == other;
+  }
+
+protected:
+  recursive_iterator_base() = default;
+  recursive_iterator_base(iterator_type<0> iter) { assign<0>(iter); }
+
+private:
+  template <size_t I = LastIndex> bool increment() {
+    auto & iter = std::get<I>(*this);
     if (iter.at_end()) { return false; } // Make sure we don't go OOB
     ++iter;
     if constexpr (I > 0) {
       while (iter.at_end() && increment<I - 1>()) {
-        assign<I>(*std::get<I - 1>(impl_));
+        assign<I>(*std::get<I - 1>(*this));
       }
     }
     return !iter.at_end();
   }
 
-  bool at_end() const { return std::get<0>(impl_).at_end(); }
-  bool equal_to(rimpl const & other) const { return impl_ == other.impl_; }
-
-  // Used by std::get, don't use elsewhere...
-  auto const & impl() const { return impl_; }
-
-private:
   template <size_t I> decltype(auto) get() const {
-    auto it = std::get<I>(impl_);
-    // In the case of a bounded recursive iterator, I need to ensure that the
-    // effectively terminal iterator is treated as such even if there is still
-    // iteration to be had.
-    if constexpr (I == size - 1) {
-      return tuple<decltype(it), unbounded, recursion_type::END>{}.get(it);
+    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);
+      } else {
+        return std::tie(*iter);
+      }
+    } else if constexpr (Assoc<value_type<I>>) {
+      return std::tie(iter->first);
     } else {
-      return tuple<decltype(it)>{}.get(it);
+      return std::make_tuple();
     }
   }
 
-  template <size_t... Is>
-  decltype(auto) build_tuple(std::index_sequence<Is...>) const {
-    return std::tuple_cat(get<Is>()...);
-  }
-
-  decltype(auto) build_tuple() const {
-    return build_tuple(std::make_index_sequence<size>());
-  }
-
-  template <size_t I, typename C> void assign(end_aware_iterator<C> it) {
-    std::get<I>(impl_) = it;
-    if constexpr (I < size - 1) {
+  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); }
     }
   }
 
-  template <size_t I, typename C> void assign(C && collection) {
-    assign<I>(end_aware_iterator(std::forward<C>(collection)));
+  template <size_t I, typename T> void assign(T && value) {
+    if constexpr (Range<T>) {
+      assign<I>(end_aware_iterator(std::forward<T>(value)));
+    } else {
+      assign<I>(value.second);
+    }
   }
+};
 
-  template <size_t I, typename K, typename V>
-  void assign(std::pair<K const, V> const & pair) {
-    assign<I>(pair.second);
-  }
-  template <size_t I, typename K, typename V>
-  void assign(std::pair<K const, V> & pair) {
-    assign<I>(pair.second);
-  }
+template <typename It, typename MaxDepth> struct recursive_iterator_helper {
+  using iterator_tuple = typename tuple_expander<It, MaxDepth>::iterator_tuple;
+
+  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>;
 };
-}
 
-MAKE_ITERATOR_FACADE_TYPEDEFS_T(::iterator::recursive::rimpl);
+/**
+ * @brief An iterator type for nested collections, allowing you to treat it as
+ * a single-layer collection.
+ *
+ * In order to provide a simple interface, if an associative container is used
+ * in the chain, the type returned by operator*() is a tuple. If multiple
+ * associative containers are nested, then the tuple will be of the form
+ * std::tuple<key1, key2, ..., keyN, value>. To avoid copies, and allow
+ * editting of underlying values, the tuple contains references.
+ *
+ * @tparam It The iterator type of the top-level collection.
+ * @tparam MaxDepth The bounding type, representing how many layers this
+ * iterator is willing to delve in the parent object.
+ */
+template <typename It, typename MaxDepth>
+class recursive_iterator : public recursive_iterator_helper<It, MaxDepth>::type,
+                           public facade<recursive_iterator<It, MaxDepth>> {
+public:
+  using sentinel_type = sentinel_t;
 
-namespace std {
-template <std::size_t I, typename It>
-auto get(::iterator::recursive_iterator<It> const & iter) {
-  return ::std::get<I>(iter.impl());
+public:
+  recursive_iterator() = default;
+
+  explicit recursive_iterator(Range auto & range, MaxDepth = {})
+      : recursive_iterator(end_aware_iterator(range)) {}
+
+  explicit recursive_iterator(end_aware_iterator<It> iter, MaxDepth = {})
+      : recursive_iterator::recursive_iterator_base(iter) {}
+
+  template <typename Ot>
+  explicit recursive_iterator(end_aware_iterator<Ot> other, MaxDepth = {})
+      : recursive_iterator(end_aware_iterator<It>(other)) {}
+
+  template <typename Ot>
+  explicit recursive_iterator(recursive_iterator<Ot, MaxDepth> other)
+      : recursive_iterator(end_aware_iterator<Ot>(other)) {}
+};
+
+template <typename Range, typename MaxDepth>
+recursive_iterator(Range &, MaxDepth)
+    -> recursive_iterator<iterator_t<Range>, MaxDepth>;
+template <typename Range>
+recursive_iterator(Range &) -> recursive_iterator<iterator_t<Range>, unbounded>;
 }
 
-template <std::size_t I, typename It, std::size_t N>
-auto get(::iterator::recursive_iterator_n<It, N> const & iter) {
-  static_assert(I < N, "Cannot get past bounding level");
-  return ::std::get<I>(iter.impl());
+namespace std {
+template <size_t I, typename It, typename MaxDepth>
+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);
 }
 }
 
-template <typename C> auto make_recursive_iterator(C && collect) {
-  return iterator::recursive_iterator<iterator::detail::iter<C>>(collect);
-}
+namespace iterator::views {
+template <size_t N>
+struct recursive_n_fn : std::ranges::range_adaptor_closure<recursive_n_fn<N>> {
+  template <std::ranges::range Rng> auto operator()(Rng && rng) const {
+    auto begin = recursive_iterator(std::forward<Rng>(rng), bounded<N>{});
+    return std::ranges::subrange(begin, decltype(begin)());
+  }
+};
+
+struct recursive_fn : std::ranges::range_adaptor_closure<recursive_fn> {
+  template <std::ranges::range Rng> auto operator()(Rng && rng) const {
+    auto begin = recursive_iterator(std::forward<Rng>(rng));
+    return std::ranges::subrange(begin, decltype(begin)());
+  }
+};
 
-template <std::size_t Max, typename C>
-auto make_recursive_iterator(C && collect) {
-  return iterator::recursive_iterator_n<iterator::detail::iter<C>, Max>(
-      collect);
+template <size_t N> constexpr recursive_n_fn<N> recursive_n{};
+constexpr recursive_fn recursive;
 }
 
 #include <iterator/detail/undef.h>

+ 1 - 3
include/iterator/unkeyed_iterator.h

@@ -20,7 +20,7 @@ namespace iterator {
  * have something crazy like a four-level map and then do:
  * \code
  * std::map<int, std::map<std::string, std::map<int, BigType>>> object;
- * auto const rit = make_recursive_iterator(object);
+ * auto const rit = recursive_iterator(object);
  * for (unkeyed_iterator<decltype(rit)> it = rit, end = {}; it != end; ++it) {
  *   // Process only BigType, discarding all of the keys that we need to walk
  * }
@@ -39,5 +39,3 @@ public:
 
 template <typename It> unkeyed_iterator(It) -> unkeyed_iterator<It>;
 }
-
-MAKE_ITERATOR_FACADE_TYPEDEFS_T(::iterator::unkeyed_iterator);

+ 12 - 13
include/iterator/zip_iterator.h

@@ -26,10 +26,14 @@ public:
   void increment() {
     [[maybe_unused]] auto l = {((++std::get<Is>(_data)), 0)...};
   }
-  void decrement() {
-    [[maybe_unused]] auto l = {((++std::get<Is>(_data)), 0)...};
+  void decrement()
+    requires(std::bidirectional_iterator<Ts> && ...)
+  {
+    [[maybe_unused]] auto l = {((--std::get<Is>(_data)), 0)...};
   }
-  void advance(difference_type d) {
+  void advance(difference_type d)
+    requires(std::random_access_iterator<Ts> && ...)
+  {
     [[maybe_unused]] auto l = {((std::get<Is>(_data) += d), 0)...};
   }
 
@@ -37,7 +41,9 @@ public:
     return _data == other._data;
   }
 
-  auto distance_to(zip_iterator_impl const & other) const {
+  auto distance_to(zip_iterator_impl const & other) const
+    requires(std::random_access_iterator<Ts> && ...)
+  {
     return std::get<0>(other._data) - std::get<0>(_data);
   }
 
@@ -53,14 +59,9 @@ template <typename... Ts>
 using zip_impl =
     detail::zip_iterator_impl<std::tuple<Ts...>, index_sequence<Ts...>>;
 
-template <typename... Iterators>
-constexpr auto
-    zip_category_for = std::min({detail::category_for_v<Iterators>...});
-
 template <typename... Iters>
-class zip_iterator
-    : public zip_impl<Iters...>,
-      public facade<zip_iterator<Iters...>, zip_category_for<Iters...>> {
+class zip_iterator : public zip_impl<Iters...>,
+                     public facade<zip_iterator<Iters...>> {
 public:
   zip_iterator() = default;
   zip_iterator(Iters... iters) : zip_impl<Iters...>(iters...) {}
@@ -68,5 +69,3 @@ public:
 
 template <typename... It> zip_iterator(It...) -> zip_iterator<It...>;
 }
-
-MAKE_ITERATOR_FACADE_TYPEDEFS_T(::iterator::zip_iterator);

+ 65 - 24
iterator.xcodeproj/project.pbxproj

@@ -3,10 +3,26 @@
 	archiveVersion = 1;
 	classes = {
 	};
-	objectVersion = 46;
+	objectVersion = 54;
 	objects = {
 
 /* Begin PBXBuildFile section */
+		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 */; };
+		CD41AFE62E7F13E4004F3E51 /* proxy.h in Headers */ = {isa = PBXBuildFile; fileRef = CDA2B61C2858128C004D5353 /* proxy.h */; };
+		CD41AFE72E7F13E4004F3E51 /* sentinel.h in Headers */ = {isa = PBXBuildFile; fileRef = CD5A8E7329D7910D008C2A4F /* sentinel.h */; };
+		CD41AFE82E7F13E4004F3E51 /* arrow_proxy.h in Headers */ = {isa = PBXBuildFile; fileRef = CDA2B61A2858128C004D5353 /* arrow_proxy.h */; };
+		CD41AFE92E7F13E4004F3E51 /* macro.h in Headers */ = {isa = PBXBuildFile; fileRef = CD5AEB3129D8885400A390A4 /* macro.h */; };
+		CD41AFEC2E7F13E4004F3E51 /* undef.h in Headers */ = {isa = PBXBuildFile; fileRef = CD5AEB3229D8886200A390A4 /* undef.h */; };
+		CD41AFED2E7F13E4004F3E51 /* capture_iterator.h in Headers */ = {isa = PBXBuildFile; fileRef = CD5AEB3329D8956600A390A4 /* capture_iterator.h */; };
+		CD41AFEE2E7F13E4004F3E51 /* end_aware_iterator.h in Headers */ = {isa = PBXBuildFile; fileRef = CDA2B61B2858128C004D5353 /* end_aware_iterator.h */; };
+		CD41AFEF2E7F13E4004F3E51 /* filter_iterator.h in Headers */ = {isa = PBXBuildFile; fileRef = CDA2B61E2858128C004D5353 /* filter_iterator.h */; };
+		CD41AFF02E7F13E4004F3E51 /* indexed_iterator.h in Headers */ = {isa = PBXBuildFile; fileRef = CDA2B6142858128C004D5353 /* indexed_iterator.h */; };
+		CD41AFF12E7F13E4004F3E51 /* join_iterator.h in Headers */ = {isa = PBXBuildFile; fileRef = CDA2B61F2858128C004D5353 /* join_iterator.h */; };
+		CD41AFF22E7F13E4004F3E51 /* recursive_iterator.h in Headers */ = {isa = PBXBuildFile; fileRef = CDA2B6162858128C004D5353 /* recursive_iterator.h */; };
+		CD41AFF32E7F13E4004F3E51 /* unkeyed_iterator.h in Headers */ = {isa = PBXBuildFile; fileRef = CDA2B6152858128C004D5353 /* unkeyed_iterator.h */; };
+		CD41AFF42E7F13E4004F3E51 /* zip_iterator.h in Headers */ = {isa = PBXBuildFile; fileRef = CDA2B61D2858128C004D5353 /* zip_iterator.h */; };
 		CD5AEB3529D897DD00A390A4 /* capture_iterator_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD5AEB3429D897DD00A390A4 /* capture_iterator_test.cxx */; };
 		CDA2B62028581295004D5353 /* iterator in Headers */ = {isa = PBXBuildFile; fileRef = CDCB3BBC24E1CDE40029B771 /* iterator */; settings = {ATTRIBUTES = (Public, ); }; };
 		CDCB3BCA24E1D39B0029B771 /* GoogleMock.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CDEC1E09235167920091D9F2 /* GoogleMock.framework */; };
@@ -63,6 +79,7 @@
 
 /* Begin PBXFileReference section */
 		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>"; };
 		CD5AEAE729D86D8100A390A4 /* ranges.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ranges.h; sourceTree = "<group>"; };
 		CD5AEB3129D8885400A390A4 /* macro.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = macro.h; sourceTree = "<group>"; };
@@ -75,8 +92,6 @@
 		CDA2B6142858128C004D5353 /* indexed_iterator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = indexed_iterator.h; sourceTree = "<group>"; };
 		CDA2B6152858128C004D5353 /* unkeyed_iterator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = unkeyed_iterator.h; sourceTree = "<group>"; };
 		CDA2B6162858128C004D5353 /* recursive_iterator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = recursive_iterator.h; sourceTree = "<group>"; };
-		CDA2B6182858128C004D5353 /* recursive_traits.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = recursive_traits.h; sourceTree = "<group>"; };
-		CDA2B6192858128C004D5353 /* traits.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = traits.h; sourceTree = "<group>"; };
 		CDA2B61A2858128C004D5353 /* arrow_proxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = arrow_proxy.h; sourceTree = "<group>"; };
 		CDA2B61B2858128C004D5353 /* end_aware_iterator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = end_aware_iterator.h; sourceTree = "<group>"; };
 		CDA2B61C2858128C004D5353 /* proxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = proxy.h; sourceTree = "<group>"; };
@@ -173,6 +188,7 @@
 		CDA2B6112858128C004D5353 /* iterator */ = {
 			isa = PBXGroup;
 			children = (
+				CD41AFE22E7F0434004F3E51 /* concepts.h */,
 				CDA2B6122858128C004D5353 /* forwards.h */,
 				CDA2B6132858128C004D5353 /* facade.h */,
 				CDA2B61C2858128C004D5353 /* proxy.h */,
@@ -195,8 +211,6 @@
 			children = (
 				CDA2B61A2858128C004D5353 /* arrow_proxy.h */,
 				CD5AEB3129D8885400A390A4 /* macro.h */,
-				CDA2B6182858128C004D5353 /* recursive_traits.h */,
-				CDA2B6192858128C004D5353 /* traits.h */,
 				CD5AEB3229D8886200A390A4 /* undef.h */,
 			);
 			path = detail;
@@ -236,6 +250,22 @@
 			buildActionMask = 2147483647;
 			files = (
 				CDA2B62028581295004D5353 /* iterator in Headers */,
+				CD41AFE32E7F13E4004F3E51 /* concepts.h in Headers */,
+				CD41AFE42E7F13E4004F3E51 /* forwards.h in Headers */,
+				CD41AFE52E7F13E4004F3E51 /* facade.h in Headers */,
+				CD41AFE62E7F13E4004F3E51 /* proxy.h in Headers */,
+				CD41AFE72E7F13E4004F3E51 /* sentinel.h in Headers */,
+				CD41AFE82E7F13E4004F3E51 /* arrow_proxy.h in Headers */,
+				CD41AFE92E7F13E4004F3E51 /* macro.h in Headers */,
+				CD41AFEC2E7F13E4004F3E51 /* undef.h in Headers */,
+				CD41AFED2E7F13E4004F3E51 /* capture_iterator.h in Headers */,
+				CD41AFEE2E7F13E4004F3E51 /* end_aware_iterator.h in Headers */,
+				CD41AFEF2E7F13E4004F3E51 /* filter_iterator.h in Headers */,
+				CD41AFF02E7F13E4004F3E51 /* indexed_iterator.h in Headers */,
+				CD41AFF12E7F13E4004F3E51 /* join_iterator.h in Headers */,
+				CD41AFF22E7F13E4004F3E51 /* recursive_iterator.h in Headers */,
+				CD41AFF32E7F13E4004F3E51 /* unkeyed_iterator.h in Headers */,
+				CD41AFF42E7F13E4004F3E51 /* zip_iterator.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -283,8 +313,9 @@
 		CD21AE151E4A3E7900536178 /* Project object */ = {
 			isa = PBXProject;
 			attributes = {
+				BuildIndependentTargetsInParallel = YES;
 				LastSwiftUpdateCheck = 1340;
-				LastUpgradeCheck = 1230;
+				LastUpgradeCheck = 2600;
 				ORGANIZATIONNAME = "Sam Jaffe";
 				TargetAttributes = {
 					CDA2B60828581255004D5353 = {
@@ -406,7 +437,7 @@
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
 				CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
-				CLANG_CXX_LANGUAGE_STANDARD = "c++17";
+				CLANG_CXX_LANGUAGE_STANDARD = "c++23";
 				CLANG_CXX_LIBRARY = "libc++";
 				CLANG_ENABLE_MODULES = YES;
 				CLANG_ENABLE_OBJC_ARC = YES;
@@ -432,10 +463,12 @@
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				CODE_SIGN_IDENTITY = "-";
 				COPY_PHASE_STRIP = NO;
+				DEAD_CODE_STRIPPING = YES;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
 				ENABLE_TESTABILITY = YES;
-				GCC_C_LANGUAGE_STANDARD = gnu99;
+				ENABLE_USER_SCRIPT_SANDBOXING = YES;
+				GCC_C_LANGUAGE_STANDARD = c23;
 				GCC_DYNAMIC_NO_PIC = NO;
 				GCC_NO_COMMON_BLOCKS = YES;
 				GCC_OPTIMIZATION_LEVEL = 0;
@@ -453,6 +486,7 @@
 				MTL_ENABLE_DEBUG_INFO = YES;
 				ONLY_ACTIVE_ARCH = YES;
 				SDKROOT = macosx;
+				STRING_CATALOG_GENERATE_SYMBOLS = YES;
 				SYSTEM_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/include";
 				USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/include";
 			};
@@ -463,7 +497,7 @@
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
 				CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
-				CLANG_CXX_LANGUAGE_STANDARD = "c++17";
+				CLANG_CXX_LANGUAGE_STANDARD = "c++23";
 				CLANG_CXX_LIBRARY = "libc++";
 				CLANG_ENABLE_MODULES = YES;
 				CLANG_ENABLE_OBJC_ARC = YES;
@@ -489,10 +523,12 @@
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				CODE_SIGN_IDENTITY = "-";
 				COPY_PHASE_STRIP = NO;
+				DEAD_CODE_STRIPPING = YES;
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 				ENABLE_NS_ASSERTIONS = NO;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
-				GCC_C_LANGUAGE_STANDARD = gnu99;
+				ENABLE_USER_SCRIPT_SANDBOXING = YES;
+				GCC_C_LANGUAGE_STANDARD = c23;
 				GCC_NO_COMMON_BLOCKS = YES;
 				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
 				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
@@ -503,6 +539,7 @@
 				MACOSX_DEPLOYMENT_TARGET = 10.10;
 				MTL_ENABLE_DEBUG_INFO = NO;
 				SDKROOT = macosx;
+				STRING_CATALOG_GENERATE_SYMBOLS = YES;
 				SYSTEM_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/include";
 				USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/include";
 			};
@@ -513,14 +550,13 @@
 			buildSettings = {
 				CLANG_ANALYZER_NONNULL = YES;
 				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
-				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CLANG_ENABLE_OBJC_WEAK = YES;
 				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
 				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
 				CODE_SIGN_STYLE = Automatic;
+				DEAD_CODE_STRIPPING = YES;
 				EXECUTABLE_PREFIX = lib;
-				GCC_C_LANGUAGE_STANDARD = gnu11;
-				MACOSX_DEPLOYMENT_TARGET = 12.0;
+				MACOSX_DEPLOYMENT_TARGET = 14.6;
 				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
 				MTL_FAST_MATH = YES;
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -533,14 +569,13 @@
 			buildSettings = {
 				CLANG_ANALYZER_NONNULL = YES;
 				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
-				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CLANG_ENABLE_OBJC_WEAK = YES;
 				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
 				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
 				CODE_SIGN_STYLE = Automatic;
+				DEAD_CODE_STRIPPING = YES;
 				EXECUTABLE_PREFIX = lib;
-				GCC_C_LANGUAGE_STANDARD = gnu11;
-				MACOSX_DEPLOYMENT_TARGET = 12.0;
+				MACOSX_DEPLOYMENT_TARGET = 14.6;
 				MTL_FAST_MATH = YES;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SKIP_INSTALL = YES;
@@ -552,16 +587,19 @@
 			buildSettings = {
 				CLANG_ANALYZER_NONNULL = YES;
 				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
-				CLANG_CXX_LANGUAGE_STANDARD = "c++17";
 				CLANG_ENABLE_OBJC_WEAK = YES;
 				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
 				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
 				CODE_SIGN_STYLE = Automatic;
 				COMBINE_HIDPI_IMAGES = YES;
-				GCC_C_LANGUAGE_STANDARD = gnu11;
+				DEAD_CODE_STRIPPING = YES;
 				INFOPLIST_FILE = "iterator-test/Info.plist";
-				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
-				MACOSX_DEPLOYMENT_TARGET = 10.15;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/../Frameworks",
+					"@loader_path/../Frameworks",
+				);
+				MACOSX_DEPLOYMENT_TARGET = 14.6;
 				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
 				MTL_FAST_MATH = YES;
 				PRODUCT_BUNDLE_IDENTIFIER = "leumasjaffe.iterator-test";
@@ -574,16 +612,19 @@
 			buildSettings = {
 				CLANG_ANALYZER_NONNULL = YES;
 				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
-				CLANG_CXX_LANGUAGE_STANDARD = "c++17";
 				CLANG_ENABLE_OBJC_WEAK = YES;
 				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
 				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
 				CODE_SIGN_STYLE = Automatic;
 				COMBINE_HIDPI_IMAGES = YES;
-				GCC_C_LANGUAGE_STANDARD = gnu11;
+				DEAD_CODE_STRIPPING = YES;
 				INFOPLIST_FILE = "iterator-test/Info.plist";
-				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
-				MACOSX_DEPLOYMENT_TARGET = 10.15;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/../Frameworks",
+					"@loader_path/../Frameworks",
+				);
+				MACOSX_DEPLOYMENT_TARGET = 14.6;
 				MTL_FAST_MATH = YES;
 				PRODUCT_BUNDLE_IDENTIFIER = "leumasjaffe.iterator-test";
 				PRODUCT_NAME = "$(TARGET_NAME)";

+ 1 - 1
iterator.xcodeproj/xcshareddata/xcschemes/iterator.xcscheme

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1340"
+   LastUpgradeVersion = "2600"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "YES"

+ 9 - 0
test/end_aware_iterator_test.cxx

@@ -1,11 +1,20 @@
 #include "iterator/end_aware_iterator.h"
 
+#include <iostream>
 #include <vector>
 
 #include "xcode_gtest_helper.h"
 
 using iterator::end_aware_iterator;
 
+TEST(EndAwareIterator, IStreamIterator) {
+  std::stringstream ss{"0 1 2 3"};
+  end_aware_iterator eai{std::istream_iterator<int>(ss),
+                         std::istream_iterator<int>()};
+
+  EXPECT_TRUE(iterator::single_pass<decltype(eai)>);
+}
+
 // TODO: This ought to be implemented as a compiles-test
 TEST(EndAwareIterator, CanCastCompatibleIterators) {
   std::vector<int> v{1, 2, 3, 4, 5};

+ 0 - 2
test/join_iterator_test.cxx

@@ -7,8 +7,6 @@
 using iterator::end_aware_iterator;
 using iterator::joining_iterator;
 
-using testing::Ne;
-
 TEST(JoinIteratorTest, FirstDereferencedElemIsTheFirstInTheChain) {
   std::vector<std::vector<int>> mv{{1, 2, 3}, {4, 5, 6}};
   EXPECT_EQ(*joining_iterator(mv), mv[0][0]);

+ 16 - 14
test/recursive_iterator_accessors_test.cxx

@@ -8,32 +8,34 @@
 #include "ranges.h"
 #include "xcode_gtest_helper.h"
 
+using iterator::bounded;
 using iterator::end_aware_iterator;
+using iterator::recursive_iterator;
 
 using testing::StaticAssertTypeEq;
 
 TEST(RecursiveIteratorTest, DoesNotUnwrapString) {
   std::vector<std::string> obj{"A", "B", "C", "D"};
-  auto rit = make_recursive_iterator(obj);
+  auto rit = recursive_iterator(obj);
   StaticAssertTypeEq<decltype(rit.operator->()), std::string *>();
 }
 
 TEST(RecursiveIteratorTest, CanArrowMultiVector) {
   std::vector<std::vector<int>> obj{{{0, 1}}, {{2, 3}}};
-  auto rit = make_recursive_iterator(obj);
+  auto rit = recursive_iterator(obj);
   StaticAssertTypeEq<decltype(rit.operator->()), int *>();
   EXPECT_EQ(rit.operator->(), &obj[0][0]);
 }
 
 // TEST(RecursiveIteratorTest, CannotArrowMap) {
 //  std::map<int, std::vector<int>> obj{{1, {{0, 1}}}, {2, {{2, 3}}}};
-//  auto rit = make_recursive_iterator(obj);
+//  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 = make_recursive_iterator(obj);
+  auto rit = recursive_iterator(obj);
 
   end_aware_iterator<decltype(obj)::iterator> inner = rit;
   EXPECT_EQ(&std::get<0>(*rit), &(inner->first));
@@ -41,7 +43,7 @@ TEST(RecursiveIteratorTest, CanAccessOuterterator) {
 
 TEST(RecursiveIteratorTest, CanAccessInnerIterator) {
   std::map<int, std::vector<int>> obj{{1, {{0, 1}}}, {2, {{2, 3}}}};
-  auto rit = make_recursive_iterator(obj);
+  auto rit = recursive_iterator(obj);
 
   end_aware_iterator<std::vector<int>::iterator> inner = rit;
   EXPECT_EQ(&std::get<1>(*rit), &*inner);
@@ -52,7 +54,7 @@ TEST(RecursiveIteratorTest, CanStdGetToAllLayersOfInternalIteration) {
       {1, {{{1, 1}}, {{2, 2}}}}, // 2 1-element maps
       {2, {{{3, 3}, {4, 4}}}}    // 1 2-element map
   };
-  auto rit = make_recursive_iterator(obj);
+  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>>();
@@ -71,7 +73,7 @@ TEST(RecursiveIteratorTest, CanAccessInternalIteratorsWithGet) {
       {1, {{{1, 1}}, {{2, 2}}}}, // 2 1-element maps
       {2, {{{3, 3}, {4, 4}}}}    // 1 2-element map
   };
-  auto rit = make_recursive_iterator(obj);
+  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]));
@@ -83,7 +85,7 @@ TEST(RecursiveIteratorTest, CanCastCompatibleIterators) {
       {1, {{{1, 1}}, {{2, 2}}}}, // 2 1-element maps
       {2, {{{3, 3}, {4, 4}}}}    // 1 2-element map
   };
-  auto rit = make_recursive_iterator(obj);
+  auto rit = recursive_iterator(obj);
   iterator::recursive_iterator<decltype(obj)::const_iterator> cit(rit);
 }
 
@@ -92,7 +94,7 @@ TEST(RecursiveIteratorTest, EmptyCtorIsEnd) {
       {1, {{{1, 1}}, {{2, 2}}}}, // 2 1-element maps
       {2, {{{3, 3}, {4, 4}}}}    // 1 2-element map
   };
-  auto rit = make_recursive_iterator(obj);
+  auto rit = recursive_iterator(obj);
 
   EXPECT_NE(rit, iterator::sentinel);
   EXPECT_EQ(ranges::distance(rit, iterator::sentinel), 4);
@@ -106,7 +108,7 @@ TEST(BoundedRecursiveIteratorTest, CanStdGetToNLayersOfInternalIteration) {
       {1, {{{1, 1}}, {{2, 2}}}}, // 2 1-element maps
       {2, {{{3, 3}, {4, 4}}}}    // 1 2-element map
   };
-  auto rit = make_recursive_iterator<2>(obj);
+  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>>();
@@ -122,7 +124,7 @@ TEST(BoundedRecursiveIteratorTest, CanAccessInternalIteratorsWithGet) {
       {1, {{{1, 1}}, {{2, 2}}}}, // 2 1-element maps
       {2, {{{3, 3}, {4, 4}}}}    // 1 2-element map
   };
-  auto rit = make_recursive_iterator<2>(obj);
+  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]));
 }
@@ -133,7 +135,7 @@ TEST(BoundedRecursiveIteratorTest, CanCastCompatibleIterators) {
       {1, {{{1, 1}}, {{2, 2}}}}, // 2 1-element maps
       {2, {{{3, 3}, {4, 4}}}}    // 1 2-element map
   };
-  auto rit = make_recursive_iterator<2>(obj);
+  auto rit = recursive_iterator(obj, bounded<2>{});
   iterator::recursive_iterator_n<decltype(obj)::const_iterator, 2> cit(rit);
 }
 
@@ -142,7 +144,7 @@ TEST(BoundedRecursiveIteratorTest, EmptyCtorIsEnd) {
       {1, {{{1, 1}}, {{2, 2}}}}, // 2 1-element maps
       {2, {{{3, 3}, {4, 4}}}}    // 1 2-element map
   };
-  auto rit = make_recursive_iterator<3>(obj);
+  auto rit = recursive_iterator(obj, bounded<3>{});
   EXPECT_NE(rit, iterator::sentinel);
   EXPECT_EQ(ranges::distance(rit, iterator::sentinel), 4);
 
@@ -155,6 +157,6 @@ TEST(BoundedRecursiveIteratorTest, CanFetchInnerCollections) {
       {{{{1, 1}}, {{2, 2}}}}, // 2 1-element maps
       {{{{3, 3}, {4, 4}}}}    // 1 2-element map
   };
-  auto rit = make_recursive_iterator<2>(obj);
+  auto rit = recursive_iterator(obj, bounded<2>{});
   EXPECT_EQ(*rit, obj[0][0]);
 }

+ 18 - 13
test/recursive_iterator_map_test.cxx

@@ -1,18 +1,24 @@
 #include "iterator/recursive_iterator.h"
 
 #include <map>
+#include <ranges>
 #include <tuple>
 #include <vector>
 
+#include <iterator/forwards.h>
+
 #include "ranges.h"
 #include "xcode_gtest_helper.h"
 
+using iterator::bounded;
+using iterator::recursive_iterator;
+
 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 = make_recursive_iterator(map);
+  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);
@@ -21,7 +27,7 @@ TEST(RecursiveIteratorMapTest, PreIncrementAdvancesIterator) {
 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 = make_recursive_iterator(map);
+  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);
@@ -30,7 +36,7 @@ TEST(RecursiveIteratorMapTest, PostIncrementReturnsCopyOfPrev) {
 TEST(RecursiveIteratorMapTest, IterDistanceIsSumOfInnerContainerSizes) {
   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 = make_recursive_iterator(map);
+  auto rit = recursive_iterator(map);
 
   EXPECT_EQ(ranges::distance(rit, iterator::sentinel), 4);
 }
@@ -40,7 +46,7 @@ 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 = make_recursive_iterator(map);
+  auto rit = recursive_iterator(map);
 
   EXPECT_EQ((ranges::to<std::vector<std::tuple<int, int, int, int>>>(
                 rit, iterator::sentinel)),
@@ -50,7 +56,7 @@ TEST(RecursiveIteratorMapTest, ElementsAreUnwrappedAsATuple) {
 TEST(RecursiveIteratorMapTest, CanMutatePointedToData) {
   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 = make_recursive_iterator(map);
+  auto rit = recursive_iterator(map);
   std::get<3>(*rit) = 4;
   EXPECT_EQ(map[1][1][1], 4);
 }
@@ -58,7 +64,7 @@ TEST(RecursiveIteratorMapTest, CanMutatePointedToData) {
 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 = make_recursive_iterator<2>(map);
+  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]);
@@ -67,7 +73,7 @@ TEST(BoundRecursiveIteratorMapTest, PreIncrementAdvancesIterator) {
 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 = make_recursive_iterator<2>(map);
+  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]);
@@ -76,7 +82,7 @@ TEST(BoundRecursiveIteratorMapTest, PostIncrementReturnsCopyOfPrev) {
 TEST(BoundRecursiveIteratorMapTest, IterDistanceSumOnNLayersSize) {
   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 = make_recursive_iterator<2>(map);
+  auto rit = recursive_iterator(map, bounded<2>{});
 
   EXPECT_EQ(ranges::distance(rit, iterator::sentinel), 3);
 }
@@ -86,17 +92,16 @@ TEST(BoundRecursiveIteratorMapTest, ElementsAreUnwrappedAsATuple) {
       {1, {{1, {{1, 1}}}}}, {2, {{2, {{2, 2}}}, {3, {{3, 3}, {4, 4}}}}}};
   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}}}};
-  auto rit = make_recursive_iterator<2>(map);
 
-  EXPECT_EQ((ranges::to<std::vector<std::tuple<int, int, std::map<int, int>>>>(
-                rit, iterator::sentinel)),
-            expected);
+  EXPECT_THAT(map | iterator::views::recursive_n<2> |
+                  std::ranges::to<std::vector>(),
+              testing::ElementsAreArray(expected));
 }
 
 TEST(BoundedRecursiveIteratorMapTest, CanMutatePointedToData) {
   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 = make_recursive_iterator<2>(map);
+  auto rit = recursive_iterator(map, bounded<2>{});
   std::get<2>(*rit).clear();
   EXPECT_THAT(map[1][1], IsEmpty());
 }

+ 15 - 13
test/recursive_iterator_mixed_container_test.cxx

@@ -6,9 +6,11 @@
 #include "ranges.h"
 #include "xcode_gtest_helper.h"
 
+using iterator::recursive_iterator;
+
 TEST(RecursiveIteratorMapVectorTest, IterDistanceIsSumOfInnerContainerSizes) {
   std::map<int, std::vector<int>> const obj{{1, {1, 2}}, {2, {3, 4, 5}}};
-  auto rit = make_recursive_iterator(obj);
+  auto rit = recursive_iterator(obj);
 
   EXPECT_EQ(ranges::distance(rit, iterator::sentinel), 5);
 }
@@ -17,7 +19,7 @@ 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 = make_recursive_iterator(obj);
+  auto rit = recursive_iterator(obj);
 
   EXPECT_EQ(
       (ranges::to<std::vector<std::tuple<int, int>>>(rit, iterator::sentinel)),
@@ -26,7 +28,7 @@ TEST(RecursiveIteratorMapVectorTest, ElementsAreUnwrappedAsATuple) {
 
 TEST(RecursiveIteratorMapVectorTest, CanMutatePointedToData) {
   std::map<int, std::vector<int>> obj{{1, {1, 2}}, {2, {3, 4, 5}}};
-  auto rit = make_recursive_iterator(obj);
+  auto rit = recursive_iterator(obj);
   std::get<1>(*rit) = 6;
   EXPECT_EQ(obj[1][0], 6);
 }
@@ -34,7 +36,7 @@ TEST(RecursiveIteratorMapVectorTest, CanMutatePointedToData) {
 TEST(RecursiveIteratorMapMapVectorTest, CanMutatePointedToData) {
   std::map<int, std::map<int, std::vector<int>>> obj{{1, {{1, {1, 2}}}},
                                                      {2, {{1, {3, 4, 5}}}}};
-  auto rit = make_recursive_iterator(obj);
+  auto rit = recursive_iterator(obj);
   std::get<2>(*rit) = 6;
   EXPECT_EQ(obj[1][1][0], 6);
 }
@@ -42,7 +44,7 @@ TEST(RecursiveIteratorMapMapVectorTest, CanMutatePointedToData) {
 TEST(RecursiveIteratorVectorMapTest, IterDistanceIsSumOfInnerContainerSizes) {
   std::vector<std::map<int, int>> const obj{{{1, 1}, {2, 2}},
                                             {{3, 3}, {4, 4}, {5, 5}}};
-  auto rit = make_recursive_iterator(obj);
+  auto rit = recursive_iterator(obj);
 
   EXPECT_EQ(ranges::distance(rit, iterator::sentinel), 5);
 }
@@ -52,7 +54,7 @@ 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 = make_recursive_iterator(obj);
+  auto rit = recursive_iterator(obj);
 
   EXPECT_EQ(
       (ranges::to<std::vector<std::pair<int, int>>>(rit, iterator::sentinel)),
@@ -62,7 +64,7 @@ TEST(RecursiveIteratorVectorMapTest, ElementsAreUnwrappedAsATuple) {
 TEST(RecursiveIteratorVectorMapTest, CanMutatePointedToData) {
   std::vector<std::map<int, int>> obj{{{1, 1}, {2, 2}},
                                       {{3, 3}, {4, 4}, {5, 5}}};
-  auto rit = make_recursive_iterator(obj);
+  auto rit = recursive_iterator(obj);
   std::get<1>(*rit) = 6;
   EXPECT_EQ(obj[0][1], 6);
 }
@@ -71,7 +73,7 @@ TEST(RecursiveIteratorMapVecMapTest, IterDistanceIsSumOfInnerContainerSizes) {
   std::map<int, std::vector<std::map<int, int>>> const obj{
       {1, {{{1, 1}, {2, 2}}}}};
   std::vector<std::tuple<int, int, int>> const expected{{1, 1, 1}, {1, 2, 2}};
-  auto rit = make_recursive_iterator(obj);
+  auto rit = recursive_iterator(obj);
 
   EXPECT_EQ(ranges::distance(rit, iterator::sentinel), expected.size());
 }
@@ -80,7 +82,7 @@ TEST(RecursiveIteratorMapVecMapTest, ElementsAreUnwrappedAsATuple) {
   std::map<int, std::vector<std::map<int, int>>> const obj{
       {1, {{{1, 1}, {2, 2}}}}};
   std::vector<std::tuple<int, int, int>> const expected{{1, 1, 1}, {1, 2, 2}};
-  auto rit = make_recursive_iterator(obj);
+  auto rit = recursive_iterator(obj);
 
   EXPECT_EQ((ranges::to<std::vector<std::tuple<int, int, int>>>(
                 rit, iterator::sentinel)),
@@ -89,7 +91,7 @@ TEST(RecursiveIteratorMapVecMapTest, ElementsAreUnwrappedAsATuple) {
 
 TEST(RecursiveIteratorMapVecMapTest, CanMutatePointedToData) {
   std::map<int, std::vector<std::map<int, int>>> obj{{1, {{{1, 1}, {2, 2}}}}};
-  auto rit = make_recursive_iterator(obj);
+  auto rit = recursive_iterator(obj);
   std::get<2>(*rit) = 4;
   EXPECT_EQ(obj[1][0][1], 4);
 }
@@ -97,7 +99,7 @@ TEST(RecursiveIteratorMapVecMapTest, CanMutatePointedToData) {
 TEST(RecursiveIteratorVecMapVecTest, IterDistanceIsSumOfInnerContainerSizes) {
   std::vector<std::map<int, std::vector<int>>> const obj{
       {{1, {1, 2}}, {2, {3, 4, 5}}}, {{1, {3, 4}}}};
-  auto rit = make_recursive_iterator(obj);
+  auto rit = recursive_iterator(obj);
 
   EXPECT_EQ(ranges::distance(rit, iterator::sentinel), 7);
 }
@@ -107,7 +109,7 @@ 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 = make_recursive_iterator(obj);
+  auto rit = recursive_iterator(obj);
 
   EXPECT_EQ(
       (ranges::to<std::vector<std::tuple<int, int>>>(rit, iterator::sentinel)),
@@ -117,7 +119,7 @@ TEST(RecursiveIteratorVecMapVecTest, ElementsAreUnwrappedAsATuple) {
 TEST(RecursiveIteratorVecMapVecTest, CanMutatePointedToData) {
   std::vector<std::map<int, std::vector<int>>> obj{
       {{1, {1, 2}}, {2, {3, 4, 5}}}, {{1, {3, 4}}}};
-  auto rit = make_recursive_iterator(obj);
+  auto rit = recursive_iterator(obj);
   std::get<1>(*rit) = 6;
   EXPECT_EQ(obj[0][1][0], 6);
 }

+ 8 - 6
test/recursive_iterator_single_level_test.cxx

@@ -14,44 +14,46 @@
 #include "ranges.h"
 #include "xcode_gtest_helper.h"
 
+using iterator::recursive_iterator;
+
 TEST(RecursiveIteratorSingleVectorTest, IterDistanceIsContainerSize) {
   std::vector<int> const vec{1, 2, 3, 4, 5};
-  auto rit = make_recursive_iterator(vec);
+  auto rit = recursive_iterator(vec);
 
   EXPECT_THAT(ranges::distance(rit, iterator::sentinel), vec.size());
 }
 
 TEST(RecursiveIteratorSingleVectorTest, DataMatchesContainerIterator) {
   std::vector<int> const vec{1, 2, 3, 4, 5};
-  auto rit = make_recursive_iterator(vec);
+  auto rit = recursive_iterator(vec);
 
   EXPECT_THAT(ranges::to<std::vector<int>>(rit, iterator::sentinel), vec);
 }
 
 TEST(RecursiveIteratorSingleVectorTest, CanMutatePointedToData) {
   std::vector<int> vec{1, 2, 3, 4, 5};
-  auto rit = make_recursive_iterator(vec);
+  auto rit = recursive_iterator(vec);
   *rit = 6;
   EXPECT_THAT(vec[0], 6);
 }
 
 TEST(RecursiveIteratorSingleMapTest, IterDistanceIsContainerSize) {
   std::map<int, int> const map{{1, 1}, {2, 2}, {3, 3}};
-  auto rit = make_recursive_iterator(map);
+  auto rit = recursive_iterator(map);
 
   EXPECT_THAT(ranges::distance(rit, iterator::sentinel), map.size());
 }
 
 TEST(RecursiveIteratorSingleMapTest, DataMatchesContainerIterator) {
   std::map<int, int> const map{{1, 1}, {2, 2}, {3, 3}};
-  auto rit = make_recursive_iterator(map);
+  auto rit = recursive_iterator(map);
 
   EXPECT_THAT((ranges::to<std::map<int, int>>(rit, iterator::sentinel)), map);
 }
 
 TEST(RecursiveIteratorSingleMapTest, CanMutatePointedToData) {
   std::map<int, int> map{{1, 1}, {2, 2}, {3, 3}};
-  auto rit = make_recursive_iterator(map);
+  auto rit = recursive_iterator(map);
   std::get<1>(*rit) = 4;
   EXPECT_THAT(map[1], 4);
 }

+ 15 - 10
test/recursive_iterator_vector_test.cxx

@@ -2,14 +2,19 @@
 
 #include <vector>
 
+#include <iterator/forwards.h>
+
 #include "ranges.h"
 #include "xcode_gtest_helper.h"
 
+using iterator::bounded;
+using iterator::recursive_iterator;
+
 using testing::IsEmpty;
 
 TEST(RecursiveIteratorVecTest, PreIncrementAdvancesIterator) {
   std::vector<std::vector<std::vector<int>>> const vec{{{1, 2}}, {{3}, {4, 5}}};
-  auto rit = make_recursive_iterator(vec);
+  auto rit = recursive_iterator(vec);
   EXPECT_THAT(*rit, 1);
   EXPECT_THAT(*++rit, 2);
   EXPECT_THAT(*rit, 2);
@@ -17,7 +22,7 @@ TEST(RecursiveIteratorVecTest, PreIncrementAdvancesIterator) {
 
 TEST(RecursiveIteratorVecTest, PostIncrementReturnsCopyOfPrev) {
   std::vector<std::vector<std::vector<int>>> const vec{{{1, 2}}, {{3}, {4, 5}}};
-  auto rit = make_recursive_iterator(vec);
+  auto rit = recursive_iterator(vec);
   EXPECT_THAT(*rit, 1);
   EXPECT_THAT(*rit++, 1);
   EXPECT_THAT(*rit, 2);
@@ -25,14 +30,14 @@ TEST(RecursiveIteratorVecTest, PostIncrementReturnsCopyOfPrev) {
 
 TEST(RecursiveIteratorVecTest, IterDistanceIsSumOfInnerContainerSizes) {
   std::vector<std::vector<std::vector<int>>> const vec{{{1, 2}}, {{3}, {4, 5}}};
-  auto rit = make_recursive_iterator(vec);
+  auto rit = recursive_iterator(vec);
 
   EXPECT_THAT(ranges::distance(rit, iterator::sentinel), 5);
 }
 
 TEST(RecursiveIteratorVecTest, FlattensVectorDataLikeJoinIterator) {
   std::vector<std::vector<std::vector<int>>> const vec{{{1, 2}}, {{3}, {4, 5}}};
-  auto rit = make_recursive_iterator(vec);
+  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);
@@ -40,14 +45,14 @@ TEST(RecursiveIteratorVecTest, FlattensVectorDataLikeJoinIterator) {
 
 TEST(RecursiveIteratorVecTest, CanMutatePointedToData) {
   std::vector<std::vector<std::vector<int>>> vec{{{1, 2}}, {{3}, {4, 5}}};
-  auto rit = make_recursive_iterator(vec);
+  auto rit = recursive_iterator(vec);
   *rit = 6;
   EXPECT_THAT(vec[0][0][0], 6);
 }
 
 TEST(BoundedRecursiveIteratorVecTest, PreIncrementAdvancesIterator) {
   std::vector<std::vector<std::vector<int>>> const vec{{{1, 2}}, {{3}, {4, 5}}};
-  auto rit = make_recursive_iterator<2>(vec);
+  auto rit = recursive_iterator(vec, bounded<2>{});
   EXPECT_THAT(*rit, vec[0][0]);
   EXPECT_THAT(*++rit, vec[1][0]);
   EXPECT_THAT(*rit, vec[1][0]);
@@ -55,7 +60,7 @@ TEST(BoundedRecursiveIteratorVecTest, PreIncrementAdvancesIterator) {
 
 TEST(BoundedRecursiveIteratorVecTest, PostIncrementReturnsCopyOfPrev) {
   std::vector<std::vector<std::vector<int>>> const vec{{{1, 2}}, {{3}, {4, 5}}};
-  auto rit = make_recursive_iterator<2>(vec);
+  auto rit = recursive_iterator(vec, bounded<2>{});
   EXPECT_THAT(*rit, vec[0][0]);
   EXPECT_THAT(*rit++, vec[0][0]);
   EXPECT_THAT(*rit, vec[1][0]);
@@ -63,14 +68,14 @@ TEST(BoundedRecursiveIteratorVecTest, PostIncrementReturnsCopyOfPrev) {
 
 TEST(BoundedRecursiveIteratorVecTest, IterDistanceSumOnNLayersSize) {
   std::vector<std::vector<std::vector<int>>> const vec{{{1, 2}}, {{3}, {4, 5}}};
-  auto rit = make_recursive_iterator<2>(vec);
+  auto rit = recursive_iterator(vec, bounded<2>{});
 
   EXPECT_THAT(ranges::distance(rit, iterator::sentinel), 3);
 }
 
 TEST(BoundedRecursiveIteratorVecTest, ElementsAreUnwrappedAsATuple) {
   std::vector<std::vector<std::vector<int>>> const vec{{{1, 2}}, {{3}, {4, 5}}};
-  auto rit = make_recursive_iterator<2>(vec);
+  auto rit = recursive_iterator(vec, bounded<2>{});
 
   std::vector<std::vector<int>> const expected{{1, 2}, {3}, {4, 5}};
   EXPECT_THAT(
@@ -80,7 +85,7 @@ TEST(BoundedRecursiveIteratorVecTest, ElementsAreUnwrappedAsATuple) {
 
 TEST(BoundedRecursiveIteratorVecTest, CanMutatePointedToData) {
   std::vector<std::vector<std::vector<int>>> vec{{{1, 2}}, {{3}, {4, 5}}};
-  auto rit = make_recursive_iterator<2>(vec);
+  auto rit = recursive_iterator(vec, bounded<2>{});
   rit->clear();
   EXPECT_THAT(vec[0][0], IsEmpty());
 }