فهرست منبع

feat: add view_interface, sentinel-propagation helper
fix: distance-to-end for common_iterator
fix: std::invoke in transform_iterator

Sam Jaffe 2 سال پیش
والد
کامیت
9402a60819

+ 1 - 1
external/iterator

@@ -1 +1 @@
-Subproject commit c51d6b7adc07573976bab75dbe0e098f804a9b45
+Subproject commit 52b436acc9636f7cefd11fc1bf7afc41da742f51

+ 6 - 1
include/stream/detail/traits.h

@@ -53,7 +53,12 @@ template <typename It, typename S>
 constexpr bool is_comparable_v = is_comparable<It, S>{};
 
 template <typename S>
-constexpr bool is_sentinal_v = !std::is_same_v<begin_t<S>, end_t<S>>;
+constexpr bool has_sentinal_v = !std::is_same_v<begin_t<S>, end_t<S>>;
+
+template <typename S, template <typename...> class Iter, typename... Ts>
+using sentinel_iterator =
+    std::conditional_t<detail::has_sentinal_v<S>, detail::end_t<S>,
+                       Iter<detail::begin_t<S>, Ts...>>;
 
 template <typename It, typename S>
 constexpr bool is_sized_sentinel_v = is_sized_sentinel<It, S>{};

+ 1 - 0
include/stream/forward.h

@@ -12,6 +12,7 @@ namespace stream::ranges {
 using ::iterator::end_aware_iterator;
 using ::iterator::facade;
 using ::iterator::proxy;
+using ::iterator::sentinel_t;
 
 template <typename> class any_view;
 }

+ 7 - 1
include/stream/iterator/common_iterator.h

@@ -27,7 +27,13 @@ public:
 
   SFINAE(super_t::category_enum == iterator::category::random_access)
   auto distance_to(common_iterator const & other) const {
-    return (at_end() && other.at_end()) ? 0 : super_t::distance_to(other);
+    if (at_end()) {
+      return other.at_end() ? 0 : -(S() - other);
+    } else if (other.at_end()) {
+      return S() - *this;
+    } else {
+      return super_t::distance_to(other);
+    }
   }
 
   bool equal_to(common_iterator const & other) const {

+ 17 - 11
include/stream/iterator/iota_iterator.h

@@ -11,31 +11,37 @@
 
 #include <stream/forward.h>
 
+#include <stream/detail/traits.h>
+
+#include <stream/detail/macro.h>
+
 namespace stream::ranges {
 
 template <typename T, typename Bound>
-class iota_iterator
-    : public facade<iota_iterator<T, Bound>, iterator::category::forward> {
+class iota_iterator : public facade<iota_iterator<T, Bound>,
+                                    iterator::category::random_access> {
 public:
-  using sentinal_type = Bound;
+  using sentinal_type =
+      std::conditional_t<std::is_same_v<T, Bound>, void, Bound>;
 
   iota_iterator() = default;
-  iota_iterator(T value, Bound bound) : value_(value), bound_(bound) {}
+  iota_iterator(T value) : value_(value) {}
 
   T dereference() const { return value_; }
-  void increment() { ++value_; }
-  bool at_end() const { return value_ == bound_; }
-  // Some weird bug in dependent template instantiation???
-  bool equal_to(iota_iterator const & other) const {
-    return value_ == other.value_;
-  }
+  void advance(std::ptrdiff_t off) { value_ += off; }
   std::ptrdiff_t distance_to(iota_iterator const & other) const {
     return other.value_ - value_;
   }
 
+  SFINAE((detail::is_sized_sentinel_v<T, Bound>))
+  friend bool operator-(Bound bound, iota_iterator const & self) {
+    return bound - self.value_;
+  }
+
 private:
   T value_;
-  Bound bound_;
 };
 
 }
+
+#include <stream/detail/undef.h>

+ 31 - 0
include/stream/iterator/sentinel.h

@@ -0,0 +1,31 @@
+//
+//  sentinel.h
+//  stream
+//
+//  Created by Sam Jaffe on 4/8/23.
+//
+
+#pragma once
+
+#include <iterator/sentinel.h>
+
+#include <stream/forward.h>
+
+#include <stream/detail/traits.h>
+
+#include <stream/detail/macro.h>
+
+namespace stream::ranges {
+constexpr sentinel_t sentinel{};
+
+template <typename Self, typename Base, typename... Args>
+auto sentinel_end(Self *, Base && base, Args &&... args) {
+  if constexpr (detail::has_sentinal_v<Base>) {
+    return FWD(base).end();
+  } else {
+    return detail::begin_t<Self>(FWD(base).end(), FWD(args)...);
+  }
+}
+}
+
+#include <stream/detail/undef.h>

+ 1 - 3
include/stream/iterator/transform_iterator.h

@@ -26,10 +26,8 @@ public:
       : super_t(std::move(iter)), projection_(projection) {}
 
   decltype(auto) dereference() const {
-    return projection_(super_t::dereference());
+    return std::invoke(projection_, super_t::dereference());
   }
-
-  bool at_end() const { return super_t::impl().at_end(); }
 };
 }
 

+ 1 - 1
include/stream/to_container.h

@@ -33,7 +33,7 @@ public:
 
 template <template <typename...> class C> struct to_range {
   template <typename Stream> friend auto operator|(Stream && stream, to_range) {
-    if constexpr (detail::is_sentinal_v<Stream>) {
+    if constexpr (detail::has_sentinal_v<Stream>) {
       common_view common(FWD(stream));
       return C(common.begin(), common.end());
     } else {

+ 1 - 1
include/stream/view/any.h

@@ -23,7 +23,7 @@ private:
       common_iterator<detail::begin_t<S>, detail::end_t<S>>;
   template <typename S, typename It>
   using iter_cast_t =
-      std::conditional_t<detail::is_sentinal_v<S>, sentinel_iterator<S>, It>;
+      std::conditional_t<detail::has_sentinal_v<S>, sentinel_iterator<S>, It>;
 
   using make_iter_t = any_iterator<T> (*)(void *);
 

+ 6 - 6
include/stream/view/common.h

@@ -11,12 +11,16 @@
 
 #include <stream/detail/traits.h>
 #include <stream/iterator/common_iterator.h>
+#include <stream/view/interface.h>
 
 #include <stream/detail/macro.h>
 
 namespace stream::ranges {
 
-template <typename S> class common_view {
+template <typename S>
+class common_view
+    : public view_interface<common_view<S>, common_iterator<detail::begin_t<S>,
+                                                            detail::end_t<S>>> {
 private:
   using iterator = common_iterator<detail::begin_t<S>, detail::end_t<S>>;
 
@@ -28,10 +32,6 @@ public:
 
   auto begin() const { return iterator(stream_.begin()); }
   auto end() const { return iterator(stream_.end()); }
-
-  SFINAE(detail::has_empty_v<S>) bool empty() const { return stream_.empty(); }
-
-  SFINAE(detail::has_size_v<S>) size_t size() const { return stream_.size(); }
 };
 
 template <typename S> common_view(S &&) -> common_view<S>;
@@ -40,7 +40,7 @@ template <typename S> common_view(S &&) -> common_view<S>;
 namespace stream::ranges::views {
 struct common {
   template <typename Stream> friend auto operator|(Stream && stream, common) {
-    if constexpr (detail::is_sentinal_v<Stream>) {
+    if constexpr (detail::has_sentinal_v<Stream>) {
       return common_view(FWD(stream));
     } else {
       return FWD(stream);

+ 12 - 5
include/stream/view/filter.h

@@ -1,17 +1,22 @@
 #pragma once
 
-#include <functional>
-
 #include <iterator/filter_iterator.h>
 
 #include <stream/forward.h>
 
 #include <stream/detail/traits.h>
+#include <stream/iterator/sentinel.h>
+#include <stream/view/interface.h>
 
 #include <stream/detail/macro.h>
 
 namespace stream::ranges {
-template <typename S, typename P> class filter_view {
+template <typename S, typename P>
+class filter_view : public proxy_view_interface<filter_view<S, P>, S,
+                                                iterator::filter_iterator, P> {
+public:
+  using iterator = iterator::filter_iterator<detail::begin_t<S>, P>;
+
 private:
   S stream_;
   P predicate_;
@@ -20,8 +25,10 @@ public:
   filter_view(S && stream, P predicate)
       : stream_(FWD(stream)), predicate_(predicate) {}
 
-  auto begin() const { return iterator::filter_iterator(stream_, predicate_); }
-  auto end() const { return iterator::sentinel; }
+  auto begin() const { return iterator(stream_, predicate_); }
+  auto end() const {
+    return sentinel_end(this, stream_, stream_.end(), predicate_);
+  }
 };
 
 template <typename S, typename P> filter_view(S &&, P) -> filter_view<S, P>;

+ 58 - 0
include/stream/view/interface.h

@@ -0,0 +1,58 @@
+//
+//  interface.h
+//  stream
+//
+//  Created by Sam Jaffe on 4/6/23.
+//
+
+#pragma once
+
+#include <stream/forward.h>
+
+#include <stream/detail/traits.h>
+
+#include <stream/detail/macro.h>
+
+namespace stream::ranges {
+template <typename self_type, typename It, typename S = It>
+class view_interface {
+private:
+  using category = iterator::category;
+  using difference_type = typename std::iterator_traits<It>::difference_type;
+  constexpr static auto category_enum = detail::category_for_v<It>;
+  constexpr static bool is_common = std::is_same_v<It, S>;
+
+  constexpr static bool has_distance = detail::is_sized_sentinel_v<It, S>;
+
+public:
+  SFINAE(category_enum >= category::forward || has_distance)
+  bool empty() const { return self().begin() == self().end(); }
+
+  SFINAE(category_enum >= category::forward && has_distance)
+  size_t size() const { return self().end() - self().begin(); }
+
+  SFINAE(category_enum >= category::forward) auto front() const {
+    return *self().begin();
+  }
+
+  SFINAE(category_enum >= category::bidirectional && is_common)
+  auto back() const { return *--self().end(); }
+
+  SFINAE(category_enum >= category::random_access)
+  auto operator[](difference_type off) const { return self().begin()[off]; }
+
+protected:
+  self_type & self() { return *static_cast<self_type *>(this); }
+  self_type const & self() const {
+    return *static_cast<self_type const *>(this);
+  }
+};
+
+template <typename self_type, typename base_type,
+          template <typename...> class iterator, typename... Ts>
+using proxy_view_interface =
+    view_interface<self_type, iterator<detail::begin_t<base_type>, Ts...>,
+                   detail::sentinel_iterator<base_type, iterator, Ts...>>;
+}
+
+#include <stream/detail/undef.h>

+ 21 - 15
include/stream/view/iota.h

@@ -10,14 +10,14 @@
 #include <stream/forward.h>
 
 #include <stream/iterator/iota_iterator.h>
+#include <stream/view/interface.h>
 
 #include <stream/detail/macro.h>
 
 namespace stream::ranges {
-template <typename T, typename Bound> struct iota_view {
-public:
-  constexpr static inline bool has_size_v = std::is_constructible_v<T, Bound>;
-
+template <typename T, typename Bound>
+struct iota_view : public view_interface<iota_view<T, Bound>,
+                                         iota_iterator<T, Bound>, Bound> {
 private:
   T value_;
   Bound bound_;
@@ -25,18 +25,24 @@ private:
 public:
   iota_view(T value, Bound bound) : value_(value), bound_(bound) {}
 
-  auto begin() const { return iota_iterator(value_, bound_); }
-  auto end() const {
-    if constexpr (has_size_v) {
-      return iota_iterator(bound_, bound_);
-    } else {
-      return bound_;
-    }
-  }
-
-  bool empty() const { return begin().at_end(); }
-  SFINAE(has_size_v) size_t size() const { return begin().distance_to(end()); }
+  auto begin() const { return iota_iterator<T, Bound>(value_); }
+  auto end() const { return bound_; }
+};
+
+template <typename T>
+struct iota_view<T, T>
+    : public view_interface<iota_view<T, T>, iota_iterator<T, T>> {
+private:
+  T value_;
+  T bound_;
+
+public:
+  iota_view(T value, T bound) : value_(value), bound_(bound) {}
+
+  auto begin() const { return iota_iterator<T, T>(value_); }
+  auto end() const { return iota_iterator<T, T>(bound_); }
 };
+
 }
 
 namespace stream::ranges::views {

+ 6 - 2
include/stream/view/join.h

@@ -5,11 +5,16 @@
 #include <stream/forward.h>
 
 #include <stream/detail/traits.h>
+#include <stream/view/interface.h>
 
 #include <stream/detail/macro.h>
 
 namespace stream::ranges {
-template <typename S> struct join_view {
+template <typename S>
+struct join_view
+    : public view_interface<join_view<S>,
+                            iterator::joining_iterator<detail::begin_t<S>>,
+                            sentinel_t> {
 private:
   S stream_;
 
@@ -18,7 +23,6 @@ public:
 
   auto begin() const { return iterator::joining_iterator(stream_); }
   auto end() const { return iterator::sentinel; }
-  SFINAE(detail::has_empty_v<S>) bool empty() const { return stream_.empty(); }
 };
 
 template <typename S> join_view(S &&) -> join_view<S>;

+ 3 - 5
include/stream/view/subrange.h

@@ -5,11 +5,13 @@
 #include <stream/forward.h>
 
 #include <stream/detail/traits.h>
+#include <stream/view/interface.h>
 
 #include <stream/detail/macro.h>
 
 namespace stream::ranges {
-template <typename It, typename S = It> class subrange {
+template <typename It, typename S = It>
+class subrange : public view_interface<subrange<It, S>, It, S> {
 private:
   It begin_;
   S end_;
@@ -26,10 +28,6 @@ public:
 
   auto begin() const { return begin_; }
   auto end() const { return end_; }
-  bool empty() const { return begin_ == end_; }
-  SFINAE((detail::is_sized_sentinel_v<It, S>)) size_t size() const {
-    return end_ - begin_;
-  }
 };
 
 template <typename It, typename S> subrange(It, S) -> subrange<It, S>;

+ 10 - 22
include/stream/view/transform.h

@@ -5,13 +5,19 @@
 #include <stream/forward.h>
 
 #include <stream/detail/traits.h>
+#include <stream/iterator/sentinel.h>
 #include <stream/iterator/transform_iterator.h>
+#include <stream/view/interface.h>
 
 #include <stream/detail/macro.h>
 
 namespace stream::ranges {
-template <typename S, typename Proj> class transform_view {
-private:
+template <typename S, typename Proj>
+class transform_view : public proxy_view_interface<transform_view<S, Proj>, S,
+                                                   transform_iterator, Proj> {
+public:
+  using iterator = transform_iterator<detail::begin_t<S>, Proj>;
+
 private:
   S stream_;
   Proj projection_;
@@ -20,26 +26,8 @@ public:
   transform_view(S && stream, Proj projection)
       : stream_(FWD(stream)), projection_(projection) {}
 
-  auto begin() const { return transform_iterator(stream_.begin(), invoke()); }
-
-  auto end() const {
-    if constexpr (detail::is_sentinal_v<S>) {
-      return stream_.end();
-    } else {
-      return transform_iterator(stream_.end(), invoke());
-    }
-  }
-
-  SFINAE(detail::has_empty_v<S>) bool empty() const { return stream_.empty(); }
-
-  SFINAE(detail::has_size_v<S>) size_t size() { return stream_.size(); }
-
-private:
-  auto invoke() const {
-    return std::function([this](detail::cref_t<S> t) -> decltype(auto) {
-      return std::invoke(projection_, t);
-    });
-  }
+  auto begin() const { return iterator(stream_.begin(), projection_); }
+  auto end() const { return sentinel_end(this, stream_, projection_); }
 };
 
 template <typename S, typename Proj>

+ 4 - 0
stream.xcodeproj/project.pbxproj

@@ -118,6 +118,7 @@
 		CD6EBE1C29D5C61700F387C1 /* common.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = common.h; sourceTree = "<group>"; };
 		CD6EBE2329D5CAD900F387C1 /* size.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = size.h; sourceTree = "<group>"; };
 		CD6EBE2629D63B9E00F387C1 /* minmax.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = minmax.h; sourceTree = "<group>"; };
+		CD94CE1929E1B7EF00AFA3A6 /* sentinel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = sentinel.h; sourceTree = "<group>"; };
 		CDA37D3929D9BDED000A1F97 /* macro.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = macro.h; sourceTree = "<group>"; };
 		CDA37D3A29D9BDF2000A1F97 /* undef.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = undef.h; sourceTree = "<group>"; };
 		CDA37D3B29D9C30B000A1F97 /* common_iterator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = common_iterator.h; sourceTree = "<group>"; };
@@ -141,6 +142,7 @@
 		CDEC1D5B23514BB50091D9F2 /* GoogleMock.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = GoogleMock.xcodeproj; path = "../../../../gmock-xcode-master/GoogleMock.xcodeproj"; sourceTree = "<group>"; };
 		CDEC1D6E23514BC60091D9F2 /* stream-test.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "stream-test.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
 		CDEC1D7223514BC60091D9F2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		CDFB3D1529DFA67F00B8F048 /* interface.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = interface.h; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -291,6 +293,7 @@
 				CD5A55AC29DF92D900881E7E /* any_iterator.h */,
 				CDA37D3B29D9C30B000A1F97 /* common_iterator.h */,
 				CD5A55AB29DF920100881E7E /* iota_iterator.h */,
+				CD94CE1929E1B7EF00AFA3A6 /* sentinel.h */,
 				CDA37D3C29D9C311000A1F97 /* transform_iterator.h */,
 			);
 			path = iterator;
@@ -311,6 +314,7 @@
 				CD6EBE1C29D5C61700F387C1 /* common.h */,
 				CD52827729D4EED6001A84DE /* empty.h */,
 				CD5281F529D3B173001A84DE /* filter.h */,
+				CDFB3D1529DFA67F00B8F048 /* interface.h */,
 				CD52827A29D4EFBE001A84DE /* iota.h */,
 				CD5281EB29D3B173001A84DE /* join.h */,
 				CD52828129D5133C001A84DE /* map.h */,

+ 2 - 2
test/common_test.cxx

@@ -41,8 +41,8 @@ TEST(CommonView, NotRequiredToProvideSizeOrEmpty) {
 
   auto range = input | views::common();
 
-  static_assert(!stream::detail::has_size_v<decltype(range)>);
-  static_assert(!stream::detail::has_empty_v<decltype(range)>);
+  static_assert(stream::detail::has_size_v<decltype(range)>);
+  static_assert(stream::detail::has_empty_v<decltype(range)>);
 }
 
 TEST(CommonView, PropagatesSize) {

+ 1 - 1
test/filter_test.cxx

@@ -21,7 +21,7 @@ TEST(FilterView, DoesNotPropagatesEmpty) {
   Range<std::vector<int>, Sentinel, Sized> input{0, 1, 2, 3, 4};
 
   auto range = input | views::filter([](int) { return true; });
-  static_assert(!stream::detail::has_empty_v<decltype(range)>);
+  static_assert(stream::detail::has_empty_v<decltype(range)>);
 }
 
 TEST(FilterView, NoOpFilterReturnOriginal) {

+ 4 - 2
test/transform_test.cxx

@@ -7,6 +7,8 @@
 
 #include "stream/view/transform.h"
 
+#include <list>
+
 #include "stream_helpers.h"
 #include "stream_matchers.h"
 
@@ -14,12 +16,12 @@
 template class std::basic_string<char>;
 
 TEST(TransformView, NotRequiredToProvideSizeOrEmpty) {
-  Range<std::vector<int>, Sentinel> input{0, 1, 2, 3, 4};
+  Range<std::list<int>, Sentinel> input{0, 1, 2, 3, 4};
 
   auto range = input | views::transform([](int i) { return i; });
 
   static_assert(!stream::detail::has_size_v<decltype(range)>);
-  static_assert(!stream::detail::has_empty_v<decltype(range)>);
+  static_assert(stream::detail::has_empty_v<decltype(range)>);
 }
 
 TEST(TransformView, PropagatesSize) {