Selaa lähdekoodia

Merge branch 'major/fixes'

* major/fixes:
  refactor: add sentinel difference features (TODO: move back to facade)
  feat: add support for capturing the proper category for filter/indexed
  breaking: implement a category enum instead of using deduction
  refactor: improve SFINAE macro to allow auto
  refactor: add end_aware_iterator::end
  clean: remove unused zip iterator test
  refactor: make end_aware_iterator a proxy to allow random/bidirectional
  refactor: add FWD(X) macro refactor: delete sentinel_iterator as only used by ranges::common_view
  fix: permit the default-construction of filter iterators with lambdas
  fix: fix bugs in the construction of sentinel_iterator, and with unbound iteration
  cleanup: remove unused value_proxy/ignore_proxy
  refactor: clean up filter_iterator, allow non std::function predicates
  refactor: re-implement joining_iterator using capture_iterator as needed
  feat: add capture iterator to replace filter<It, Cache> concept
  fix: better SFINAE chore: re-arrange files
  feat: allow propagation of sentinels
  refactor: extract facade traits to a separate header
Sam Jaffe 2 kuukautta sitten
vanhempi
commit
64bc633f43
36 muutettua tiedostoa jossa 1039 lisäystä ja 824 poistoa
  1. 56 0
      include/iterator/capture_iterator.h
  2. 0 31
      include/iterator/detail/arrow_proxy.h
  3. 25 0
      include/iterator/detail/macro.h
  4. 79 21
      include/iterator/detail/traits.h
  5. 21 0
      include/iterator/detail/undef.h
  6. 76 0
      include/iterator/end_aware_iterator.h
  7. 0 65
      include/iterator/end_aware_iterator.hpp
  8. 45 97
      include/iterator/facade.h
  9. 87 0
      include/iterator/filter_iterator.h
  10. 0 76
      include/iterator/filter_iterator.hpp
  11. 46 0
      include/iterator/forwards.h
  12. 28 7
      include/iterator/indexed_iterator.hpp
  13. 0 36
      include/iterator/iterator_fwd.hpp
  14. 109 0
      include/iterator/join_iterator.h
  15. 0 132
      include/iterator/join_iterator.hpp
  16. 55 33
      include/iterator/proxy.h
  17. 15 9
      include/iterator/recursive_iterator.hpp
  18. 2 0
      include/iterator/sentinel.h
  19. 0 47
      include/iterator/sentinel_iterator.h
  20. 2 2
      include/iterator/unkeyed_iterator.hpp
  21. 9 11
      include/iterator/zip_iterator.hpp
  22. 31 21
      iterator.xcodeproj/project.pbxproj
  23. 43 0
      test/capture_iterator_test.cxx
  24. 32 33
      test/end_aware_iterator_test.cxx
  25. 41 31
      test/filter_iterator_test.cxx
  26. 21 14
      test/indexed_iterator_test.cxx
  27. 17 16
      test/join_iterator_test.cxx
  28. 40 0
      test/ranges.h
  29. 41 33
      test/recursive_iterator_accessors_test.cxx
  30. 29 24
      test/recursive_iterator_map_test.cxx
  31. 32 26
      test/recursive_iterator_mixed_container_test.cxx
  32. 10 9
      test/recursive_iterator_single_level_test.cxx
  33. 15 13
      test/recursive_iterator_vector_test.cxx
  34. 3 9
      test/unkeyed_iterator_test.cxx
  35. 4 0
      test/xcode_gtest_helper.h
  36. 25 28
      test/zip_iterator_test.cxx

+ 56 - 0
include/iterator/capture_iterator.h

@@ -0,0 +1,56 @@
+//
+//  capture_iterator.h
+//  iterator
+//
+//  Created by Sam Jaffe on 4/1/23.
+//  Copyright © 2023 Sam Jaffe. All rights reserved.
+//
+
+#pragma once
+
+#include <iterator/forwards.h>
+#include <iterator/proxy.h>
+
+#include <iterator/detail/macro.h>
+
+namespace iterator {
+
+template <typename It>
+class capture_iterator : public proxy<It, capture_iterator<It>> {
+public:
+  using super_t = proxy<It, capture_iterator<It>>;
+  using value_type = typename std::iterator_traits<It>::value_type;
+  using difference_type = typename std::iterator_traits<It>::difference_type;
+
+private:
+  value_type cache_;
+
+public:
+  capture_iterator() = default;
+  capture_iterator(It it) : super_t(it) { cache_ = super_t::dereference(); }
+
+  value_type const & dereference() const { return cache_; }
+
+  SFINAE(super_t::category_enum >= category::forward) void increment() {
+    super_t::increment();
+    cache_ = super_t::dereference();
+  }
+
+  SFINAE(super_t::category_enum >= category::bidirectional)
+  void decrement() {
+    super_t::decrement();
+    cache_ = super_t::dereference();
+  }
+
+  SFINAE(super_t::category_enum >= category::random_access)
+  void advance(difference_type off) {
+    super_t::advance(off);
+    cache_ = super_t::dereference();
+  }
+};
+
+template <typename It> capture_iterator(It) -> capture_iterator<It>;
+
+}
+
+#include <iterator/detail/undef.h>

+ 0 - 31
include/iterator/detail/arrow_proxy.h

@@ -11,35 +11,4 @@ template <typename Reference> struct arrow_proxy {
 };
 
 template <typename R> arrow_proxy(R r) -> arrow_proxy<R>;
-
-template <typename Value> struct value_proxy {
-  value_proxy operator=(Value && value) {
-    this->value = std::move(value);
-    return *this;
-  }
-
-  operator Value const &() const { return value; }
-  Value const & get() const { return value; }
-
-  Value value;
-};
-
-template <typename Value> struct value_proxy<Value &> {
-  value_proxy operator=(Value & value) {
-    this->value = &value;
-    return *this;
-  }
-
-  operator Value &() const { return *value; }
-  Value & get() const { return *value; }
-
-  Value * value;
-};
-
-struct ignore_proxy {
-  template <typename Value> decltype(auto) operator=(Value && value) {
-    return std::forward<Value>(value);
-  }
-};
-
 }

+ 25 - 0
include/iterator/detail/macro.h

@@ -0,0 +1,25 @@
+//
+//  macro.h
+//  iterator
+//
+//  Created by Sam Jaffe on 4/1/23.
+//  Copyright © 2023 Sam Jaffe. All rights reserved.
+//
+
+#ifndef _ITERATOR_MACRO_H
+#define _ITERATOR_MACRO_H
+
+#define FWD(x) std::forward<decltype(x)>(x)
+
+#define REQUIRES_T(trait, rval, ...) std::enable_if_t<trait, rval>
+#define REQUIRES(trait) typename = REQUIRES_T(trait, void)
+
+#define SFINAE(trait) template <bool _ = true, REQUIRES(trait && _)>
+
+#define VAL(X) std::declval<X>()
+#define DEREF_TYPE(X) decltype(*VAL(X))
+#define TYPE(X, EXPR) decltype(VAL(X).EXPR)
+#define EXISTS(EXPR) std::void_t<decltype(EXPR)>
+#define DEFER(TYPE) (std::is_void_v<TYPE> || true)
+
+#endif

+ 79 - 21
include/iterator/detail/traits.h

@@ -1,38 +1,96 @@
 #pragma once
 
-#define _val(type) std::declval<type>()
-#define exists(expr) std::void_t<decltype(expr)>
+#include <algorithm>
+#include <iterator>
+#include <type_traits>
 
-#include <iterator/iterator_fwd.hpp>
+#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 {
-template <typename T, typename = void> struct reference_helper {
-  using type = decltype(*std::declval<T>());
+// 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 T>
-struct reference_helper<T, std::void_t<typename T::reference>> {
-  using type = typename T::reference;
+template <typename It>
+struct sentinel_type<It, std::void_t<typename It::sentinel_type>> {
+  using type = typename It::sentinel_type;
 };
+}
 
-template <typename T, typename = void> struct value_type_helper {
-  using reference = typename reference_helper<T>::type;
-  using type = std::remove_cv_t<std::remove_reference_t<reference>>;
+// 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 <typename T>
-struct value_type_helper<T, std::void_t<typename T::value_type>> {
-  using type = typename T::value_type;
+
+template <category Limit>
+struct category_for<std::forward_iterator_tag, Limit> {
+  constexpr static category value = std::min(category::forward, Limit);
 };
 
-template <typename T> using value_type = typename value_type_helper<T>::type;
-template <typename T> using reference = typename reference_helper<T>::type;
+template <category Limit>
+struct category_for<std::bidirectional_iterator_tag, Limit> {
+  constexpr static category value = std::min(category::bidirectional, Limit);
+};
 
-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 <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<decltype(*std::declval<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>

+ 21 - 0
include/iterator/detail/undef.h

@@ -0,0 +1,21 @@
+//
+//  undef.h
+//  iterator
+//
+//  Created by Sam Jaffe on 4/1/23.
+//  Copyright © 2023 Sam Jaffe. All rights reserved.
+//
+
+#ifdef _ITERATOR_MACRO_H
+#undef _ITERATOR_MACRO_H
+
+#undef FWD
+#undef SFINAE
+#undef REQUIRES
+#undef REQUIRES_T
+#undef EXISTS
+#undef TYPE
+#undef DEREF_TYPE
+#undef VAL
+
+#endif

+ 76 - 0
include/iterator/end_aware_iterator.h

@@ -0,0 +1,76 @@
+//
+//  end_aware_iterator.h
+//  iterator
+//
+//  Created by Sam Jaffe on 2/7/17.
+//
+
+#pragma once
+
+#include <iterator/detail/traits.h>
+#include <iterator/forwards.h>
+#include <iterator/proxy.h>
+#include <iterator/sentinel.h>
+
+#include <iterator/detail/macro.h>
+
+namespace iterator {
+/**
+ * @class end_aware_iterator
+ * @brief An iterator that keeps track of the relative end of the range.
+ *
+ * @tparam It The underlying iterator type
+ */
+template <typename It>
+class end_aware_iterator : public proxy<It, end_aware_iterator<It>> {
+public:
+  using super_t = proxy<It, end_aware_iterator<It>>;
+  using sentinel_type = sentinel_t;
+
+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");
+  }
+
+  template <typename Ot>
+  end_aware_iterator(end_aware_iterator<Ot> const & other)
+      : super_t(other.impl()), end_(other.end_) {}
+
+  bool at_end() const {
+    if constexpr (super_t::category_enum == category::random_access) {
+      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) {
+    return self.end() - self.impl();
+  }
+
+protected:
+  It end() const { return end_; }
+
+private:
+  template <typename O> friend class end_aware_iterator;
+  It end_;
+};
+
+template <typename C>
+end_aware_iterator(C &&) -> end_aware_iterator<detail::iter<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>

+ 0 - 65
include/iterator/end_aware_iterator.hpp

@@ -1,65 +0,0 @@
-//
-//  end_aware_iterator.hpp
-//  iterator
-//
-//  Created by Sam Jaffe on 2/7/17.
-//
-
-#pragma once
-
-#include <iterator/detail/traits.h>
-#include <iterator/facade.h>
-#include <iterator/iterator_fwd.hpp>
-#include <iterator/sentinel.h>
-
-namespace iterator {
-/**
- * @class end_aware_iterator
- * @brief An iterator that keeps track of the relative end of the range.
- *
- * @tparam It The underlying iterator type
- */
-template <typename It>
-class end_aware_iterator : public facade<end_aware_iterator<It>> {
-public:
-  using sentinel_type = sentinel_t;
-
-public:
-  end_aware_iterator() = default;
-  end_aware_iterator(It it, It end) : curr_(it), end_(end) {}
-
-  template <typename C, typename = std::enable_if_t<detail::is_container_v<C>>>
-  end_aware_iterator(C && container)
-      : curr_(std::begin(container)), end_(std::end(container)) {
-    static_assert(std::is_reference_v<C>,
-                  "Cannot access iterator of a temporary");
-  }
-
-  template <typename Ot>
-  end_aware_iterator(end_aware_iterator<Ot> const & other)
-      : curr_(other.curr_), end_(other.end_) {}
-
-  end_aware_iterator(end_aware_iterator<It> other, end_aware_iterator<It>)
-      : curr_(other.curr_), end_(other.end_) {}
-
-  decltype(auto) dereference() const { return *curr_; }
-  void increment() { ++curr_; }
-  bool at_end() const { return curr_ == end_; }
-  bool equal_to(end_aware_iterator const & other) const {
-    // TODO: Fix this clause
-    return (at_end() && other.at_end()) || curr_ == other.curr_;
-  }
-
-private:
-  template <typename O> friend class end_aware_iterator;
-  It curr_, end_;
-};
-
-template <typename C> end_aware_iterator(C &&) -> end_aware_iterator<iter<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);

+ 45 - 97
include/iterator/facade.h

@@ -6,82 +6,33 @@
 #include <iterator/detail/arrow_proxy.h>
 #include <iterator/detail/traits.h>
 
-namespace iterator::detail {
-template <typename, typename = void> struct has_equal_to : std::false_type {};
-template <typename T>
-struct has_equal_to<T, exists(_val(T).equal_to(_val(T)))> : std::true_type {};
-
-template <typename, typename = void>
-struct has_distance_to : std::false_type {};
-template <typename T>
-struct has_distance_to<T, exists(_val(T).distance_to(_val(T)))>
-    : std::true_type {};
-
-template <typename, typename = void>
-struct is_advanceable_iterator : std::false_type {};
-template <typename T>
-struct is_advanceable_iterator<T, exists(_val(T).advance({}))>
-    : std::true_type {};
-
-template <typename, typename = void>
-struct is_single_pass_iterator : std::false_type {};
-template <typename T>
-struct is_single_pass_iterator<T, std::void_t<typename T::single_pass_iterator>>
-    : std::true_type {};
-
-template <typename, typename = void> struct has_increment : std::false_type {};
-template <typename T>
-struct has_increment<T, exists(_val(T).increment())> : std::true_type {};
-
-template <typename, typename = void> struct has_decrement : std::false_type {};
-template <typename T>
-struct has_decrement<T, exists(_val(T).decrement())> : std::true_type {};
+#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, std::enable_if_t<has_distance_to<T>{}>> {
-  using type = decltype(_val(T).distance_to(_val(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;
-template <typename T> constexpr bool has_equal_to_v = has_equal_to<T>{};
-template <typename T> constexpr bool has_distance_to_v = has_distance_to<T>{};
-template <typename T> constexpr bool has_increment_v = has_increment<T>{};
-template <typename T> constexpr bool has_decrement_v = has_decrement<T>{};
-template <typename T>
-constexpr bool is_advanceable_iterator_v = is_advanceable_iterator<T>{};
-
-template <typename T>
-constexpr bool is_random_access_iterator_v =
-    has_distance_to_v<T> && is_advanceable_iterator_v<T>;
-template <typename T>
-constexpr bool is_bidirectional_iterator_v =
-    has_decrement_v<T> && has_increment_v<T> && has_equal_to_v<T>;
-template <typename T>
-constexpr bool is_single_pass_iterator_v = is_single_pass_iterator<T>{};
-
-template <typename T>
-using iterator_category_t = std::conditional_t<
-    is_random_access_iterator_v<T>, std::random_access_iterator_tag,
-    std::conditional_t<is_bidirectional_iterator_v<T>,
-                       std::bidirectional_iterator_tag,
-                       std::conditional_t<is_single_pass_iterator_v<T>,
-                                          std::input_iterator_tag,
-                                          std::forward_iterator_tag>>>;
 }
 
-#undef _val
-#undef exists
-
 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 self_type> class facade {
+template <typename CRTP, category C> class facade {
+private:
+  using self_type = CRTP;
+
+public:
+  constexpr static auto category_enum = C;
+
 public:
   decltype(auto) operator*() const { return self().dereference(); }
 
@@ -93,13 +44,14 @@ public:
     }
   }
 
-  template <typename D, typename = difference_type_arg_t<D, self_type>>
+  template <typename D, typename = difference_type_arg_t<D, self_type>,
+            REQUIRES(DEFER(D) && C == category::random_access)>
   decltype(auto) operator[](D off) const {
     return *(self() + off);
   }
 
   self_type & operator++() {
-    if constexpr (detail::is_advanceable_iterator_v<self_type>) {
+    if constexpr (C == category::random_access) {
       self() += 1;
     } else {
       self().increment();
@@ -108,7 +60,7 @@ public:
   }
 
   auto operator++(int) {
-    if constexpr (detail::is_single_pass_iterator_v<self_type>) {
+    if constexpr (C == category::single_pass) {
       ++*this;
     } else {
       auto tmp = self();
@@ -117,8 +69,9 @@ public:
     }
   }
 
+  SFINAE(C >= category::bidirectional)
   self_type & operator--() {
-    if constexpr (detail::is_advanceable_iterator_v<self_type>) {
+    if constexpr (C == category::random_access) {
       self() -= 1;
     } else {
       self().decrement();
@@ -126,55 +79,52 @@ public:
     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>>
+  template <typename D, typename = difference_type_arg_t<D, self_type>,
+            REQUIRES(DEFER(D) && C == category::random_access)>
   friend self_type & operator+=(self_type & self, D off) {
-    static_assert(detail::is_advanceable_iterator_v<self_type>,
-                  "must be advancable");
     self.advance(off);
     return self;
   }
 
-  template <typename D, typename = difference_type_arg_t<D, self_type>>
+  template <typename D, typename = difference_type_arg_t<D, self_type>,
+            REQUIRES(DEFER(D) && C == category::random_access)>
   friend self_type & operator-=(self_type & self, D off) {
-    static_assert(detail::is_advanceable_iterator_v<self_type>,
-                  "must be advancable");
     self.advance(-off);
     return self;
   }
 
-  template <typename D, typename = difference_type_arg_t<D, self_type>>
+  template <typename D, typename = difference_type_arg_t<D, self_type>,
+            REQUIRES(DEFER(D) && C == category::random_access)>
   friend auto operator+(self_type self, D off) {
-    static_assert(detail::is_advanceable_iterator_v<self_type>,
-                  "must be advancable");
     return self += off;
   }
 
-  template <typename D, typename = difference_type_arg_t<D, self_type>>
+  template <typename D, typename = difference_type_arg_t<D, self_type>,
+            REQUIRES(DEFER(D) && C == category::random_access)>
   friend auto operator+(D off, self_type self) {
-    static_assert(detail::is_advanceable_iterator_v<self_type>,
-                  "must be advancable");
     return self += off;
   }
 
-  template <typename D, typename = difference_type_arg_t<D, self_type>>
+  template <typename D, typename = difference_type_arg_t<D, self_type>,
+            REQUIRES(DEFER(D) && C == category::random_access)>
   friend auto operator-(self_type self, D off) {
-    static_assert(detail::is_advanceable_iterator_v<self_type>,
-                  "must be advancable");
     return self -= off;
   }
 
+  SFINAE(C == category::random_access)
   friend auto operator-(self_type const & left, self_type const & right) {
     return right.distance_to(left);
   }
 
   friend bool operator==(self_type const & left, self_type const & right) {
-    if constexpr (detail::has_distance_to_v<self_type>) {
+    if constexpr (C == category::random_access) {
       return (left - right) == 0;
     } else {
       return left.equal_to(right);
@@ -185,23 +135,27 @@ public:
     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;
   }
 
-private:
+protected:
   self_type & self() { return *static_cast<self_type *>(this); }
   self_type const & self() const {
     return *static_cast<self_type const *>(this);
@@ -211,20 +165,14 @@ private:
 
 // In C++20, a concept/requires could be used to eschew the need for the below
 // macros.
-template <typename I> struct std::iterator_traits<::iterator::facade<I>> {
-  using reference = decltype(*std::declval<I>());
-  using value_type = std::remove_cv_t<std::remove_reference_t<reference>>;
-  using pointer = decltype(std::declval<I>().operator->());
-  using difference_type = ::iterator::detail::distance_to_t<I>;
-  using iterator_category = ::iterator::detail::iterator_category_t<I>;
-};
-
-#define MAKE_ITERATOR_FACADE_TYPEDEFS(type)                                    \
-  template <>                                                                  \
-  struct std::iterator_traits<type>                                            \
-      : std::iterator_traits<::iterator::facade<type>> {}
+#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>;             \
+  }
 
-#define MAKE_ITERATOR_FACADE_TYPEDEFS_T(type)                                  \
-  template <typename... T>                                                     \
-  struct std::iterator_traits<type<T...>>                                      \
-      : std::iterator_traits<::iterator::facade<type<T...>>> {}
+#include <iterator/detail/undef.h>

+ 87 - 0
include/iterator/filter_iterator.h

@@ -0,0 +1,87 @@
+//
+//  filter_iterator.h
+//  iterator
+//
+//  Created by Sam Jaffe on 6/17/17.
+//
+
+#pragma once
+
+#include <functional>
+
+#include <iterator/detail/traits.h>
+#include <iterator/end_aware_iterator.h>
+#include <iterator/facade.h>
+#include <iterator/forwards.h>
+
+#include <iterator/detail/macro.h>
+
+namespace iterator {
+template <typename Iter, typename Pred>
+class filter_iterator
+    : public facade<filter_iterator<Iter, Pred>,
+                    detail::category_for_v<Iter, category::bidirectional>> {
+public:
+  using sentinel_type = sentinel_t;
+
+  using super_t = facade<filter_iterator<Iter, Pred>,
+                         detail::category_for_v<Iter, category::bidirectional>>;
+
+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)) {
+    if (should_advance()) { increment(); }
+  }
+
+  filter_iterator(Iter curr, Iter end, Pred pred)
+      : base_(end_aware_iterator(curr, end)), pred_(std::move(pred)) {
+    if (should_advance()) { increment(); }
+  }
+
+  filter_iterator(end_aware_iterator<Iter> curr, Pred pred)
+      : base_(curr), pred_(std::move(pred)) {
+    if (should_advance()) { increment(); }
+  }
+
+  decltype(auto) dereference() const { return base_.dereference(); }
+
+  void increment() {
+    do {
+      ++base_;
+    } while (should_advance());
+  }
+
+  SFINAE(super_t::category_enum == category::bidirectional) void decrement() {
+    do {
+      --base_;
+    } while (should_advance());
+  }
+
+  bool at_end() const { return base_.at_end(); }
+  bool equal_to(filter_iterator const & other) const {
+    return base_ == other.base_;
+  }
+
+public:
+  bool should_advance() {
+    return !base_.at_end() && !std::invoke(pred_, dereference());
+  }
+
+  end_aware_iterator<Iter> base_;
+  Pred pred_;
+};
+
+template <typename C, typename P>
+filter_iterator(C &&, P) -> filter_iterator<detail::iter<C>, P>;
+template <typename It, typename P>
+filter_iterator(P, 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>

+ 0 - 76
include/iterator/filter_iterator.hpp

@@ -1,76 +0,0 @@
-//
-//  filter_iterator.hpp
-//  iterator
-//
-//  Created by Sam Jaffe on 6/17/17.
-//
-
-#pragma once
-
-#include <functional>
-
-#include <iterator/detail/traits.h>
-#include <iterator/end_aware_iterator.hpp>
-#include <iterator/facade.h>
-#include <iterator/iterator_fwd.hpp>
-
-namespace iterator {
-template <typename Iter, bool Cache>
-class filter_iterator : public facade<filter_iterator<Iter, Cache>> {
-public:
-  using sentinel_type = sentinel_t;
-
-  using reference = typename std::iterator_traits<Iter>::reference;
-  using cache_t = std::conditional_t<Cache, detail::value_proxy<reference>,
-                                     detail::ignore_proxy>;
-  using predicate_t = std::function<bool(reference)>;
-
-public:
-  filter_iterator() = default;
-
-  template <typename C>
-  filter_iterator(predicate_t pred, C && c)
-      : filter_iterator(std::move(pred), std::begin(c), std::end(c)) {}
-
-  filter_iterator(predicate_t pred, Iter curr, Iter end)
-      : filter_iterator(std::move(pred), end_aware_iterator(curr, end)) {}
-
-  filter_iterator(predicate_t pred, end_aware_iterator<Iter> curr)
-      : base_(curr), pred_(std::move(pred)) {
-    if (should_advance()) { increment(); }
-  }
-
-  decltype(auto) dereference() const { return base_.dereference(); }
-  void increment() {
-    do {
-      base_.increment();
-    } while (should_advance());
-  }
-
-  bool at_end() const { return base_.at_end(); }
-  bool equal_to(filter_iterator const & other) const {
-    return base_ == other.base_;
-  }
-
-public:
-  bool should_advance() {
-    return !base_.at_end() && !pred_(cache_ = dereference());
-  }
-
-  end_aware_iterator<Iter> base_;
-  cache_t cache_;
-  predicate_t pred_;
-};
-
-template <typename P, typename C>
-filter_iterator(P, C &&) -> filter_iterator<iter<C>>;
-template <typename P, typename It>
-filter_iterator(P, end_aware_iterator<It>) -> filter_iterator<It>;
-template <typename P, typename It>
-filter_iterator(P, It, It) -> filter_iterator<It>;
-}
-
-template <typename It, bool B>
-struct std::iterator_traits<::iterator::filter_iterator<It, B>>
-    : std::iterator_traits<
-          ::iterator::facade<::iterator::filter_iterator<It, B>>> {};

+ 46 - 0
include/iterator/forwards.h

@@ -0,0 +1,46 @@
+//
+//  iterator_fwd.h
+//  iterator
+//
+//  Created by Sam Jaffe on 2/18/17.
+//
+
+#pragma once
+
+#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 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 CRTP, category C> class facade;
+
+template <typename Iterator, typename CRTP,
+          typename = typename std::iterator_traits<Iterator>::iterator_category>
+class proxy;
+}

+ 28 - 7
include/iterator/indexed_iterator.hpp

@@ -1,5 +1,5 @@
 //
-//  indexed_iterator.hpp
+//  indexed_iterator.h
 //  iterator
 //
 //  Created by Sam Jaffe on 3/5/17.
@@ -10,16 +10,21 @@
 #include <iterator>
 
 #include <iterator/facade.h>
-#include <iterator/iterator_fwd.hpp>
+#include <iterator/forwards.h>
+
+#include <iterator/detail/macro.h>
 
 namespace iterator {
 template <typename It>
-class indexed_iterator : public facade<indexed_iterator<It>> {
+class indexed_iterator
+    : public facade<indexed_iterator<It>, detail::category_for_v<It>> {
 public:
-  using reference =
-      std::pair<size_t, typename std::iterator_traits<It>::reference>;
+  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>>;
+
 public:
   indexed_iterator() = default;
   indexed_iterator(It base) : base_(base) {}
@@ -31,20 +36,36 @@ public:
 
   reference dereference() const { return {index_, *base_}; }
 
+  SFINAE(super_t::category_enum >= category::forward) void decrement() {
+    ++base_;
+    ++index_;
+  }
+
+  SFINAE(super_t::category_enum >= category::bidirectional) void increment() {
+    --base_;
+    --index_;
+  }
+
+  SFINAE(super_t::category_enum >= category::random_access)
   void advance(difference_type off) {
     base_ += off;
     index_ += off;
   }
 
-  // SFINAE means that if Iterator is not random access, then this still works
-  // TODO: Investigate using _index for comparisons instead of _base
+  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 {
     return other.base_ - base_;
   }
 
+  SFINAE(detail::has_sentinel_type_v<It>) bool at_end() const {
+    return base_ == typename It::sentinel_type();
+  }
+
 private:
   template <typename O> friend class indexed_iterator;
   It base_;

+ 0 - 36
include/iterator/iterator_fwd.hpp

@@ -1,36 +0,0 @@
-//
-//  iterator_fwd.hpp
-//  iterator
-//
-//  Created by Sam Jaffe on 2/18/17.
-//
-
-#pragma once
-
-#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 {
-template <typename> class end_aware_iterator;
-template <typename, bool = false> class filter_iterator;
-template <typename> class joining_iterator;
-template <typename> class unkeyed_iterator;
-template <typename...> class zip_iterator;
-
-template <typename Iter> using recursive_iterator = recursive::rimpl<Iter>;
-template <typename Iter, std::size_t N>
-using recursive_iterator_n = recursive::rimpl<Iter, recursive::bounded<N>>;
-
-template <typename> class facade;
-template <typename Iter, typename,
-          typename = typename std::iterator_traits<Iter>::iterator_category>
-class proxy;
-
-template <typename C> using iter = decltype(std::begin(std::declval<C>()));
-}

+ 109 - 0
include/iterator/join_iterator.h

@@ -0,0 +1,109 @@
+//
+//  join_iterator.h
+//  iterator
+//
+//  Created by Sam Jaffe on 2/7/17.
+//
+
+#pragma once
+
+#include <iterator>
+#include <memory>
+#include <utility>
+
+#include <iterator/capture_iterator.h>
+#include <iterator/end_aware_iterator.h>
+#include <iterator/facade.h>
+#include <iterator/forwards.h>
+
+#include <iterator/detail/macro.h>
+
+namespace iterator {
+template <typename It>
+class joining_iterator
+    : public facade<joining_iterator<It>, category::forward> {
+private:
+  template <typename Ot> friend class joining_iterator;
+  constexpr static bool requires_caching = detail::is_rvalue_iterator_v<It>;
+
+public:
+  using sentinel_type = sentinel_t;
+  using outer_iterator_t =
+      std::conditional_t<requires_caching,
+                         capture_iterator<end_aware_iterator<It>>,
+                         end_aware_iterator<It>>;
+  using inner_iterator_t =
+      end_aware_iterator<detail::iter<DEREF_TYPE(outer_iterator_t)>>;
+
+private:
+  outer_iterator_t outer_;
+  inner_iterator_t inner_;
+
+public:
+  joining_iterator() = default;
+
+  template <typename Ot>
+  joining_iterator(joining_iterator<Ot> const & other) : outer_(other.outer_) {
+    safely_init_inner_iterator(other.outer_, other.inner_);
+  }
+
+  template <typename C, REQUIRES(detail::is_container_v<C>)>
+  joining_iterator(C && container) : outer_(FWD(container)) {
+    update_iterator();
+  }
+
+  joining_iterator(outer_iterator_t outer) : outer_(outer) {
+    update_iterator();
+  }
+
+  joining_iterator(outer_iterator_t const & outer,
+                   inner_iterator_t const & inner)
+      : outer_(outer) {
+    safely_init_inner_iterator(outer, inner);
+  }
+
+  void increment() {
+    if ((++inner_).at_end()) {
+      ++outer_;
+      update_iterator();
+    }
+  }
+
+  decltype(auto) dereference() const { return *inner_; }
+
+  bool at_end() const { return outer_.at_end(); }
+  bool equal_to(joining_iterator const & other) const {
+    return outer_ == other.outer_ && inner_ == other.inner_;
+  }
+
+  auto outer_iterator() const { return outer_; }
+  auto inner_iterator() const { return inner_; }
+
+private:
+  template <typename OuterIt, typename InnerIt>
+  void safely_init_inner_iterator(OuterIt const & outer,
+                                  InnerIt const & inner) {
+    if constexpr (requires_caching) {
+      inner_ = *outer_;
+      std::advance(inner_, std::distance(InnerIt(*outer), inner));
+    } else {
+      inner_ = inner;
+    }
+  }
+
+  void update_iterator() {
+    while (!outer_.at_end() && (inner_ = *outer_).at_end()) {
+      ++outer_;
+    }
+  }
+};
+
+template <typename C>
+joining_iterator(C &&) -> joining_iterator<detail::iter<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>

+ 0 - 132
include/iterator/join_iterator.hpp

@@ -1,132 +0,0 @@
-//
-//  join_iterator.hpp
-//  iterator
-//
-//  Created by Sam Jaffe on 2/7/17.
-//
-
-#pragma once
-
-#include <iterator>
-#include <memory>
-#include <utility>
-
-#include <iterator/end_aware_iterator.hpp>
-#include <iterator/facade.h>
-#include <iterator/iterator_fwd.hpp>
-
-namespace iterator::joining {
-template <typename Iter, typename = void> class iterator {
-protected:
-  using inner_t = decltype(*std::declval<Iter>());
-
-public:
-  iterator() = default;
-
-  template <typename I>
-  iterator(iterator<I> const & other)
-      : iterator(other.joiner_, other.element_) {}
-
-  template <typename C, typename = std::enable_if_t<detail::is_container_v<C>>>
-  iterator(C && container)
-      : iterator(end_aware_iterator(std::forward<C>(container))) {}
-
-  iterator(end_aware_iterator<Iter> join) : iterator(join, {}, true) {}
-
-  iterator(end_aware_iterator<Iter> join,
-           end_aware_iterator<iter<inner_t>> elem, bool update = false)
-      : joiner_(join), element_(elem) {
-    if (update) { update_iterator(); }
-  }
-
-protected:
-  void update_iterator() {
-    while (!joiner_.at_end() &&
-           (element_ = end_aware_iterator(*joiner_)).at_end()) {
-      ++joiner_;
-    }
-  }
-
-protected:
-  template <typename, typename> friend class iterator;
-  end_aware_iterator<Iter> joiner_;
-  end_aware_iterator<iter<inner_t>> element_;
-};
-
-template <typename Iter>
-class iterator<Iter, std::enable_if_t<detail::is_rvalue_iterator_v<Iter>>> {
-protected:
-  using inner_t = decltype(*std::declval<Iter>());
-
-public:
-  iterator() = default;
-  template <typename C, typename = std::enable_if_t<detail::is_container_v<C>>>
-  iterator(C && container) : joiner_(std::forward<C>(container)) {
-    update_iterator();
-  }
-
-  iterator(end_aware_iterator<Iter> join) : joiner_(join) { update_iterator(); }
-
-protected:
-  void update_iterator() {
-    while (!joiner_.at_end() && sync().at_end()) {
-      ++joiner_;
-    }
-  }
-
-private:
-  end_aware_iterator<iter<inner_t>> const & sync() {
-    if (joiner_.at_end()) { return element_ = {}; }
-    cache_ = std::make_shared<inner_t>(*joiner_);
-    element_ = end_aware_iterator(*cache_);
-    return element_;
-  }
-
-protected:
-  end_aware_iterator<Iter> joiner_;
-  std::shared_ptr<inner_t> cache_;
-  end_aware_iterator<iter<inner_t>> element_;
-};
-}
-
-namespace iterator {
-template <typename Iter>
-class joining_iterator : public facade<joining_iterator<Iter>>,
-                         public joining::iterator<Iter> {
-public:
-  using super_t = joining::iterator<Iter>;
-  using sentinel_type = sentinel_t;
-
-public:
-  using super_t::super_t;
-
-  template <typename C, typename = std::enable_if_t<detail::is_container_v<C>>>
-  joining_iterator(C && container) : super_t(std::forward<C>(container)) {}
-
-  joining_iterator(end_aware_iterator<Iter> join) : super_t(join) {}
-
-  void increment() {
-    if ((++super_t::element_).at_end()) {
-      ++super_t::joiner_;
-      super_t::update_iterator();
-    }
-  }
-
-  decltype(auto) dereference() const { return *super_t::element_; }
-
-  bool at_end() const { return join_iterator().at_end(); }
-  bool equal_to(joining_iterator const & other) const {
-    return join_iterator() == other.join_iterator() &&
-           element_iterator() == other.element_iterator();
-  }
-
-  auto const & join_iterator() const { return super_t::joiner_; }
-  auto const & element_iterator() const { return super_t::element_; }
-};
-
-template <typename C> joining_iterator(C &&) -> joining_iterator<iter<C>>;
-template <typename JI>
-joining_iterator(end_aware_iterator<JI>) -> joining_iterator<JI>;
-}
-
-MAKE_ITERATOR_FACADE_TYPEDEFS_T(::iterator::joining_iterator);

+ 55 - 33
include/iterator/proxy.h

@@ -1,90 +1,112 @@
 #pragma once
 
 #include <iterator/facade.h>
-#include <iterator/iterator_fwd.hpp>
+#include <iterator/forwards.h>
+
+#include <iterator/detail/macro.h>
 
 namespace iterator {
-template <typename Iter, typename Self>
-class proxy<Iter, Self, std::input_iterator_tag> : public facade<Self> {
+template <typename It, typename Self, typename Cat>
+class proxy : public facade<Self, category::single_pass> {
 public:
   using single_pass_iterator = void;
 
 private:
-  Iter impl_;
+  It impl_;
 
 public:
-  proxy(Iter impl = {}) : impl_(impl) {}
-  template <typename... Args>
-  proxy(Args &&... args) : impl_(std::forward<Args>(args)...) {}
+  proxy() = default;
+  proxy(It impl) : impl_(impl) {}
+  template <typename... Args> proxy(Args &&... args) : impl_(FWD(args)...) {}
 
   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 {
+    return impl() == typename It::sentinel_type();
+  }
 
 protected:
-  auto & impl() const { return impl_; }
+  auto impl() const { return impl_; }
 };
 
-template <typename Iter, typename Self>
-class proxy<Iter, Self, std::forward_iterator_tag> : public facade<Self> {
+template <typename It, typename Self>
+class proxy<It, Self, std::forward_iterator_tag>
+    : public facade<Self, category::forward> {
 private:
-  Iter impl_;
+  It impl_;
 
 public:
-  proxy(Iter impl = {}) : impl_(impl) {}
-  template <typename... Args>
-  proxy(Args &&... args) : impl_(std::forward<Args>(args)...) {}
+  proxy() = default;
+  proxy(It impl) : impl_(impl) {}
+  template <typename... Args> proxy(Args &&... args) : impl_(FWD(args)...) {}
 
   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 {
+    return impl() == typename It::sentinel_type();
+  }
 
 protected:
-  auto & impl() const { return impl_; }
+  auto impl() const { return impl_; }
 };
 
-template <typename Iter, typename Self>
-class proxy<Iter, Self, std::bidirectional_iterator_tag> : public facade<Self> {
+template <typename It, typename Self>
+class proxy<It, Self, std::bidirectional_iterator_tag>
+    : public facade<Self, category::bidirectional> {
 private:
-  Iter impl_;
+  It impl_;
 
 public:
-  proxy(Iter impl = {}) : impl_(impl) {}
-  template <typename... Args>
-  proxy(Args &&... args) : impl_(std::forward<Args>(args)...) {}
+  proxy() = default;
+  proxy(It impl) : impl_(impl) {}
+  template <typename... Args> proxy(Args &&... args) : impl_(FWD(args)...) {}
 
   decltype(auto) dereference() const { return *impl_; }
   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 {
+    return impl() == typename It::sentinel_type();
+  }
 
 protected:
-  auto & impl() const { return impl_; }
+  auto impl() const { return impl_; }
 };
 
-template <typename Iter, typename Self>
-class proxy<Iter, Self, std::random_access_iterator_tag> : public facade<Self> {
+template <typename It, typename Self>
+class proxy<It, Self, std::random_access_iterator_tag>
+    : public facade<Self, category::random_access> {
 public:
-  using difference_type = typename std::iterator_traits<Iter>::difference_type;
+  using difference_type = typename std::iterator_traits<It>::difference_type;
 
 private:
-  Iter impl_;
+  It impl_;
 
 public:
-  proxy(Iter impl = {}) : impl_(impl) {}
-  template <typename... Args>
-  proxy(Args &&... args) : impl_(std::forward<Args>(args)...) {}
+  proxy() = default;
+  proxy(It impl) : impl_(impl) {}
+  template <typename... Args> proxy(Args &&... args) : impl_(FWD(args)...) {}
 
   decltype(auto) dereference() const { return *impl_; }
   void advance(difference_type off) { impl_ += off; }
-  // This shouldn't need to be implemented, but for some reason my traits
-  // are not correctly deducing here.
-  bool equal_to(Self const & other) const { return distance_to(other) == 0; }
   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) {
+    return sentinel - self.impl();
+  }
+  
+  SFINAE(detail::has_sentinel_type_v<It>) bool at_end() const {
+    return (typename It::sentinel_type() - impl()) <= 0;
+  }
 
 protected:
-  auto & impl() const { return impl_; }
+  auto impl() const { return impl_; }
 };
 }
+
+#include <iterator/detail/undef.h>

+ 15 - 9
include/iterator/recursive_iterator.hpp

@@ -1,5 +1,5 @@
 //
-//  recursive_iterator.hpp
+//  recursive_iterator.h
 //  iterator
 //
 //  Created by Sam Jaffe on 2/17/17.
@@ -12,9 +12,11 @@
 #include <utility>
 
 #include <iterator/detail/recursive_traits.h>
-#include <iterator/end_aware_iterator.hpp>
+#include <iterator/end_aware_iterator.h>
 #include <iterator/facade.h>
-#include <iterator/iterator_fwd.hpp>
+#include <iterator/forwards.h>
+
+#include <iterator/detail/macro.h>
 
 namespace iterator::recursive {
 template <size_t N, size_t I> struct bounded {
@@ -49,11 +51,11 @@ struct tuple<It, Bnd, recursion_type::END> {
 };
 
 template <typename... Ts>
-using tuple_cat_t = decltype(std::tuple_cat(std::declval<Ts>()...));
+using tuple_cat_t = decltype(std::tuple_cat(VAL(Ts)...));
 
 template <typename It, typename Bnd>
 struct tuple<It, Bnd, recursion_type::THRU> {
-  using next = decltype(std::begin(*std::declval<It>()));
+  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(); }
@@ -61,7 +63,7 @@ struct tuple<It, Bnd, recursion_type::THRU> {
 
 template <typename It, typename Bnd>
 struct tuple<It, Bnd, recursion_type::ASSOC> {
-  using next = decltype(std::begin(std::declval<It>()->second));
+  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); };
@@ -82,8 +84,9 @@ struct tuple<It, Bnd, recursion_type::ASSOC> {
  * is willing to delve in the parent object.
  */
 template <typename It, typename Bnd>
-class rimpl : public facade<rimpl<It, Bnd>> {
+class rimpl : public facade<rimpl<It, Bnd>, category::forward> {
 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>;
@@ -194,10 +197,13 @@ auto get(::iterator::recursive_iterator_n<It, N> const & iter) {
 }
 
 template <typename C> auto make_recursive_iterator(C && collect) {
-  return iterator::recursive_iterator<iterator::iter<C>>(collect);
+  return iterator::recursive_iterator<iterator::detail::iter<C>>(collect);
 }
 
 template <std::size_t Max, typename C>
 auto make_recursive_iterator(C && collect) {
-  return iterator::recursive_iterator_n<iterator::iter<C>, Max>(collect);
+  return iterator::recursive_iterator_n<iterator::detail::iter<C>, Max>(
+      collect);
 }
+
+#include <iterator/detail/undef.h>

+ 2 - 0
include/iterator/sentinel.h

@@ -8,6 +8,8 @@
 
 #pragma once
 
+#include <iterator/forwards.h>
+
 namespace iterator {
 struct sentinel_t {};
 inline constexpr sentinel_t sentinel;

+ 0 - 47
include/iterator/sentinel_iterator.h

@@ -1,47 +0,0 @@
-//
-//  sentinel_iterator.h
-//  iterator
-//
-//  Created by Sam Jaffe on 3/30/23.
-//  Copyright © 2023 Sam Jaffe. All rights reserved.
-//
-
-#pragma once
-
-#include <iterator/detail/traits.h>
-#include <iterator/facade.h>
-#include <iterator/iterator_fwd.hpp>
-
-namespace iterator {
-template <typename It>
-class sentinel_iterator : public facade<sentinel_iterator<It>> {
-public:
-  using sentinel_type = sentinel_iterator;
-
-public:
-  sentinel_iterator() = default;
-  sentinel_iterator(It it) : curr_(it) {}
-
-  template <typename C, typename = std::enable_if_t<detail::is_container_v<C>>>
-  sentinel_iterator(C && container) : curr_(std::begin(container)) {
-    static_assert(std::is_reference_v<C>,
-                  "Cannot access iterator of a temporary");
-  }
-
-  decltype(auto) dereference() const { return *curr_; }
-  void increment() { ++curr_; }
-  bool at_end() const { return curr_.at_end() || curr_ == It(); }
-  bool equal_to(sentinel_iterator const & other) const {
-    // TODO: Fix this clause
-    return (at_end() && other.at_end()) || curr_ == other.curr_;
-  }
-
-private:
-  It curr_;
-};
-
-template <typename C> sentinel_iterator(C &&) -> sentinel_iterator<iter<C>>;
-template <typename It> sentinel_iterator(It) -> sentinel_iterator<It>;
-}
-
-MAKE_ITERATOR_FACADE_TYPEDEFS_T(::iterator::sentinel_iterator);

+ 2 - 2
include/iterator/unkeyed_iterator.hpp

@@ -1,5 +1,5 @@
 //
-//  unkeyed_iterator.hpp
+//  unkeyed_iterator.h
 //  iterator
 //
 //  Created by Sam Jaffe on 2/20/17.
@@ -7,7 +7,7 @@
 
 #pragma once
 
-#include <iterator/iterator_fwd.hpp>
+#include <iterator/forwards.h>
 #include <iterator/proxy.h>
 
 namespace iterator {

+ 9 - 11
include/iterator/zip_iterator.hpp

@@ -4,7 +4,7 @@
 #include <tuple>
 
 #include <iterator/facade.h>
-#include <iterator/iterator_fwd.hpp>
+#include <iterator/forwards.h>
 
 namespace iterator::detail {
 template <typename Tuple, typename IS> class zip_iterator_impl;
@@ -53,9 +53,14 @@ 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...>> {
+class zip_iterator
+    : public zip_impl<Iters...>,
+      public facade<zip_iterator<Iters...>, zip_category_for<Iters...>> {
 public:
   zip_iterator() = default;
   zip_iterator(Iters... iters) : zip_impl<Iters...>(iters...) {}
@@ -64,11 +69,4 @@ public:
 template <typename... It> zip_iterator(It...) -> zip_iterator<It...>;
 }
 
-template <typename... T>
-struct std::iterator_traits<::iterator::zip_iterator<T...>>
-    : std::iterator_traits<::iterator::facade<::iterator::zip_iterator<T...>>> {
-  // This shouldn't need to be implemented, but for some reason my traits
-  // are not correctly deducing here.
-  using iterator_category =
-      common_type_t<typename iterator_traits<T>::iterator_category...>;
-};
+MAKE_ITERATOR_FACADE_TYPEDEFS_T(::iterator::zip_iterator);

+ 31 - 21
iterator.xcodeproj/project.pbxproj

@@ -7,6 +7,7 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		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 */; };
 		CDCB3BD624E1D5320029B771 /* join_iterator_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CDCB3BCD24E1D5320029B771 /* join_iterator_test.cxx */; };
@@ -63,21 +64,25 @@
 /* Begin PBXFileReference section */
 		CD3C6DDB26238F8F00548B64 /* xcode_gtest_helper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = xcode_gtest_helper.h; sourceTree = "<group>"; };
 		CD5A8E7329D7910D008C2A4F /* sentinel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = sentinel.h; sourceTree = "<group>"; };
-		CD6EBE2229D5C93A00F387C1 /* sentinel_iterator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = sentinel_iterator.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>"; };
+		CD5AEB3229D8886200A390A4 /* undef.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = undef.h; sourceTree = "<group>"; };
+		CD5AEB3329D8956600A390A4 /* capture_iterator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = capture_iterator.h; sourceTree = "<group>"; };
+		CD5AEB3429D897DD00A390A4 /* capture_iterator_test.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = capture_iterator_test.cxx; sourceTree = "<group>"; };
 		CDA2B60928581255004D5353 /* libiterator.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libiterator.a; sourceTree = BUILT_PRODUCTS_DIR; };
-		CDA2B6122858128C004D5353 /* iterator_fwd.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = iterator_fwd.hpp; sourceTree = "<group>"; };
+		CDA2B6122858128C004D5353 /* forwards.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = forwards.h; sourceTree = "<group>"; };
 		CDA2B6132858128C004D5353 /* facade.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = facade.h; sourceTree = "<group>"; };
-		CDA2B6142858128C004D5353 /* indexed_iterator.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = indexed_iterator.hpp; sourceTree = "<group>"; };
-		CDA2B6152858128C004D5353 /* unkeyed_iterator.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = unkeyed_iterator.hpp; sourceTree = "<group>"; };
-		CDA2B6162858128C004D5353 /* recursive_iterator.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = recursive_iterator.hpp; sourceTree = "<group>"; };
+		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.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = end_aware_iterator.hpp; 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>"; };
-		CDA2B61D2858128C004D5353 /* zip_iterator.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = zip_iterator.hpp; sourceTree = "<group>"; };
-		CDA2B61E2858128C004D5353 /* filter_iterator.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = filter_iterator.hpp; sourceTree = "<group>"; };
-		CDA2B61F2858128C004D5353 /* join_iterator.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = join_iterator.hpp; sourceTree = "<group>"; };
+		CDA2B61D2858128C004D5353 /* zip_iterator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = zip_iterator.h; sourceTree = "<group>"; };
+		CDA2B61E2858128C004D5353 /* filter_iterator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = filter_iterator.h; sourceTree = "<group>"; };
+		CDA2B61F2858128C004D5353 /* join_iterator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = join_iterator.h; sourceTree = "<group>"; };
 		CDCB3BBC24E1CDE40029B771 /* iterator */ = {isa = PBXFileReference; lastKnownFileType = folder; name = iterator; path = include/iterator; sourceTree = "<group>"; };
 		CDCB3BC124E1D3880029B771 /* iterator-test.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "iterator-test.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
 		CDCB3BC524E1D3880029B771 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -140,6 +145,8 @@
 			isa = PBXGroup;
 			children = (
 				CD3C6DDB26238F8F00548B64 /* xcode_gtest_helper.h */,
+				CD5AEAE729D86D8100A390A4 /* ranges.h */,
+				CD5AEB3429D897DD00A390A4 /* capture_iterator_test.cxx */,
 				CDCB3BD224E1D5320029B771 /* end_aware_iterator_test.cxx */,
 				CDCB3BD324E1D5320029B771 /* filter_iterator_test.cxx */,
 				CDCB3BD424E1D5320029B771 /* indexed_iterator_test.cxx */,
@@ -149,8 +156,8 @@
 				CDCB3BD524E1D5320029B771 /* recursive_iterator_map_test.cxx */,
 				CDCB3BCF24E1D5320029B771 /* recursive_iterator_vector_test.cxx */,
 				CDCB3BFB24E327CF0029B771 /* recursive_iterator_single_level_test.cxx */,
-				CDCB3C0224E33A1E0029B771 /* zip_iterator_test.cxx */,
 				CDCB3BCE24E1D5320029B771 /* unkeyed_iterator_test.cxx */,
+				CDCB3C0224E33A1E0029B771 /* zip_iterator_test.cxx */,
 			);
 			path = test;
 			sourceTree = "<group>";
@@ -166,19 +173,19 @@
 		CDA2B6112858128C004D5353 /* iterator */ = {
 			isa = PBXGroup;
 			children = (
-				CDA2B6122858128C004D5353 /* iterator_fwd.hpp */,
+				CDA2B6122858128C004D5353 /* forwards.h */,
 				CDA2B6132858128C004D5353 /* facade.h */,
-				CDA2B6142858128C004D5353 /* indexed_iterator.hpp */,
-				CDA2B6152858128C004D5353 /* unkeyed_iterator.hpp */,
-				CDA2B6162858128C004D5353 /* recursive_iterator.hpp */,
-				CDA2B6172858128C004D5353 /* detail */,
-				CDA2B61B2858128C004D5353 /* end_aware_iterator.hpp */,
-				CD6EBE2229D5C93A00F387C1 /* sentinel_iterator.h */,
 				CDA2B61C2858128C004D5353 /* proxy.h */,
 				CD5A8E7329D7910D008C2A4F /* sentinel.h */,
-				CDA2B61D2858128C004D5353 /* zip_iterator.hpp */,
-				CDA2B61E2858128C004D5353 /* filter_iterator.hpp */,
-				CDA2B61F2858128C004D5353 /* join_iterator.hpp */,
+				CDA2B6172858128C004D5353 /* detail */,
+				CD5AEB3329D8956600A390A4 /* capture_iterator.h */,
+				CDA2B61B2858128C004D5353 /* end_aware_iterator.h */,
+				CDA2B61E2858128C004D5353 /* filter_iterator.h */,
+				CDA2B6142858128C004D5353 /* indexed_iterator.h */,
+				CDA2B61F2858128C004D5353 /* join_iterator.h */,
+				CDA2B6162858128C004D5353 /* recursive_iterator.h */,
+				CDA2B6152858128C004D5353 /* unkeyed_iterator.h */,
+				CDA2B61D2858128C004D5353 /* zip_iterator.h */,
 			);
 			path = iterator;
 			sourceTree = "<group>";
@@ -186,9 +193,11 @@
 		CDA2B6172858128C004D5353 /* detail */ = {
 			isa = PBXGroup;
 			children = (
+				CDA2B61A2858128C004D5353 /* arrow_proxy.h */,
+				CD5AEB3129D8885400A390A4 /* macro.h */,
 				CDA2B6182858128C004D5353 /* recursive_traits.h */,
 				CDA2B6192858128C004D5353 /* traits.h */,
-				CDA2B61A2858128C004D5353 /* arrow_proxy.h */,
+				CD5AEB3229D8886200A390A4 /* undef.h */,
 			);
 			path = detail;
 			sourceTree = "<group>";
@@ -376,6 +385,7 @@
 				CDCB3BDA24E1D5320029B771 /* recursive_iterator_mixed_container_test.cxx in Sources */,
 				CDCB3C0324E33A1E0029B771 /* zip_iterator_test.cxx in Sources */,
 				CDCB3BD924E1D5320029B771 /* recursive_iterator_accessors_test.cxx in Sources */,
+				CD5AEB3529D897DD00A390A4 /* capture_iterator_test.cxx in Sources */,
 				CDCB3BFC24E327CF0029B771 /* recursive_iterator_single_level_test.cxx in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;

+ 43 - 0
test/capture_iterator_test.cxx

@@ -0,0 +1,43 @@
+//
+//  capture_iterator_test.cxx
+//  iterator-test
+//
+//  Created by Sam Jaffe on 4/1/23.
+//  Copyright © 2023 Sam Jaffe. All rights reserved.
+//
+
+#include "iterator/capture_iterator.h"
+
+#include "ranges.h"
+#include "xcode_gtest_helper.h"
+
+using iterator::capture_iterator;
+
+using testing::Address;
+using testing::ElementsAre;
+using testing::Not;
+
+struct fake_iterator {
+  using value_type = std::vector<int>;
+  using reference = std::vector<int>;
+  using pointer = void;
+  using difference_type = std::ptrdiff_t;
+  using iterator_category = std::output_iterator_tag;
+
+  std::vector<int> operator*() const { return {0, 1, 2}; }
+  void operator++() {}
+  friend bool operator==(fake_iterator, fake_iterator) { return true; }
+};
+
+TEST(CaptureIterator, UnderlyingReturnsUnique) {
+  auto it = fake_iterator();
+  auto const & result = *it;
+  EXPECT_THAT(*it, Not(Address(&result)));
+}
+
+TEST(CaptureIterator, CachesValue) {
+  auto it = capture_iterator(fake_iterator());
+
+  auto const & result = *it;
+  EXPECT_THAT(*it, Address(&result));
+}

+ 32 - 33
test/end_aware_iterator_test.cxx

@@ -1,70 +1,69 @@
-#include "iterator/end_aware_iterator.hpp"
+#include "iterator/end_aware_iterator.h"
 
 #include <vector>
 
 #include "xcode_gtest_helper.h"
 
-using end_aware_iterator =
-    ::iterator::end_aware_iterator<std::vector<int>::iterator>;
+using iterator::end_aware_iterator;
 
 // TODO: This ought to be implemented as a compiles-test
-TEST(EndAwareIteratorTest, CanCastCompatibleIterators) {
+TEST(EndAwareIterator, CanCastCompatibleIterators) {
   std::vector<int> v{1, 2, 3, 4, 5};
   end_aware_iterator eai(v.begin(), v.end());
-  ::iterator::end_aware_iterator<std::vector<int>::const_iterator>{eai};
+
+  end_aware_iterator<std::vector<int>::const_iterator>{eai};
 }
 
-TEST(EndAwareIteratorTest, BeginWrapperIsEqualToBegin) {
+TEST(EndAwareIterator, BeginWrapperIsEqualToBegin) {
   std::vector<int> v{1, 2, 3, 4, 5};
-  EXPECT_THAT(*v.begin(), *end_aware_iterator(v.begin(), v.end()));
+
+  EXPECT_EQ(*v.begin(), *end_aware_iterator(v.begin(), v.end()));
 }
 
-TEST(EndAwareIteratorTest, MutableActionsArePassthrough) {
+TEST(EndAwareIterator, MutableActionsArePassthrough) {
   std::vector<int> v{1, 2, 3, 4, 5};
   *end_aware_iterator(v.begin(), v.end()) = -1;
-  EXPECT_THAT(v[0], -1);
+
+  EXPECT_EQ(v[0], -1);
 }
 
-TEST(EndAwareIteratorTest, CanTellYouThatItsReachedEnd) {
+TEST(EndAwareIterator, CanTellYouThatItsReachedEnd) {
   std::vector<int> v{1, 2, 3, 4, 5};
   end_aware_iterator it{v.end() - 1, v.end()};
   EXPECT_FALSE(it.at_end());
   ++it;
-  EXPECT_THAT(it, end_aware_iterator(v.end(), v.end()));
+  EXPECT_EQ(it, end_aware_iterator(v.end(), v.end()));
   EXPECT_TRUE(it.at_end());
 }
 
-TEST(EndAwareIteratorTest, EmptyIteratorIsEnd) {
-  EXPECT_TRUE(end_aware_iterator().at_end());
-}
-
-TEST(EndAwareIteratorTest, AllEndPointsAreEqual) {
-  std::vector<int> v1{1, 2, 3, 4, 5};
-  std::vector<int> v2{8, 9, 10};
-  EXPECT_THAT(end_aware_iterator(v1.end(), v1.end()),
-              end_aware_iterator(v2.end(), v2.end()));
+TEST(EndAwareIterator, EmptyIteratorIsEnd) {
+  EXPECT_TRUE(end_aware_iterator<std::vector<int>::iterator>().at_end());
 }
 
-TEST(EndAwareIteratorTest, PreIncrementAdvancesIterator) {
+TEST(EndAwareIterator, PreIncrementAdvancesIterator) {
   std::vector<int> v{1, 2, 3, 4, 5};
   end_aware_iterator eai(v.begin(), v.end());
-  EXPECT_THAT(*eai, 1);
-  EXPECT_THAT(*++eai, 2);
-  EXPECT_THAT(*eai, 2);
+
+  EXPECT_EQ(*eai, 1);
+  EXPECT_EQ(*++eai, 2);
+  EXPECT_EQ(*eai, 2);
 }
 
-TEST(EndAwareIteratorTest, PostIncrementReturnsCopyOfPrev) {
+TEST(EndAwareIterator, PostIncrementReturnsCopyOfPrev) {
   std::vector<int> v{1, 2, 3, 4, 5};
   end_aware_iterator eai(v.begin(), v.end());
-  EXPECT_THAT(*eai, 1);
-  EXPECT_THAT(*eai++, 1);
-  EXPECT_THAT(*eai, 2);
+
+  EXPECT_EQ(*eai, 1);
+  EXPECT_EQ(*eai++, 1);
+  EXPECT_EQ(*eai, 2);
 }
 
-TEST(EndAwareIteratorTest, IncrementOnEndIsUnsafe) {
+TEST(EndAwareIterator, CanIterateWithSentinel) {
   std::vector<int> v{1, 2, 3, 4, 5};
-  end_aware_iterator it{v.end(), v.end()};
-  end_aware_iterator const cp = it;
-  ++it;
-  EXPECT_NE(it, cp);
+  size_t count{0};
+  for (end_aware_iterator it(v); it != iterator::sentinel; ++it) {
+    ++count;
+  }
+
+  EXPECT_EQ(count, 5);
 }

+ 41 - 31
test/filter_iterator_test.cxx

@@ -1,56 +1,66 @@
-#include "iterator/filter_iterator.hpp"
+#include "iterator/filter_iterator.h"
 
 #include <vector>
 
+#include "ranges.h"
 #include "xcode_gtest_helper.h"
 
 using iterator::filter_iterator;
 
-TEST(FilterIteratorTest, CanPerformSkipsOnData) {
+bool is_even(int i) { return i % 2 == 0; }
+
+TEST(FilterIterator, CanPerformSkipsOnData) {
   std::vector<int> const data = {1, 2, 3, 4, 5};
-  auto pred = [](int i) { return i % 2 == 0; };
-  filter_iterator it(pred, data);
-  decltype(it) end = {};
-  EXPECT_THAT(std::distance(it, end), 2);
-  EXPECT_THAT(*it++, 2);
-  EXPECT_THAT(*it++, 4);
+  filter_iterator it(data, is_even);
+
+  EXPECT_EQ(ranges::distance(it, iterator::sentinel), 2);
+  EXPECT_EQ(*it++, 2);
+  EXPECT_EQ(*it++, 4);
 }
 
 // TODO: Maybe this should actually move
-TEST(FilterIteratorTest, MutatingContainerDoesNotMoveIterator) {
+TEST(FilterIterator, MutatingContainerDoesNotMoveIterator) {
   std::vector<int> data = {1, 2, 3, 4, 5};
-  auto pred = [](int i) { return i % 2 == 0; };
-  filter_iterator it(pred, data);
-  decltype(it) end = {};
-  EXPECT_THAT(std::distance(it, end), 2);
+  filter_iterator it(data, is_even);
+
+  EXPECT_EQ(ranges::distance(it, iterator::sentinel), 2);
+
   *it = 1;
-  EXPECT_THAT(std::distance(it, end), 2);
-  EXPECT_THAT(*it, 1);
+  EXPECT_EQ(ranges::distance(it, iterator::sentinel), 2);
+  EXPECT_EQ(*it, 1);
 }
 
-TEST(FilterIteratorTest, CanConstructFilterFromSubRange) {
+TEST(FilterIterator, CanConstructFilterFromSubRange) {
   std::vector<int> data = {1, 2, 3, 4, 5};
-  auto pred = [](int i) { return i % 2 == 0; };
-  filter_iterator it(pred, data.begin(), data.begin() + 3);
-  decltype(it) end = {};
-  EXPECT_THAT(std::distance(it, end), 1);
+  filter_iterator it(data.begin(), data.begin() + 3, is_even);
+
+  EXPECT_EQ(ranges::distance(it, iterator::sentinel), 1);
 }
 
-TEST(FilterIteratorTest, IfNonMatchThenStartIsEnd) {
+TEST(FilterIterator, IfNonMatchThenStartIsEnd) {
   std::vector<int> const data = {1, 3, 5};
-  auto pred = [](int i) { return i % 2 == 0; };
-  filter_iterator it(pred, data);
-  decltype(it) end = {};
-  EXPECT_THAT(it, end);
+  filter_iterator it(data, is_even);
+
+  EXPECT_EQ(it, iterator::sentinel);
 }
 
-TEST(FilterIteratorTest, IncrementOnEndIsUnsafe) {
+TEST(FilterIterator, CapsIterationAtEnd) {
   std::vector<int> const data = {1, 2, 3, 4, 5};
-  auto pred = [](int i) { return i % 2 == 0; };
-  filter_iterator it(pred, data);
-  decltype(it) end = {};
+  filter_iterator it(data, is_even);
+
   ++ ++it;
-  EXPECT_THAT(it, end);
+  EXPECT_EQ(it, iterator::sentinel);
+
   ++it;
-  EXPECT_NE(it, end);
+  EXPECT_EQ(it, iterator::sentinel);
+}
+
+TEST(FilterIterator, CanIterateWithSentinel) {
+  std::vector<int> v{1, 2, 3, 4, 5};
+  size_t count{0};
+  for (filter_iterator it(v, is_even); it != iterator::sentinel; ++it) {
+    ++count;
+  }
+
+  EXPECT_EQ(count, 2);
 }

+ 21 - 14
test/indexed_iterator_test.cxx

@@ -1,22 +1,27 @@
-#include "iterator/indexed_iterator.hpp"
+#include "iterator/indexed_iterator.h"
 
 #include <map>
 #include <vector>
 
-#include "xcode_gtest_helper.h"
+#include "iterator/end_aware_iterator.h"
 
-#include "iterator/end_aware_iterator.hpp"
+#include "ranges.h"
+#include "xcode_gtest_helper.h"
 
 using iterator::end_aware_iterator;
 using iterator::indexed_iterator;
 
+using testing::Ge;
+using testing::Gt;
+using testing::Le;
+using testing::Lt;
 using testing::Ne;
 
 // TODO: This ought to be implemented as a compiles-test
 TEST(IndexedIteratorTest, CanCastCompatibleIterators) {
   std::vector<int> v{1, 2, 3, 4, 5};
   indexed_iterator eai(v.begin());
-  ::iterator::indexed_iterator<std::vector<int>::const_iterator>{eai};
+  indexed_iterator<std::vector<int>::const_iterator>{eai};
 }
 
 TEST(IndexedIteratorTest, CanLieAboutIndex) {
@@ -29,7 +34,7 @@ TEST(IndexedIteratorTest, FakeIndexDoesntEffectEqualityCheck) {
   std::vector<int> vec{5, 3, 2, 8, 9, 11, 2, 4};
   EXPECT_THAT(indexed_iterator(vec.begin()), indexed_iterator(vec.begin(), 3));
   EXPECT_THAT(indexed_iterator(vec.begin()) + 3,
-              testing::Ne(indexed_iterator(vec.begin(), 3)));
+              Ne(indexed_iterator(vec.begin(), 3)));
 }
 
 TEST(IndexedIteratorTest, DoesNotTrackByIndex) {
@@ -41,19 +46,21 @@ TEST(IndexedIteratorTest, DoesNotTrackByIndex) {
 TEST(IndexedIteratorTest, IteratorPropagatesAtEnd) {
   std::vector<int> vec{5, 3, 2, 8, 9, 11, 2, 4};
   indexed_iterator end(end_aware_iterator(vec.end(), vec.end()));
-  EXPECT_THAT(decltype(end){}, end);
+
+  EXPECT_EQ(end, iterator::sentinel);
 }
 
 TEST(IndexedIteratorTest, CanCompareIteratorOrder) {
   std::vector<int> vec{5, 3, 2, 8, 9, 11, 2, 4};
   indexed_iterator const begin(vec.begin());
   indexed_iterator const it = begin + 3;
-  EXPECT_THAT(begin, testing::Lt(it));
-  EXPECT_THAT(begin, testing::Le(it));
-  EXPECT_THAT(it, testing::Le(it));
-  EXPECT_THAT(it, testing::Gt(begin));
-  EXPECT_THAT(it, testing::Ge(begin));
-  EXPECT_THAT(it, testing::Ge(it));
+
+  EXPECT_THAT(begin, Lt(it));
+  EXPECT_THAT(begin, Le(it));
+  EXPECT_THAT(it, Le(it));
+  EXPECT_THAT(it, Gt(begin));
+  EXPECT_THAT(it, Ge(begin));
+  EXPECT_THAT(it, Ge(it));
 }
 
 TEST(IndexedIteratorTest, PreIncrementAdvancesIterator) {
@@ -93,7 +100,7 @@ TEST(IndexedIteratorTest, CanWalkNStepsForward) {
   indexed_iterator const begin(vec.begin());
   indexed_iterator it = begin;
   it += 4;
-  EXPECT_THAT(std::distance(begin, it), 4);
+  EXPECT_THAT(ranges::distance(begin, it), 4);
 }
 
 TEST(IndexedIteratorTest, CanWalkNStepsBackwards) {
@@ -101,7 +108,7 @@ TEST(IndexedIteratorTest, CanWalkNStepsBackwards) {
   indexed_iterator const end(vec.end());
   indexed_iterator it = end;
   it -= 4;
-  EXPECT_THAT(std::distance(it, end), 4);
+  EXPECT_THAT(ranges::distance(it, end), 4);
 }
 
 TEST(IndexedIteratorTest, RandomAccessIsPassthrough) {

+ 17 - 16
test/join_iterator_test.cxx

@@ -1,4 +1,4 @@
-#include "iterator/join_iterator.hpp"
+#include "iterator/join_iterator.h"
 
 #include <vector>
 
@@ -11,20 +11,21 @@ using testing::Ne;
 
 TEST(JoinIteratorTest, FirstDereferencedElemIsTheFirstInTheChain) {
   std::vector<std::vector<int>> mv{{1, 2, 3}, {4, 5, 6}};
-  EXPECT_THAT(*joining_iterator(mv), mv[0][0]);
+  EXPECT_EQ(*joining_iterator(mv), mv[0][0]);
 }
 
 TEST(JoinIteratorTest, HoldsReferenceToContainedElements) {
   std::vector<std::vector<int>> mv{{1, 2, 3}, {4, 5, 6}};
-  EXPECT_THAT(joining_iterator(mv).operator->(), &mv[0][0]);
+  EXPECT_EQ(joining_iterator(mv).operator->(), &mv[0][0]);
 }
 
-TEST(JoinIteratorTest, DefaultCtorIsEnd) {
+TEST(JoinIteratorTest, EmptyContainerBeginIsEnd) {
   std::vector<std::vector<int>> mv{{1, 2, 3}, {4, 5, 6}};
   joining_iterator it(mv);
-  EXPECT_THAT(it, Ne(decltype(it)()));
+  EXPECT_NE(it, iterator::sentinel);
+
   mv.clear();
-  EXPECT_THAT(joining_iterator(mv), decltype(it)());
+  EXPECT_EQ(joining_iterator(mv), iterator::sentinel);
 }
 
 // TODO: This ought to be implemented as a compiles-test
@@ -38,41 +39,41 @@ TEST(JoinIteratorTest, CanAccessInternalIterator) {
   std::vector<std::vector<int>> mv{{1, 2, 3}, {4, 5, 6}};
   auto eai = end_aware_iterator(mv);
   joining_iterator it(eai);
-  EXPECT_THAT(it.join_iterator(), eai);
+  EXPECT_EQ(it.outer_iterator(), eai);
 }
 
 TEST(JoinIteratorTest, CanAccessChildIterator) {
   std::vector<std::vector<int>> mv{{1, 2, 3}, {4, 5, 6}};
   joining_iterator it(mv);
-  EXPECT_THAT(it.element_iterator(), end_aware_iterator(mv[0]));
+  EXPECT_EQ(it.inner_iterator(), end_aware_iterator(mv[0]));
 }
 
 TEST(JoinIteratorTest, PreIncrementAdvancesIterator) {
   std::vector<std::vector<int>> mv{{1, 2, 3}, {4, 5, 6}};
   joining_iterator it(mv);
-  EXPECT_THAT(*it, 1);
-  EXPECT_THAT(*++it, 2);
-  EXPECT_THAT(*it, 2);
+  EXPECT_EQ(*it, 1);
+  EXPECT_EQ(*++it, 2);
+  EXPECT_EQ(*it, 2);
 }
 
 TEST(JoinIteratorTest, PostIncrementReturnsCopyOfPrev) {
   std::vector<std::vector<int>> mv{{1, 2, 3}, {4, 5, 6}};
   joining_iterator it(mv);
-  EXPECT_THAT(*it, 1);
-  EXPECT_THAT(*it++, 1);
-  EXPECT_THAT(*it, 2);
+  EXPECT_EQ(*it, 1);
+  EXPECT_EQ(*it++, 1);
+  EXPECT_EQ(*it, 2);
 }
 
 TEST(JoinIteratorTest, MovesFromListToListWhenReachingEnd) {
   std::vector<std::vector<int>> mv{{1, 2, 3}, {4, 5, 6}};
   joining_iterator it(mv);
   std::advance(it, 2);
-  EXPECT_THAT(*++it, mv[1][0]);
+  EXPECT_EQ(*++it, mv[1][0]);
 }
 
 TEST(JoinIteratorTest, SkipsOverEmptyElements) {
   std::vector<std::vector<int>> mv{{1, 2, 3}, {}, {4, 5, 6}};
   joining_iterator it(mv);
   std::advance(it, 2);
-  EXPECT_THAT(*++it, mv[2][0]);
+  EXPECT_EQ(*++it, mv[2][0]);
 }

+ 40 - 0
test/ranges.h

@@ -0,0 +1,40 @@
+//
+//  ranges.h
+//  iterator
+//
+//  Created by Sam Jaffe on 4/1/23.
+//  Copyright © 2023 Sam Jaffe. All rights reserved.
+//
+
+#pragma once
+
+namespace ranges {
+template <typename It, typename S, typename F>
+auto for_each(It it, S end, F consumer) {
+  for (; it != end; ++it) {
+    std::invoke(consumer, *it);
+  }
+  return std::make_pair(it, consumer);
+}
+
+template <typename It, typename S> auto distance(It it, S end) {
+  typename std::iterator_traits<It>::difference_type diff{0};
+  for_each(it, end, [&diff](auto const &) { ++diff; });
+  return diff;
+}
+
+template <template <typename...> class C, typename It, typename S>
+auto to(It it, S end) {
+  decltype(C(it, it)) rval;
+  for_each(it, end,
+           [&rval](auto const & elem) { rval.insert(rval.end(), elem); });
+  return rval;
+}
+
+template <typename C, typename It, typename S> auto to(It it, S end) {
+  C rval;
+  for_each(it, end,
+           [&rval](auto const & elem) { rval.insert(rval.end(), elem); });
+  return rval;
+}
+}

+ 41 - 33
test/recursive_iterator_accessors_test.cxx

@@ -1,45 +1,50 @@
-#include "iterator/recursive_iterator.hpp"
+#include "iterator/recursive_iterator.h"
 
 #include <map>
 #include <string>
 #include <tuple>
 #include <vector>
 
+#include "ranges.h"
 #include "xcode_gtest_helper.h"
 
 using iterator::end_aware_iterator;
 
+using testing::StaticAssertTypeEq;
+
 TEST(RecursiveIteratorTest, DoesNotUnwrapString) {
   std::vector<std::string> obj{"A", "B", "C", "D"};
   auto rit = make_recursive_iterator(obj);
-  testing::StaticAssertTypeEq<decltype(rit.operator->()), std::string *>();
+  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);
-  testing::StaticAssertTypeEq<decltype(rit.operator->()), int *>();
-  EXPECT_THAT(rit.operator->(), &obj[0][0]);
+  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);
-//  testing::StaticAssertTypeEq<decltype(rit.operator->()), void>();
+//  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);
-  iterator::end_aware_iterator<decltype(obj)::iterator> inner = rit;
-  EXPECT_THAT(&std::get<0>(*rit), &(inner->first));
+
+  end_aware_iterator<decltype(obj)::iterator> inner = rit;
+  EXPECT_EQ(&std::get<0>(*rit), &(inner->first));
 }
 
 TEST(RecursiveIteratorTest, CanAccessInnerIterator) {
   std::map<int, std::vector<int>> obj{{1, {{0, 1}}}, {2, {{2, 3}}}};
   auto rit = make_recursive_iterator(obj);
-  iterator::end_aware_iterator<std::vector<int>::iterator> inner = rit;
-  EXPECT_THAT(&std::get<1>(*rit), &*inner);
+
+  end_aware_iterator<std::vector<int>::iterator> inner = rit;
+  EXPECT_EQ(&std::get<1>(*rit), &*inner);
 }
 
 TEST(RecursiveIteratorTest, CanStdGetToAllLayersOfInternalIteration) {
@@ -49,16 +54,16 @@ TEST(RecursiveIteratorTest, CanStdGetToAllLayersOfInternalIteration) {
   };
   auto rit = make_recursive_iterator(obj);
   using mvm_iterator = std::map<int, std::vector<std::map<int, int>>>::iterator;
-  testing::StaticAssertTypeEq<decltype(std::get<0>(rit)),
-                              iterator::end_aware_iterator<mvm_iterator>>();
+  StaticAssertTypeEq<decltype(std::get<0>(rit)),
+                     end_aware_iterator<mvm_iterator>>();
   using vm_iterator = std::vector<std::map<int, int>>::iterator;
-  testing::StaticAssertTypeEq<decltype(std::get<1>(rit)),
-                              iterator::end_aware_iterator<vm_iterator>>();
+  StaticAssertTypeEq<decltype(std::get<1>(rit)),
+                     end_aware_iterator<vm_iterator>>();
   using m_iterator = std::map<int, int>::iterator;
-  testing::StaticAssertTypeEq<decltype(std::get<2>(rit)),
-                              iterator::end_aware_iterator<m_iterator>>();
+  StaticAssertTypeEq<decltype(std::get<2>(rit)),
+                     end_aware_iterator<m_iterator>>();
   using tup_i_i_i = std::tuple<int const &, int const &, int &>;
-  testing::StaticAssertTypeEq<decltype(*rit), tup_i_i_i>();
+  StaticAssertTypeEq<decltype(*rit), tup_i_i_i>();
 }
 
 TEST(RecursiveIteratorTest, CanAccessInternalIteratorsWithGet) {
@@ -67,9 +72,9 @@ TEST(RecursiveIteratorTest, CanAccessInternalIteratorsWithGet) {
       {2, {{{3, 3}, {4, 4}}}}    // 1 2-element map
   };
   auto rit = make_recursive_iterator(obj);
-  EXPECT_THAT(std::get<0>(rit), end_aware_iterator(obj));
-  EXPECT_THAT(std::get<1>(rit), end_aware_iterator(obj[1]));
-  EXPECT_THAT(std::get<2>(rit), end_aware_iterator(obj[1][0]));
+  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]));
 }
 
 // TODO: This ought to be implemented as a compiles-test
@@ -88,10 +93,12 @@ TEST(RecursiveIteratorTest, EmptyCtorIsEnd) {
       {2, {{{3, 3}, {4, 4}}}}    // 1 2-element map
   };
   auto rit = make_recursive_iterator(obj);
-  EXPECT_THAT(rit, testing::Ne(decltype(rit)()));
-  EXPECT_THAT(std::distance(rit, decltype(rit)()), 4);
+
+  EXPECT_NE(rit, iterator::sentinel);
+  EXPECT_EQ(ranges::distance(rit, iterator::sentinel), 4);
+
   std::advance(rit, 4);
-  EXPECT_THAT(rit, decltype(rit)());
+  EXPECT_EQ(rit, iterator::sentinel);
 }
 
 TEST(BoundedRecursiveIteratorTest, CanStdGetToNLayersOfInternalIteration) {
@@ -101,13 +108,13 @@ TEST(BoundedRecursiveIteratorTest, CanStdGetToNLayersOfInternalIteration) {
   };
   auto rit = make_recursive_iterator<2>(obj);
   using mvm_iterator = std::map<int, std::vector<std::map<int, int>>>::iterator;
-  testing::StaticAssertTypeEq<decltype(std::get<0>(rit)),
-                              iterator::end_aware_iterator<mvm_iterator>>();
+  StaticAssertTypeEq<decltype(std::get<0>(rit)),
+                     end_aware_iterator<mvm_iterator>>();
   using vm_iterator = std::vector<std::map<int, int>>::iterator;
-  testing::StaticAssertTypeEq<decltype(std::get<1>(rit)),
-                              iterator::end_aware_iterator<vm_iterator>>();
+  StaticAssertTypeEq<decltype(std::get<1>(rit)),
+                     end_aware_iterator<vm_iterator>>();
   using tup_i_mii = std::tuple<int const &, std::map<int, int> &>;
-  testing::StaticAssertTypeEq<decltype(*rit), tup_i_mii>();
+  StaticAssertTypeEq<decltype(*rit), tup_i_mii>();
 }
 
 TEST(BoundedRecursiveIteratorTest, CanAccessInternalIteratorsWithGet) {
@@ -116,8 +123,8 @@ TEST(BoundedRecursiveIteratorTest, CanAccessInternalIteratorsWithGet) {
       {2, {{{3, 3}, {4, 4}}}}    // 1 2-element map
   };
   auto rit = make_recursive_iterator<2>(obj);
-  EXPECT_THAT(std::get<0>(rit), end_aware_iterator(obj));
-  EXPECT_THAT(std::get<1>(rit), end_aware_iterator(obj[1]));
+  EXPECT_EQ(std::get<0>(rit), end_aware_iterator(obj));
+  EXPECT_EQ(std::get<1>(rit), end_aware_iterator(obj[1]));
 }
 
 // TODO: This ought to be implemented as a compiles-test
@@ -136,10 +143,11 @@ TEST(BoundedRecursiveIteratorTest, EmptyCtorIsEnd) {
       {2, {{{3, 3}, {4, 4}}}}    // 1 2-element map
   };
   auto rit = make_recursive_iterator<3>(obj);
-  EXPECT_THAT(rit, testing::Ne(decltype(rit)()));
-  EXPECT_THAT(std::distance(rit, decltype(rit)()), 4);
+  EXPECT_NE(rit, iterator::sentinel);
+  EXPECT_EQ(ranges::distance(rit, iterator::sentinel), 4);
+
   std::advance(rit, 4);
-  EXPECT_THAT(rit, decltype(rit)());
+  EXPECT_EQ(rit, iterator::sentinel);
 }
 
 TEST(BoundedRecursiveIteratorTest, CanFetchInnerCollections) {
@@ -148,5 +156,5 @@ TEST(BoundedRecursiveIteratorTest, CanFetchInnerCollections) {
       {{{{3, 3}, {4, 4}}}}    // 1 2-element map
   };
   auto rit = make_recursive_iterator<2>(obj);
-  EXPECT_THAT(*rit, obj[0][0]);
+  EXPECT_EQ(*rit, obj[0][0]);
 }

+ 29 - 24
test/recursive_iterator_map_test.cxx

@@ -1,36 +1,38 @@
-#include "iterator/recursive_iterator.hpp"
+#include "iterator/recursive_iterator.h"
 
 #include <map>
 #include <tuple>
 #include <vector>
 
+#include "ranges.h"
 #include "xcode_gtest_helper.h"
 
+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);
-  EXPECT_THAT(std::get<3>(*rit), 1);
-  EXPECT_THAT(std::get<3>(*++rit), 2);
-  EXPECT_THAT(std::get<3>(*rit), 2);
+  EXPECT_EQ(std::get<3>(*rit), 1);
+  EXPECT_EQ(std::get<3>(*++rit), 2);
+  EXPECT_EQ(std::get<3>(*rit), 2);
 }
 
 TEST(RecursiveIteratorMapTest, PostIncrementReturnsCopyOfPrev) {
   std::map<int, std::map<int, std::map<int, int>>> const map{
       {1, {{1, {{1, 1}}}}}, {2, {{2, {{2, 2}}}, {3, {{3, 3}, {4, 4}}}}}};
   auto rit = make_recursive_iterator(map);
-  EXPECT_THAT(std::get<3>(*rit), 1);
-  EXPECT_THAT(std::get<3>(*rit++), 1);
-  EXPECT_THAT(std::get<3>(*rit), 2);
+  EXPECT_EQ(std::get<3>(*rit), 1);
+  EXPECT_EQ(std::get<3>(*rit++), 1);
+  EXPECT_EQ(std::get<3>(*rit), 2);
 }
 
 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);
-  decltype(rit) end{};
-  // TODO: Actually perform the summation?
-  EXPECT_THAT(std::distance(rit, end), 4);
+
+  EXPECT_EQ(ranges::distance(rit, iterator::sentinel), 4);
 }
 
 TEST(RecursiveIteratorMapTest, ElementsAreUnwrappedAsATuple) {
@@ -39,8 +41,10 @@ TEST(RecursiveIteratorMapTest, ElementsAreUnwrappedAsATuple) {
   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);
-  decltype(expected) result(rit, decltype(rit)());
-  EXPECT_THAT(result, expected);
+
+  EXPECT_EQ((ranges::to<std::vector<std::tuple<int, int, int, int>>>(
+                rit, iterator::sentinel)),
+            expected);
 }
 
 TEST(RecursiveIteratorMapTest, CanMutatePointedToData) {
@@ -48,34 +52,33 @@ TEST(RecursiveIteratorMapTest, CanMutatePointedToData) {
       {1, {{1, {{1, 1}}}}}, {2, {{2, {{2, 2}}}, {3, {{3, 3}, {4, 4}}}}}};
   auto rit = make_recursive_iterator(map);
   std::get<3>(*rit) = 4;
-  EXPECT_THAT(map[1][1][1], 4);
+  EXPECT_EQ(map[1][1][1], 4);
 }
 
 TEST(BoundRecursiveIteratorMapTest, PreIncrementAdvancesIterator) {
   std::map<int, std::map<int, std::map<int, int>>> map{
       {1, {{1, {{1, 1}}}}}, {2, {{2, {{2, 2}}}, {3, {{3, 3}, {4, 4}}}}}};
   auto rit = make_recursive_iterator<2>(map);
-  EXPECT_THAT(std::get<2>(*rit), map[1][1]);
-  EXPECT_THAT(std::get<2>(*++rit), map[2][2]);
-  EXPECT_THAT(std::get<2>(*rit), map[2][2]);
+  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]);
 }
 
 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);
-  EXPECT_THAT(std::get<2>(*rit), map[1][1]);
-  EXPECT_THAT(std::get<2>(*rit++), map[1][1]);
-  EXPECT_THAT(std::get<2>(*rit), map[2][2]);
+  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]);
 }
 
 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);
-  decltype(rit) end{};
 
-  EXPECT_THAT(std::distance(rit, end), 3);
+  EXPECT_EQ(ranges::distance(rit, iterator::sentinel), 3);
 }
 
 TEST(BoundRecursiveIteratorMapTest, ElementsAreUnwrappedAsATuple) {
@@ -84,8 +87,10 @@ TEST(BoundRecursiveIteratorMapTest, ElementsAreUnwrappedAsATuple) {
   std::vector<std::tuple<int, int, std::map<int, int>>> const expected{
       {1, 1, {{1, 1}}}, {2, 2, {{2, 2}}}, {2, 3, {{3, 3}, {4, 4}}}};
   auto rit = make_recursive_iterator<2>(map);
-  decltype(expected) result(rit, decltype(rit)());
-  EXPECT_THAT(result, expected);
+
+  EXPECT_EQ((ranges::to<std::vector<std::tuple<int, int, std::map<int, int>>>>(
+                rit, iterator::sentinel)),
+            expected);
 }
 
 TEST(BoundedRecursiveIteratorMapTest, CanMutatePointedToData) {
@@ -93,5 +98,5 @@ TEST(BoundedRecursiveIteratorMapTest, CanMutatePointedToData) {
       {1, {{1, {{1, 1}}}}}, {2, {{2, {{2, 2}}}, {3, {{3, 3}, {4, 4}}}}}};
   auto rit = make_recursive_iterator<2>(map);
   std::get<2>(*rit).clear();
-  EXPECT_THAT(map[1][1], testing::IsEmpty());
+  EXPECT_THAT(map[1][1], IsEmpty());
 }

+ 32 - 26
test/recursive_iterator_mixed_container_test.cxx

@@ -1,16 +1,16 @@
-#include "iterator/recursive_iterator.hpp"
+#include "iterator/recursive_iterator.h"
 
 #include <map>
 #include <vector>
 
+#include "ranges.h"
 #include "xcode_gtest_helper.h"
 
 TEST(RecursiveIteratorMapVectorTest, IterDistanceIsSumOfInnerContainerSizes) {
   std::map<int, std::vector<int>> const obj{{1, {1, 2}}, {2, {3, 4, 5}}};
   auto rit = make_recursive_iterator(obj);
-  decltype(rit) end{};
-  // TODO: Actually perform the summation?
-  EXPECT_THAT(std::distance(rit, end), 5);
+
+  EXPECT_EQ(ranges::distance(rit, iterator::sentinel), 5);
 }
 
 TEST(RecursiveIteratorMapVectorTest, ElementsAreUnwrappedAsATuple) {
@@ -18,15 +18,17 @@ TEST(RecursiveIteratorMapVectorTest, ElementsAreUnwrappedAsATuple) {
   std::vector<std::tuple<int, int>> const expected{
       {1, 1}, {1, 2}, {2, 3}, {2, 4}, {2, 5}};
   auto rit = make_recursive_iterator(obj);
-  decltype(expected) result(rit, decltype(rit)());
-  EXPECT_THAT(result, expected);
+
+  EXPECT_EQ(
+      (ranges::to<std::vector<std::tuple<int, int>>>(rit, iterator::sentinel)),
+      expected);
 }
 
 TEST(RecursiveIteratorMapVectorTest, CanMutatePointedToData) {
   std::map<int, std::vector<int>> obj{{1, {1, 2}}, {2, {3, 4, 5}}};
   auto rit = make_recursive_iterator(obj);
   std::get<1>(*rit) = 6;
-  EXPECT_THAT(obj[1][0], 6);
+  EXPECT_EQ(obj[1][0], 6);
 }
 
 TEST(RecursiveIteratorMapMapVectorTest, CanMutatePointedToData) {
@@ -34,26 +36,27 @@ TEST(RecursiveIteratorMapMapVectorTest, CanMutatePointedToData) {
                                                      {2, {{1, {3, 4, 5}}}}};
   auto rit = make_recursive_iterator(obj);
   std::get<2>(*rit) = 6;
-  EXPECT_THAT(obj[1][1][0], 6);
+  EXPECT_EQ(obj[1][1][0], 6);
 }
 
 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);
-  decltype(rit) end{};
-  // TODO: Actually perform the summation?
-  EXPECT_THAT(std::distance(rit, end), 5);
+
+  EXPECT_EQ(ranges::distance(rit, iterator::sentinel), 5);
 }
 
 TEST(RecursiveIteratorVectorMapTest, ElementsAreUnwrappedAsATuple) {
   std::vector<std::map<int, int>> const obj{{{1, 1}, {2, 2}},
                                             {{3, 3}, {4, 4}, {5, 5}}};
-  std::vector<std::pair<int const, int>> const expected{
+  std::vector<std::pair<int, int>> const expected{
       {1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}};
   auto rit = make_recursive_iterator(obj);
-  decltype(expected) result(rit, decltype(rit)());
-  EXPECT_THAT(result, expected);
+
+  EXPECT_EQ(
+      (ranges::to<std::vector<std::pair<int, int>>>(rit, iterator::sentinel)),
+      expected);
 }
 
 TEST(RecursiveIteratorVectorMapTest, CanMutatePointedToData) {
@@ -61,7 +64,7 @@ TEST(RecursiveIteratorVectorMapTest, CanMutatePointedToData) {
                                       {{3, 3}, {4, 4}, {5, 5}}};
   auto rit = make_recursive_iterator(obj);
   std::get<1>(*rit) = 6;
-  EXPECT_THAT(obj[0][1], 6);
+  EXPECT_EQ(obj[0][1], 6);
 }
 
 TEST(RecursiveIteratorMapVecMapTest, IterDistanceIsSumOfInnerContainerSizes) {
@@ -69,8 +72,8 @@ TEST(RecursiveIteratorMapVecMapTest, IterDistanceIsSumOfInnerContainerSizes) {
       {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);
-  decltype(rit) end{};
-  EXPECT_THAT(std::distance(rit, end), expected.size());
+
+  EXPECT_EQ(ranges::distance(rit, iterator::sentinel), expected.size());
 }
 
 TEST(RecursiveIteratorMapVecMapTest, ElementsAreUnwrappedAsATuple) {
@@ -78,24 +81,25 @@ TEST(RecursiveIteratorMapVecMapTest, ElementsAreUnwrappedAsATuple) {
       {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);
-  decltype(expected) result(rit, decltype(rit)());
-  EXPECT_THAT(result, expected);
+
+  EXPECT_EQ((ranges::to<std::vector<std::tuple<int, int, int>>>(
+                rit, iterator::sentinel)),
+            expected);
 }
 
 TEST(RecursiveIteratorMapVecMapTest, CanMutatePointedToData) {
   std::map<int, std::vector<std::map<int, int>>> obj{{1, {{{1, 1}, {2, 2}}}}};
   auto rit = make_recursive_iterator(obj);
   std::get<2>(*rit) = 4;
-  EXPECT_THAT(obj[1][0][1], 4);
+  EXPECT_EQ(obj[1][0][1], 4);
 }
 
 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);
-  decltype(rit) end{};
-  // TODO: Actually perform the summation?
-  EXPECT_THAT(std::distance(rit, end), 7);
+
+  EXPECT_EQ(ranges::distance(rit, iterator::sentinel), 7);
 }
 
 TEST(RecursiveIteratorVecMapVecTest, ElementsAreUnwrappedAsATuple) {
@@ -104,8 +108,10 @@ TEST(RecursiveIteratorVecMapVecTest, ElementsAreUnwrappedAsATuple) {
   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);
-  decltype(expected) result(rit, decltype(rit)());
-  EXPECT_THAT(result, expected);
+
+  EXPECT_EQ(
+      (ranges::to<std::vector<std::tuple<int, int>>>(rit, iterator::sentinel)),
+      expected);
 }
 
 TEST(RecursiveIteratorVecMapVecTest, CanMutatePointedToData) {
@@ -113,5 +119,5 @@ TEST(RecursiveIteratorVecMapVecTest, CanMutatePointedToData) {
       {{1, {1, 2}}, {2, {3, 4, 5}}}, {{1, {3, 4}}}};
   auto rit = make_recursive_iterator(obj);
   std::get<1>(*rit) = 6;
-  EXPECT_THAT(obj[0][1][0], 6);
+  EXPECT_EQ(obj[0][1][0], 6);
 }

+ 10 - 9
test/recursive_iterator_single_level_test.cxx

@@ -6,25 +6,26 @@
 //  Copyright © 2020 Sam Jaffe. All rights reserved.
 //
 
-#include "iterator/recursive_iterator.hpp"
+#include "iterator/recursive_iterator.h"
 
 #include <map>
 #include <vector>
 
+#include "ranges.h"
 #include "xcode_gtest_helper.h"
 
 TEST(RecursiveIteratorSingleVectorTest, IterDistanceIsContainerSize) {
   std::vector<int> const vec{1, 2, 3, 4, 5};
   auto rit = make_recursive_iterator(vec);
-  decltype(rit) end{};
-  EXPECT_THAT(std::distance(rit, end), vec.size());
+
+  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);
-  decltype(vec) result(rit, decltype(rit)());
-  EXPECT_THAT(result, vec);
+
+  EXPECT_THAT(ranges::to<std::vector<int>>(rit, iterator::sentinel), vec);
 }
 
 TEST(RecursiveIteratorSingleVectorTest, CanMutatePointedToData) {
@@ -37,15 +38,15 @@ TEST(RecursiveIteratorSingleVectorTest, CanMutatePointedToData) {
 TEST(RecursiveIteratorSingleMapTest, IterDistanceIsContainerSize) {
   std::map<int, int> const map{{1, 1}, {2, 2}, {3, 3}};
   auto rit = make_recursive_iterator(map);
-  decltype(rit) end{};
-  EXPECT_THAT(std::distance(rit, end), map.size());
+
+  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);
-  decltype(map) result(rit, decltype(rit)());
-  EXPECT_THAT(result, map);
+
+  EXPECT_THAT((ranges::to<std::map<int, int>>(rit, iterator::sentinel)), map);
 }
 
 TEST(RecursiveIteratorSingleMapTest, CanMutatePointedToData) {

+ 15 - 13
test/recursive_iterator_vector_test.cxx

@@ -1,9 +1,12 @@
-#include "iterator/recursive_iterator.hpp"
+#include "iterator/recursive_iterator.h"
 
 #include <vector>
 
+#include "ranges.h"
 #include "xcode_gtest_helper.h"
 
+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);
@@ -23,18 +26,16 @@ 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);
-  decltype(rit) end{};
 
-  // TODO: Actually perform the summation?
-  EXPECT_THAT(std::distance(rit, end), 5);
+  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}}};
-  std::vector<int> const expected{1, 2, 3, 4, 5};
   auto rit = make_recursive_iterator(vec);
-  decltype(expected) result(rit, decltype(rit)());
-  EXPECT_THAT(result, expected);
+
+  std::vector<int> const expected{1, 2, 3, 4, 5};
+  EXPECT_THAT(ranges::to<std::vector<int>>(rit, iterator::sentinel), expected);
 }
 
 TEST(RecursiveIteratorVecTest, CanMutatePointedToData) {
@@ -63,22 +64,23 @@ 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);
-  decltype(rit) end{};
 
-  EXPECT_THAT(std::distance(rit, end), 3);
+  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}}};
-  std::vector<std::vector<int>> const expected{{1, 2}, {3}, {4, 5}};
   auto rit = make_recursive_iterator<2>(vec);
-  decltype(expected) result(rit, decltype(rit)());
-  EXPECT_THAT(result, expected);
+
+  std::vector<std::vector<int>> const expected{{1, 2}, {3}, {4, 5}};
+  EXPECT_THAT(
+      ranges::to<std::vector<std::vector<int>>>(rit, iterator::sentinel),
+      expected);
 }
 
 TEST(BoundedRecursiveIteratorVecTest, CanMutatePointedToData) {
   std::vector<std::vector<std::vector<int>>> vec{{{1, 2}}, {{3}, {4, 5}}};
   auto rit = make_recursive_iterator<2>(vec);
   rit->clear();
-  EXPECT_THAT(vec[0][0], testing::IsEmpty());
+  EXPECT_THAT(vec[0][0], IsEmpty());
 }

+ 3 - 9
test/unkeyed_iterator_test.cxx

@@ -1,11 +1,11 @@
-#include "iterator/unkeyed_iterator.hpp"
+#include "iterator/unkeyed_iterator.h"
 
 #include <map>
 #include <vector>
 
 #include "xcode_gtest_helper.h"
 
-#include "iterator/end_aware_iterator.hpp"
+#include "iterator/end_aware_iterator.h"
 
 using iterator::end_aware_iterator;
 using iterator::unkeyed_iterator;
@@ -66,16 +66,10 @@ TEST(UnkeyedIteratorTest, EqualityIsPassthrough) {
               Ne(unkeyed_iterator(++map.begin())));
 }
 
-TEST(UnkeyedIteratorTest, DoesNotGenerateAtEnd) {
-  std::map<int, int> map{{1, 2}, {2, 3}};
-  unkeyed_iterator end(map.end());
-  EXPECT_THAT(decltype(end)(), Ne(end));
-}
-
 TEST(UnkeyedIteratorTest, PropagatesAtEnd) {
   std::map<int, int> map{{1, 2}, {2, 3}};
   unkeyed_iterator end(end_aware_iterator(map.end(), map.end()));
-  EXPECT_THAT(decltype(end)(), end);
+  EXPECT_EQ(end, iterator::sentinel);
 }
 
 TEST(UnkeyedIteratorTest, CanPointToObject) {

+ 4 - 0
test/xcode_gtest_helper.h

@@ -22,6 +22,10 @@
 #if defined(TARGET_OS_OSX)
 // This is a hack to allow XCode to properly display failures when running
 // unit tests.
+#undef EXPECT_EQ
+#define EXPECT_EQ ASSERT_EQ
+#undef EXPECT_NE
+#define EXPECT_NE ASSERT_NE
 #undef EXPECT_THAT
 #define EXPECT_THAT ASSERT_THAT
 #undef EXPECT_THROW

+ 25 - 28
test/zip_iterator_test.cxx

@@ -6,7 +6,7 @@
 //  Copyright © 2020 Sam Jaffe. All rights reserved.
 //
 
-#include "iterator/zip_iterator.hpp"
+#include "iterator/zip_iterator.h"
 
 #include <forward_list>
 #include <list>
@@ -16,6 +16,14 @@
 
 using iterator::zip_iterator;
 
+using testing::Eq;
+using testing::Ge;
+using testing::Gt;
+using testing::Le;
+using testing::Lt;
+using testing::Ne;
+using testing::StaticAssertTypeEq;
+
 TEST(ZipIteratorTest, CategoryIsMostRestrictiveOfTypes) {
   std::vector<int> rnd{1, 2, 3};
   std::vector<std::string> ss{"A", "B", "C"};
@@ -23,37 +31,26 @@ TEST(ZipIteratorTest, CategoryIsMostRestrictiveOfTypes) {
   std::forward_list<int> fwd{1, 2, 3};
   {
     zip_iterator zit(rnd.begin(), ss.begin());
-    testing::StaticAssertTypeEq<
-        std::iterator_traits<decltype(zit)>::iterator_category,
-        std::random_access_iterator_tag>();
+    StaticAssertTypeEq<std::iterator_traits<decltype(zit)>::iterator_category,
+                       std::random_access_iterator_tag>();
   }
   {
     zip_iterator zit(rnd.begin(), bid.begin());
-    testing::StaticAssertTypeEq<
-        std::iterator_traits<decltype(zit)>::iterator_category,
-        std::bidirectional_iterator_tag>();
+    StaticAssertTypeEq<std::iterator_traits<decltype(zit)>::iterator_category,
+                       std::bidirectional_iterator_tag>();
   }
   {
     zip_iterator zit(rnd.begin(), fwd.begin());
-    testing::StaticAssertTypeEq<
-        std::iterator_traits<decltype(zit)>::iterator_category,
-        std::forward_iterator_tag>();
+    StaticAssertTypeEq<std::iterator_traits<decltype(zit)>::iterator_category,
+                       std::forward_iterator_tag>();
   }
   {
     zip_iterator zit(bid.begin(), fwd.begin());
-    testing::StaticAssertTypeEq<
-        std::iterator_traits<decltype(zit)>::iterator_category,
-        std::forward_iterator_tag>();
+    StaticAssertTypeEq<std::iterator_traits<decltype(zit)>::iterator_category,
+                       std::forward_iterator_tag>();
   }
 }
 
-// TEST(ZipIteratorTest, CannotInvokeOperatorArrow) {
-//  std::vector<int> is{1, 2, 3};
-//  std::vector<std::string> ss{"A", "B", "C"};
-//  auto zit = make_zip_iterator(is.begin(), ss.begin());
-//  testing::StaticAssertTypeEq<decltype(zit.operator->()), void>();
-//}
-
 TEST(ZipIteratorTest, CanCombineParallelObjects) {
   std::vector<int> is{1, 2, 3};
   std::vector<std::string> ss{"A", "B", "C"};
@@ -114,12 +111,12 @@ TEST(ZipIteratorTest, CanCompareIterators) {
   std::vector<std::string> ss{"A", "B", "C"};
   zip_iterator const zit(is.begin(), ss.begin());
   zip_iterator const zend(is.end(), ss.end());
-  EXPECT_THAT(zit, testing::Eq(zip_iterator(is.begin(), ss.begin())));
-  EXPECT_THAT(zit, testing::Ne(zend));
-  EXPECT_THAT(zit, testing::Lt(zend));
-  EXPECT_THAT(zit, testing::Le(zend));
-  EXPECT_THAT(zit, testing::Le(zit));
-  EXPECT_THAT(zend, testing::Gt(zit));
-  EXPECT_THAT(zend, testing::Ge(zit));
-  EXPECT_THAT(zend, testing::Ge(zend));
+  EXPECT_THAT(zit, Eq(zip_iterator(is.begin(), ss.begin())));
+  EXPECT_THAT(zit, Ne(zend));
+  EXPECT_THAT(zit, Lt(zend));
+  EXPECT_THAT(zit, Le(zend));
+  EXPECT_THAT(zit, Le(zit));
+  EXPECT_THAT(zend, Gt(zit));
+  EXPECT_THAT(zend, Ge(zit));
+  EXPECT_THAT(zend, Ge(zend));
 }