Преглед на файлове

refactor: convert facade et al to use concepts

Sam Jaffe преди 2 месеца
родител
ревизия
d77e51c2b4

+ 3 - 5
include/iterator/capture_iterator.h

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

+ 78 - 0
include/iterator/concepts.h

@@ -0,0 +1,78 @@
+//
+//  concepts.h
+//  iterator
+//
+//  Created by Sam Jaffe on 9/20/25.
+//  Copyright © 2025 Sam Jaffe. All rights reserved.
+//
+
+#pragma once
+
+#include <concepts>
+
+namespace iterator {
+template <typename It>
+concept implements_distance_to = requires(It const &it) {
+  { it.distance_to(it) } -> std::integral;
+};
+
+template <typename> struct infer_difference_type {
+  using type = std::ptrdiff_t;
+};
+
+template <implements_distance_to It>
+struct infer_difference_type<It> {
+  static const It& _it;
+  using type = decltype(_it.distance_to(_it));
+};
+
+template <typename It>
+using infer_difference_type_t = typename infer_difference_type<It>::type;
+
+template <typename D, typename It>
+concept difference_type_arg = std::convertible_to<D, infer_difference_type_t<It>>;
+
+template <typename It>
+struct infer_value_type {
+  static const It& _it;
+  using type = std::remove_cvref_t<decltype(*_it)>;
+};
+
+template <typename It>
+  requires requires { typename It::value_type; }
+struct infer_value_type<It> {
+  using type = typename It::value_type;
+};
+
+template <typename It>
+using infer_value_type_t = infer_value_type<It>::type;
+
+template <typename T, typename U> concept not_same_as = not std::same_as<T, U>;
+
+template <typename It>
+concept has_sentinel = requires(It const &it) {
+  { it.as_end() } -> std::same_as<bool>;
+};
+
+template <typename It>
+concept single_pass = bool(It::single_pass_iterator);
+
+template <typename It>
+concept forward = requires(It &it) {
+  { it.dereference() } -> not_same_as<void>;
+  { it.equal_to(it) } -> std::same_as<bool>;
+  { it.increment() } -> std::same_as<void>;
+};
+
+template <typename It>
+concept bidirectional = forward<It> && requires(It &it) {
+  { it.decrement() } -> std::same_as<void>;
+};
+
+template <typename It>
+concept random_access = requires(It &it, infer_difference_type_t<It> offset) {
+  { it.dereference() } -> not_same_as<void>;
+  { it.equal_to(it) } -> std::same_as<bool>;
+  { it.advance(offset) } -> std::same_as<void>;
+};
+}

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

@@ -31,49 +31,6 @@ struct sentinel_type<It, std::void_t<typename It::sentinel_type>> {
 };
 }
 
-// Mappings between iterator::category enum and iterator_category tags
-namespace iterator::detail {
-template <typename Tag, category Limit = static_cast<category>(~0)>
-struct category_for {
-  constexpr static category value = category::single_pass;
-};
-
-template <category Limit>
-struct category_for<std::forward_iterator_tag, Limit> {
-  constexpr static category value = std::min(category::forward, Limit);
-};
-
-template <category Limit>
-struct category_for<std::bidirectional_iterator_tag, Limit> {
-  constexpr static category value = std::min(category::bidirectional, Limit);
-};
-
-template <category Limit>
-struct category_for<std::random_access_iterator_tag, Limit> {
-  constexpr static category value = std::min(category::random_access, Limit);
-};
-
-template <category> struct tag_for;
-template <> struct tag_for<category::single_pass> {
-  using type = std::input_iterator_tag;
-};
-
-template <category> struct tag_for;
-template <> struct tag_for<category::forward> {
-  using type = std::forward_iterator_tag;
-};
-
-template <category> struct tag_for;
-template <> struct tag_for<category::bidirectional> {
-  using type = std::bidirectional_iterator_tag;
-};
-
-template <category> struct tag_for;
-template <> struct tag_for<category::random_access> {
-  using type = std::random_access_iterator_tag;
-};
-}
-
 namespace iterator::detail {
 template <typename C> constexpr bool is_container_v = is_container<C>{};
 
@@ -85,12 +42,6 @@ constexpr bool has_sentinel_type_v = !std::is_void_v<typename sentinel_type<It>:
 
 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>

+ 2 - 3
include/iterator/end_aware_iterator.h

@@ -43,15 +43,14 @@ public:
       : super_t(other.impl()), end_(other.end_) {}
 
   bool at_end() const {
-    if constexpr (super_t::category_enum == category::random_access) {
+    if constexpr (std::random_access_iterator<It>) {
       return super_t::impl() >= end_;
     } else {
       return super_t::impl() == end_;
     }
   }
   
-  SFINAE(super_t::category_enum == category::random_access)
-  friend auto operator-(sentinel_type, end_aware_iterator const &self) {
+  friend auto operator-(sentinel_type, end_aware_iterator const &self) requires(std::random_access_iterator<It>) {
     return self.end() - self.impl();
   }
 

+ 83 - 80
include/iterator/facade.h

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

+ 3 - 6
include/iterator/filter_iterator.h

@@ -19,13 +19,10 @@
 namespace iterator {
 template <typename Iter, typename Pred>
 class filter_iterator
-    : public facade<filter_iterator<Iter, Pred>,
-                    detail::category_for_v<Iter, category::bidirectional>> {
+    : public facade<filter_iterator<Iter, Pred>> {
 public:
   using sentinel_type = sentinel_t;
-
-  using super_t = facade<filter_iterator<Iter, Pred>,
-                         detail::category_for_v<Iter, category::bidirectional>>;
+  using super_t = filter_iterator::facade;
 
 public:
   filter_iterator() = default;
@@ -54,7 +51,7 @@ public:
     } while (should_advance());
   }
 
-  SFINAE(super_t::category_enum == category::bidirectional) void decrement() {
+  void decrement() requires(std::bidirectional_iterator<Iter>) {
     do {
       --base_;
     } while (should_advance());

+ 13 - 12
include/iterator/forwards.h

@@ -7,6 +7,7 @@
 
 #pragma once
 
+#include <concepts>
 #include <cstdlib>
 #include <iterator>
 
@@ -27,20 +28,20 @@ enum class category : unsigned char {
 struct sentinel_t;
 
 // Iterator types
-template <typename Iterator> class end_aware_iterator;
-template <typename Iterator, typename Predicate> class filter_iterator;
-template <typename OuterIterator> class joining_iterator;
-template <typename Iterator> class unkeyed_iterator;
-template <typename... Iterators> class zip_iterator;
+template <typename It> class end_aware_iterator;
+template <typename It, typename Predicate> class filter_iterator;
+template <typename OIt> class joining_iterator;
+template <typename It> class unkeyed_iterator;
+template <typename... Its> class zip_iterator;
 
-template <typename Iterator>
-using recursive_iterator = recursive::rimpl<Iterator>;
-template <typename Iterator, std::size_t N>
-using recursive_iterator_n = recursive::rimpl<Iterator, recursive::bounded<N>>;
+template <typename It>
+using recursive_iterator = recursive::rimpl<It>;
+template <typename It, std::size_t N>
+using recursive_iterator_n = recursive::rimpl<It, recursive::bounded<N>>;
 
-template <typename CRTP, category C> class facade;
+template <typename CRTP> class facade;
 
-template <typename Iterator, typename CRTP,
-          typename = typename std::iterator_traits<Iterator>::iterator_category>
+template <typename It, typename CRTP,
+          typename = typename std::iterator_traits<It>::iterator_category>
 class proxy;
 }

+ 6 - 9
include/iterator/indexed_iterator.h

@@ -17,13 +17,13 @@
 namespace iterator {
 template <typename It>
 class indexed_iterator
-    : public facade<indexed_iterator<It>, detail::category_for_v<It>> {
+    : public facade<indexed_iterator<It>> {
 public:
   using reference = std::pair<size_t, DEREF_TYPE(It)>;
   using difference_type = typename std::iterator_traits<It>::difference_type;
 
 private:
-  using super_t = facade<indexed_iterator<It>, detail::category_for_v<It>>;
+  using super_t = facade<indexed_iterator<It>>;
 
 public:
   indexed_iterator() = default;
@@ -36,29 +36,26 @@ public:
 
   reference dereference() const { return {index_, *base_}; }
 
-  SFINAE(super_t::category_enum >= category::forward) void decrement() {
+  void increment() requires(std::forward_iterator<It>) {
     ++base_;
     ++index_;
   }
 
-  SFINAE(super_t::category_enum >= category::bidirectional) void increment() {
+  void decrement() requires(std::bidirectional_iterator<It>) {
     --base_;
     --index_;
   }
 
-  SFINAE(super_t::category_enum >= category::random_access)
-  void advance(difference_type off) {
+  void advance(difference_type off) requires(std::random_access_iterator<It>) {
     base_ += off;
     index_ += off;
   }
 
-  SFINAE(super_t::category_enum < category::random_access)
   bool equal_to(indexed_iterator const & other) const {
     return base_ == other.base_;
   }
 
-  SFINAE(super_t::category_enum >= category::random_access)
-  difference_type distance_to(indexed_iterator const & other) const {
+  difference_type distance_to(indexed_iterator const & other) const requires(std::random_access_iterator<It>) {
     return other.base_ - base_;
   }
 

+ 1 - 1
include/iterator/join_iterator.h

@@ -21,7 +21,7 @@
 namespace iterator {
 template <typename It>
 class joining_iterator
-    : public facade<joining_iterator<It>, category::forward> {
+    : public facade<joining_iterator<It>> {
 private:
   template <typename Ot> friend class joining_iterator;
   constexpr static bool requires_caching = detail::is_rvalue_iterator_v<It>;

+ 6 - 5
include/iterator/proxy.h

@@ -7,9 +7,9 @@
 
 namespace iterator {
 template <typename It, typename Self, typename Cat>
-class proxy : public facade<Self, category::single_pass> {
+class proxy : public facade<Self> {
 public:
-  using single_pass_iterator = void;
+  static constexpr bool single_pass_iterator = true;
 
 private:
   It impl_;
@@ -32,7 +32,7 @@ protected:
 
 template <typename It, typename Self>
 class proxy<It, Self, std::forward_iterator_tag>
-    : public facade<Self, category::forward> {
+    : public facade<Self> {
 private:
   It impl_;
 
@@ -54,7 +54,7 @@ protected:
 
 template <typename It, typename Self>
 class proxy<It, Self, std::bidirectional_iterator_tag>
-    : public facade<Self, category::bidirectional> {
+    : public facade<Self> {
 private:
   It impl_;
 
@@ -77,7 +77,7 @@ protected:
 
 template <typename It, typename Self>
 class proxy<It, Self, std::random_access_iterator_tag>
-    : public facade<Self, category::random_access> {
+    : public facade<Self> {
 public:
   using difference_type = typename std::iterator_traits<It>::difference_type;
 
@@ -91,6 +91,7 @@ public:
 
   decltype(auto) dereference() const { return *impl_; }
   void advance(difference_type off) { impl_ += off; }
+  bool equal_to(Self const & other) const { return impl_ == other.impl_; }
   difference_type distance_to(Self const & other) const {
     return other.impl_ - impl_;
   }

+ 12 - 10
include/iterator/recursive_iterator.h

@@ -84,7 +84,7 @@ 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>, category::forward> {
+class rimpl : public facade<rimpl<It, Bnd>> {
 public:
   using sentinel_type = sentinel_t;
   using iters_t = typename tuple<It, Bnd>::iter;
@@ -118,26 +118,28 @@ public:
       return build_tuple();
     }
   }
+  
+  void increment() { increment_i(); }
 
-  template <size_t I = size - 1> bool increment() {
+  bool at_end() const { return std::get<0>(impl_).at_end(); }
+  bool equal_to(rimpl const & other) const { return impl_ == other.impl_; }
+
+  // Used by std::get, don't use elsewhere...
+  auto const & impl() const { return impl_; }
+
+private:
+  template <size_t I = size - 1> bool increment_i() {
     auto & iter = std::get<I>(impl_);
     if (iter.at_end()) { return false; } // Make sure we don't go OOB
     ++iter;
     if constexpr (I > 0) {
-      while (iter.at_end() && increment<I - 1>()) {
+      while (iter.at_end() && increment_i<I - 1>()) {
         assign<I>(*std::get<I - 1>(impl_));
       }
     }
     return !iter.at_end();
   }
 
-  bool at_end() const { return std::get<0>(impl_).at_end(); }
-  bool equal_to(rimpl const & other) const { return impl_ == other.impl_; }
-
-  // Used by std::get, don't use elsewhere...
-  auto const & impl() const { return impl_; }
-
-private:
   template <size_t I> decltype(auto) get() const {
     auto it = std::get<I>(impl_);
     // In the case of a bounded recursive iterator, I need to ensure that the

+ 5 - 9
include/iterator/zip_iterator.h

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

+ 69 - 20
iterator.xcodeproj/project.pbxproj

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

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

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

+ 8 - 0
test/end_aware_iterator_test.cxx

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