From 921070559890c88c62a988b3898ea0be7486dd13 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 10 Jul 2024 21:34:02 +0200 Subject: [PATCH] util/TerminatedArray: new class --- src/util/TerminatedArray.hxx | 128 ++++++++++++++++++++++++++++++ test/util/TestTerminatedArray.cxx | 34 ++++++++ test/util/meson.build | 1 + 3 files changed, 163 insertions(+) create mode 100644 src/util/TerminatedArray.hxx create mode 100644 test/util/TestTerminatedArray.cxx diff --git a/src/util/TerminatedArray.hxx b/src/util/TerminatedArray.hxx new file mode 100644 index 000000000..b0d361f66 --- /dev/null +++ b/src/util/TerminatedArray.hxx @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: BSD-2-Clause +// author: Max Kellermann + +#pragma once + +#include // for std::iterator_traits + +/** + * A container-like class which allows iterating over an array + * terminated by sentinel value. Most commonly, this is an array of + * pointers terminated by nullptr, but null-terminated C strings can + * also be used. + * + * @param T the type of an item in the aray + * @param Sentinel the sentinel value + */ +template +class TerminatedArray { + T *head; + + /** + * A special type for the end iterator which is always equal + * the iterator that points to the #Sentinel value. + */ + struct sentinel { + using Traits = std::iterator_traits; + + using iterator_category = typename Traits::iterator_category; + using difference_type = typename Traits::difference_type; + using value_type = typename Traits::value_type; + using pointer = typename Traits::pointer; + using reference = typename Traits::reference; + }; + +public: + using value_type = T; + + explicit constexpr TerminatedArray(T *_head) noexcept + :head(_head) {} + + class iterator { + using Traits = std::iterator_traits; + + T *cursor; + + public: + using iterator_category = typename Traits::iterator_category; + using difference_type = typename Traits::difference_type; + using value_type = typename Traits::value_type; + using pointer = typename Traits::pointer; + using reference = typename Traits::reference; + + explicit constexpr iterator(T *_cursor) noexcept + :cursor(_cursor) {} + + constexpr bool operator==(const iterator &other) const noexcept { + return cursor == other.cursor; + } + + constexpr bool operator==(const sentinel &) const noexcept { + return *cursor == Sentinel; + } + + constexpr auto &operator++() noexcept { + ++cursor; + return *this; + } + + constexpr auto operator++(int) noexcept { + auto old = *this; + ++cursor; + return old; + } + + constexpr auto &operator+=(difference_type n) noexcept { + cursor += n; + return *this; + } + + constexpr auto operator+(difference_type n) const noexcept { + return iterator{cursor + n}; + } + + constexpr auto &operator--() noexcept { + --cursor; + return *this; + } + + constexpr auto operator--(int) noexcept { + auto old = *this; + --cursor; + return old; + } + + constexpr auto &operator-=(difference_type n) noexcept { + cursor -= n; + return *this; + } + + constexpr auto operator-(difference_type n) const noexcept { + return iterator{cursor - n}; + } + + reference operator*() const noexcept { + return *cursor; + } + + pointer operator->() const noexcept { + return cursor; + } + }; + + constexpr iterator begin() const noexcept { + return iterator{head}; + } + + constexpr sentinel end() const noexcept { + return {}; + } + + constexpr iterator cbegin() const noexcept { + return begin(); + } + + constexpr sentinel cend() const noexcept { + return end(); + } +}; diff --git a/test/util/TestTerminatedArray.cxx b/test/util/TestTerminatedArray.cxx new file mode 100644 index 000000000..8a9339b60 --- /dev/null +++ b/test/util/TestTerminatedArray.cxx @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: BSD-2-Clause +// author: Max Kellermann + +#include "util/TerminatedArray.hxx" + +#include + +TEST(TerminatedArray, PointerArray) +{ + const char *const raw_array[] = {"foo", "bar", nullptr}; + + TerminatedArray array{raw_array}; + EXPECT_STREQ(*array.begin(), "foo"); + EXPECT_STREQ(*std::next(array.begin()), "bar"); + EXPECT_EQ(std::prev(std::next(array.begin())), array.begin()); + EXPECT_NE(array.begin(), array.end()); + EXPECT_NE(std::next(array.begin()), array.end()); + EXPECT_EQ(std::next(array.begin(), 2), array.end()); +} + +TEST(TerminatedArray, CSTring) +{ + const char raw_array[] = "abc"; + + TerminatedArray array{raw_array}; + EXPECT_EQ(*array.begin(), 'a'); + EXPECT_EQ(*std::next(array.begin()), 'b'); + EXPECT_EQ(*std::next(array.begin(), 2), 'c'); + EXPECT_EQ(std::prev(std::next(array.begin())), array.begin()); + EXPECT_NE(array.begin(), array.end()); + EXPECT_NE(std::next(array.begin()), array.end()); + EXPECT_NE(std::next(array.begin(), 2), array.end()); + EXPECT_EQ(std::next(array.begin(), 3), array.end()); +} diff --git a/test/util/meson.build b/test/util/meson.build index b0623dbcf..30e6be00f 100644 --- a/test/util/meson.build +++ b/test/util/meson.build @@ -14,6 +14,7 @@ test( 'TestSplitString.cxx', 'TestStringStrip.cxx', 'TestTemplateString.cxx', + 'TestTerminatedArray.cxx', 'TestUriExtract.cxx', 'TestUriQueryParser.cxx', 'TestUriRelative.cxx',