diff options
| -rw-r--r-- | libs/kstd/kstd/ring_buffer.hpp | 26 | ||||
| -rw-r--r-- | libs/kstd/kstd/ring_buffer.tests.cpp | 193 |
2 files changed, 219 insertions, 0 deletions
diff --git a/libs/kstd/kstd/ring_buffer.hpp b/libs/kstd/kstd/ring_buffer.hpp index 99584a90..a87ee381 100644 --- a/libs/kstd/kstd/ring_buffer.hpp +++ b/libs/kstd/kstd/ring_buffer.hpp @@ -2,6 +2,7 @@ #define KSTD_RING_BUFFER_HPP #include <kstd/bits/basic_storage.hpp> +#include <kstd/bits/concepts.hpp> #include <kstd/os/error.hpp> #include <kstd/ranges.hpp> @@ -384,6 +385,31 @@ namespace kstd } } + //! Construct a ring buffer from a range. + //! + //! @warning This function will panic if the input range contains more elements than the ring buffer can support. + //! + //! @tparam Range The type of the source range. + //! @param range The input range to construct the ring buffer from. + template<std::ranges::input_range Range> + requires((std::ranges::forward_range<Range> || std::ranges::sized_range<Range>) && + kstd::bits::container_compatible_range<Range, ValueType>) + constexpr ring_buffer(kstd::from_range_t, Range && range) + { + if (static_cast<size_type>(std::ranges::distance(range)) > capacity()) + { + os::panic("[KSTD] Tried to construct a ring buffer with more elements than it can support."); + } + + if constexpr (Capacity > 0) + { + auto guard = construction_guard{*this}; + std::ranges::for_each(std::forward<Range>(range), + [this](auto && element) { this->push_back(std::forward<decltype(element)>(element)); }); + guard.disarm(); + } + } + //! Replace the content of this ring buffer with a copy of the content of another one. //! //! @param other The ring buffer to copy from. diff --git a/libs/kstd/kstd/ring_buffer.tests.cpp b/libs/kstd/kstd/ring_buffer.tests.cpp index ef3452d3..2517ff7b 100644 --- a/libs/kstd/kstd/ring_buffer.tests.cpp +++ b/libs/kstd/kstd/ring_buffer.tests.cpp @@ -1,5 +1,6 @@ #include <kstd/ring_buffer.hpp> +#include <kstd/ranges.hpp> #include <kstd/test_support/os_panic.hpp> #include <kstd/test_support/test_types.hpp> @@ -18,6 +19,7 @@ #include <string> #include <type_traits> #include <utility> +#include <vector> namespace { @@ -721,6 +723,175 @@ SCENARIO("Ring Buffer initialization and construction", "[kstd][ring_buffer]") } } } + + GIVEN("A forward range with fewer elements than the capacity, constructed via kstd::from_range") + { + auto source = std::forward_list<int>{1, 2, 3}; + WHEN("constructing from the range") + { + auto buffer = kstd::ring_buffer<int, 5>{kstd::from_range, source}; + THEN("the buffer contains the elements of the range in order") + { + REQUIRE_THAT(buffer, Catch::Matchers::RangeEquals({1, 2, 3})); + } + } + } + + GIVEN("A forward range with exactly as many elements as the capacity, constructed via kstd::from_range") + { + auto source = std::forward_list<int>{1, 2, 3}; + + WHEN("constructing from the range") + { + auto buffer = kstd::ring_buffer<int, 3>{kstd::from_range, source}; + + THEN("the buffer is full and contains the elements of the range in order") + { + REQUIRE(buffer.size() == buffer.capacity()); + REQUIRE_THAT(buffer, Catch::Matchers::RangeEquals({1, 2, 3})); + } + } + } + + GIVEN("A forward range with more elements than the capacity, constructed via kstd::from_range") + { + auto source = std::forward_list<int>{1, 2, 3, 4, 5}; + + WHEN("constructing from the range") + { + THEN("the constructor panics") + { + REQUIRE_THROWS_MATCHES((kstd::ring_buffer<int, 3>{kstd::from_range, source}), kstd::tests::os_panic, + Catch::Matchers::Message( + "[KSTD] Tried to construct a ring buffer with more elements than it can support.")); + } + } + } + + GIVEN("An empty forward range, constructed via kstd::from_range") + { + auto source = std::forward_list<int>{}; + + WHEN("constructing from the range") + { + auto buffer = kstd::ring_buffer<int, 5>{kstd::from_range, source}; + + THEN("the buffer is empty") + { + REQUIRE(buffer.empty()); + } + } + } + + GIVEN("A forward range of static operation trackers with more elements than the capacity, constructed via " + "kstd::from_range") + { + auto source = std::forward_list<kstd::tests::static_copy_move_tracker>{}; + source.emplace_front(); + source.emplace_front(); + source.emplace_front(); + source.emplace_front(); + source.emplace_front(); + + WHEN("constructing from the range") + { + THEN("the constructor panics without touching storage") + { + kstd::tests::static_copy_move_tracker::reset(); + REQUIRE_THROWS_MATCHES((kstd::ring_buffer<kstd::tests::static_copy_move_tracker, 3>{kstd::from_range, source}), + kstd::tests::os_panic, + Catch::Matchers::Message( + "[KSTD] Tried to construct a ring buffer with more elements than it can support.")); + REQUIRE(kstd::tests::static_copy_move_tracker::copy_ctor_call_count == 0); + REQUIRE(kstd::tests::static_copy_move_tracker::move_ctor_call_count == 0); + } + } + } + + GIVEN("A sized input range that is not a forward range, with fewer elements than the capacity") + { + std::array<int, 3> source{10, 20, 30}; + auto first = kstd::tests::test_input_iterator{source.data(), source.size()}; + auto last = kstd::tests::test_input_iterator{}; + auto range = std::ranges::subrange<kstd::tests::test_input_iterator, kstd::tests::test_input_iterator, + std::ranges::subrange_kind::sized>{first, last, source.size()}; + + STATIC_REQUIRE(std::ranges::sized_range<decltype(range)>); + STATIC_REQUIRE_FALSE(std::ranges::forward_range<decltype(range)>); + + WHEN("constructing from the range") + { + auto buffer = kstd::ring_buffer<int, 5>{kstd::from_range, range}; + + THEN("the buffer contains the elements of the range in order") + { + REQUIRE_THAT(buffer, Catch::Matchers::RangeEquals({10, 20, 30})); + } + } + } + + GIVEN("A sized input range that is not a forward range, with more elements than the capacity") + { + std::array<int, 5> source{1, 2, 3, 4, 5}; + auto first = kstd::tests::test_input_iterator{source.data(), source.size()}; + auto last = kstd::tests::test_input_iterator{}; + auto range = std::ranges::subrange<kstd::tests::test_input_iterator, kstd::tests::test_input_iterator, + std::ranges::subrange_kind::sized>{first, last, source.size()}; + + WHEN("constructing from the range") + { + THEN("the constructor panics") + { + REQUIRE_THROWS_MATCHES((kstd::ring_buffer<int, 3>{kstd::from_range, range}), kstd::tests::os_panic, + Catch::Matchers::Message( + "[KSTD] Tried to construct a ring buffer with more elements than it can support.")); + } + } + } + + GIVEN("A sized, non-forward input range of static operation trackers with more elements than the capacity") + { + std::array<kstd::tests::static_copy_move_tracker, 5> source{}; + auto first = + kstd::tests::mutable_input_iterator<kstd::tests::static_copy_move_tracker>{source.data(), source.size()}; + auto last = kstd::tests::mutable_input_iterator<kstd::tests::static_copy_move_tracker>{}; + auto range = std::ranges::subrange<kstd::tests::mutable_input_iterator<kstd::tests::static_copy_move_tracker>, + kstd::tests::mutable_input_iterator<kstd::tests::static_copy_move_tracker>, + std::ranges::subrange_kind::sized>{first, last, source.size()}; + + WHEN("constructing from the range") + { + THEN("the constructor panics without touching storage") + { + kstd::tests::static_copy_move_tracker::reset(); + REQUIRE_THROWS_MATCHES((kstd::ring_buffer<kstd::tests::static_copy_move_tracker, 3>{kstd::from_range, range}), + kstd::tests::os_panic, + Catch::Matchers::Message( + "[KSTD] Tried to construct a ring buffer with more elements than it can support.")); + REQUIRE(kstd::tests::static_copy_move_tracker::copy_ctor_call_count == 0); + REQUIRE(kstd::tests::static_copy_move_tracker::move_ctor_call_count == 0); + } + } + } + + GIVEN("A range of static operation trackers whose elements are accessed as rvalues") + { + auto source = std::vector<kstd::tests::static_copy_move_tracker>(3); + + WHEN("constructing from the range") + { + kstd::tests::static_copy_move_tracker::reset(); + auto buffer = + kstd::ring_buffer<kstd::tests::static_copy_move_tracker, 5>{kstd::from_range, std::views::as_rvalue(source)}; + + THEN("each element is moved into the buffer, not copied") + { + REQUIRE(buffer.size() == 3); + REQUIRE(kstd::tests::static_copy_move_tracker::move_ctor_call_count == 3); + REQUIRE(kstd::tests::static_copy_move_tracker::copy_ctor_call_count == 0); + } + } + } } SCENARIO("Ring Buffer assignment", "[kstd][ring_buffer]") @@ -2241,6 +2412,28 @@ SCENARIO("Ring Buffer zero capacity", "[kstd][ring_buffer]") } } + WHEN("constructing by from-range constructor with an empty range") + { + auto source = std::forward_list<int>{}; + auto buffer = kstd::ring_buffer<int, 0>{kstd::from_range, source}; + + THEN("the buffer is empty") + { + REQUIRE(buffer.empty()); + } + } + + WHEN("constructing by from-range constructor with a range containing 1 element") + { + THEN("the constructor panics") + { + auto source = std::forward_list<int>{1}; + REQUIRE_THROWS_MATCHES( + (kstd::ring_buffer<int, 0>{kstd::from_range, source}), kstd::tests::os_panic, + Catch::Matchers::Message("[KSTD] Tried to construct a ring buffer with more elements than it can support.")); + } + } + GIVEN("A zero-capacity buffer of static operation trackers") { auto buffer = kstd::ring_buffer<kstd::tests::static_copy_move_tracker, 0>{}; |
