diff --git a/include/tscpp/util/TextView.h b/include/tscpp/util/TextView.h index 0ac740d6517..0bdb600e333 100644 --- a/include/tscpp/util/TextView.h +++ b/include/tscpp/util/TextView.h @@ -27,6 +27,7 @@ #pragma once #include #include +#include #include #include #include @@ -1168,7 +1169,242 @@ TextView::stream_write(Stream &os, const TextView &b) const // Provide an instantiation for @c std::ostream as it's likely this is the only one ever used. extern template std::ostream &TextView::stream_write(std::ostream &, const TextView &) const; -} // namespace ts +/** A transform view. + * + * @tparam X Transform functor type. + * @tparam V Source view type. + * + * A transform view acts like a view on the original source view @a V with each element transformed by + * @a X. + * + * This is used most commonly with @c std::string_view. For example, if the goal is to handle a + * piece of text as if it were lower case without changing the actual text, the following would + * make that possible. + * @code + * std:::string_view source; // original text. + * TransformView xv(&tolower, source); + * @endcode + * + * To avoid having to figure out the exact signature of the transform, the convenience function + * @c transform_view_of is provide. + * @code + * std::string_view source; // original text. + * auto xv = transform_view_of(&tolower, source); + * @endcode + */ +template class TransformView +{ + using self_type = TransformView; ///< Self reference type. + using iter = decltype(static_cast(nullptr)->begin()); + +public: + using transform_type = X; ///< Export transform functor type. + using source_view_type = V; ///< Export source view type. + using source_value_type = decltype(**static_cast(nullptr)); + /// Result type of calling the transform on an element of the source view. + using value_type = typename std::invoke_result::type; + + /** Construct a transform view using transform @a xf on source view @a v. + * + * @param xf Transform instance. + * @param v Source view. + */ + TransformView(transform_type &&xf, source_view_type const &v); + + /** Construct a transform view using transform @a xf on source view @a v. + * + * @param xf Transform instance. + * @param v Source view. + */ + TransformView(transform_type const &xf, source_view_type const &v); + + /// Copy constructor. + TransformView(self_type const &that) = default; + /// Move constructor. + TransformView(self_type &&that) = default; + + /// Copy assignment. + self_type &operator=(self_type const &that) = default; + /// Move assignment. + self_type &operator=(self_type &&that) = default; + + /// Equality. + bool operator==(self_type const &that) const; + /// Inequality. + bool operator!=(self_type const &that) const; + + /// Get the current element. + value_type operator*() const; + /// Move to next element. + self_type &operator++(); + /// Move to next element. + self_type operator++(int); + + /// Check if view is empty. + bool empty() const; + /// Check if bool is not empty. + explicit operator bool() const; + +protected: + transform_type _xf; + iter _spot; + iter _limit; +}; + +template +TransformView::TransformView(transform_type &&xf, source_view_type const &v) : _xf(xf), _spot(v.begin()), _limit(v.end()) +{ +} + +template +TransformView::TransformView(transform_type const &xf, source_view_type const &v) : _xf(xf), _spot(v.begin()), _limit(v.end()) +{ +} + +template auto TransformView::operator*() const -> value_type +{ + return _xf(*_spot); +} + +template +auto +TransformView::operator++() -> self_type & +{ + ++_spot; + return *this; +} + +template +auto +TransformView::operator++(int) -> self_type +{ + self_type zret{*this}; + ++_spot; + return zret; +} + +template +bool +TransformView::empty() const +{ + return _spot == _limit; +} + +template TransformView::operator bool() const +{ + return _spot != _limit; +} + +template +bool +TransformView::operator==(self_type const &that) const +{ + return _spot == that._spot && _limit == that._limit; +} + +template +bool +TransformView::operator!=(self_type const &that) const +{ + return _spot != that._spot || _limit != that._limit; +} + +template +TransformView +transform_view_of(X const &xf, V const &v) +{ + return TransformView(xf, v); +} + +// Specialization for identity transform. +template class TransformView +{ + using self_type = TransformView; ///< Self reference type. + using iter = decltype(static_cast(nullptr)->begin()); + +public: + using source_view_type = V; ///< Export source view type. + using source_value_type = decltype(**static_cast(nullptr)); + /// Result type of calling the transform on an element of the source view. + using value_type = source_value_type; + + /** Construct identity transform view from @a v. + * + * @param v Source view. + */ + TransformView(source_view_type const &v) : _spot(v.begin()), _limit(v.end()) {} + + /// Copy constructor. + TransformView(self_type const &that) = default; + /// Move constructor. + TransformView(self_type &&that) = default; + + /// Copy assignment. + self_type &operator=(self_type const &that) = default; + /// Move assignment. + self_type &operator=(self_type &&that) = default; + + /// Equality. + bool operator==(self_type const &that) const; + /// Inequality. + bool operator!=(self_type const &that) const; + + /// Get the current element. + value_type operator*() const { return *_spot; } + /// Move to next element. + self_type & + operator++() + { + ++_spot; + return *this; + } + /// Move to next element. + self_type + operator++(int) + { + auto zret{*this}; + ++*this; + return zret; + } + + /// Check if view is empty. + bool + empty() const + { + return _spot == _limit; + } + /// Check if bool is not empty. + explicit operator bool() const { return _spot != _limit; } + +protected: + iter _spot; + iter _limit; +}; + +template +TransformView +transform_view_of(V const &v) +{ + return TransformView(v); +} + +// Avoid complaints about no operator for cross type comparisons - just return false. If the +// types are the same the class method will be used. +template +bool +operator==(TransformView const &, TransformView const &) +{ + return false; +} + +template +bool +operator!=(TransformView const &, TransformView const &) +{ + return false; +} + +}; // namespace ts namespace std { @@ -1187,6 +1423,13 @@ template <> struct iterator_traits { using iterator_category = forward_iterator_tag; }; +template struct iterator_traits> { + using value_type = typename ts::TransformView::value_type; + using pointer_type = const value_type *; + using reference_type = const value_type &; + using difference_type = ssize_t; + using iterator_category = forward_iterator_tag; +}; } // namespace std // @c constexpr literal constructor for @c std::string_view diff --git a/src/tscpp/util/unit_tests/test_TextView.cc b/src/tscpp/util/unit_tests/test_TextView.cc index d9ce540b1c8..de9cd8a2fd1 100644 --- a/src/tscpp/util/unit_tests/test_TextView.cc +++ b/src/tscpp/util/unit_tests/test_TextView.cc @@ -315,3 +315,39 @@ TEST_CASE("TextView Conversions", "[libts][TextView]") REQUIRE(25 == svtoi(n3)); REQUIRE(31 == svtoi(n3, nullptr, 10)); } + +TEST_CASE("TransformView", "[libts][TransformView]") +{ + std::string_view source{"Evil Dave Rulz"}; + ts::TransformView xv1(&tolower, source); + // clang and gnu differ on the type of tolower wrt "noexcept". This makes xv1 a different type + // from xv2 (or not) depending on the compiler. Therefore we need xv3 to test the equality operator. + auto xv2 = ts::transform_view_of(&tolower, source); + auto xv3 = ts::transform_view_of(&tolower, source); + TextView tv{source}; + + bool match_p = true; + while (xv1) { + if (*xv1 != tolower(*tv)) { + match_p = false; + break; + } + ++xv1; + ++tv; + } + REQUIRE(match_p); + + REQUIRE(xv2 == xv3); + tv = source; + match_p = true; + while (xv2) { + if (*xv2 != tolower(*tv)) { + match_p = false; + break; + } + ++xv2; + ++tv; + } + REQUIRE(match_p); + REQUIRE(xv2 != xv3); +};