From ffd64f374bac21b02e685ec8381fd6f5c5f69136 Mon Sep 17 00:00:00 2001 From: Drew Rife Date: Sat, 22 Aug 2026 15:10:38 -0400 Subject: [PATCH] circular_buffer: add initialization and assignment overloads (#1556) * feat(circular_buffer): add initialization and assignment overloads * fix(circular_buffer): handle self-referential assignment * docs: document constructors and assign methods --------- Co-authored-by: John Wellbelove --- .../queues & stacks/circular-buffer.md | 36 +++++ include/etl/circular_buffer.h | 120 +++++++++++++++++ test/test_circular_buffer.cpp | 117 ++++++++++++++++ test/test_circular_buffer_external_buffer.cpp | 125 ++++++++++++++++++ 4 files changed, 398 insertions(+) diff --git a/docs/containers/queues & stacks/circular-buffer.md b/docs/containers/queues & stacks/circular-buffer.md index 687ac4b9..3c3faf2a 100644 --- a/docs/containers/queues & stacks/circular-buffer.md +++ b/docs/containers/queues & stacks/circular-buffer.md @@ -69,11 +69,19 @@ const_reverse_iterator reverse_iterator ```cpp circular_buffer(); +circular_buffer(size_t initial_size); + +circular_buffer(size_t initial_size, const_reference value); + circular_buffer(const etl::circular_buffer& other); circular_buffer(etl::circular_buffer&& other); ``` +The `initial_size` constructor fills the buffer with default-constructed values. +The `initial_size` and `value` constructor fills the buffer with copies of `value`. +If `initial_size` exceeds `SIZE`, the buffer is filled to its capacity. + --- ```cpp @@ -81,6 +89,10 @@ circular_buffer_ext(void* buffer, size_t max_size) circular_buffer_ext(size_t max_size) 20.32.1 +circular_buffer_ext(size_t initial_size, void* buffer, size_t max_size) + +circular_buffer_ext(size_t initial_size, const_reference value, void* buffer, size_t max_size) + template circular_buffer_ext(TIterator first, const TIterator& last, void* buffer, size_t max_size) @@ -89,6 +101,10 @@ circular_buffer_ext(std::initializer_list init, void* buffer, size_t max_size circular_buffer_ext(const circular_buffer_ext& other, void* buffer, size_t max_size) ``` +The `initial_size` constructor fills the buffer with default-constructed values. +The `initial_size` and `value` constructor fills the buffer with copies of `value`. +If `initial_size` exceeds `max_size`, the buffer is filled to its capacity. The external storage must contain at least `max_size + 1` elements. + ## Status ```cpp @@ -225,6 +241,26 @@ Returns the remaining available capacity in the `circular_buffer`. ## Modifiers +```cpp +void assign(iterator first, const iterator& last) +void assign(const_iterator first, const const_iterator& last) + +template +void assign(TIterator first, const TIterator& last) +``` +**Description** +Replaces the contents of the circular buffer with the elements in the range `[first, last)`. If the range exceeds the capacity, only the last `capacity()` elements are retained. The range may refer to the circular buffer being assigned. + +--- + +```cpp +void assign(size_type n, const_reference value) +``` +**Description** +Replaces the contents of the circular buffer with `n` copies of `value`. If `n` exceeds the capacity, the buffer is filled to its capacity. `value` may refer to an element in the circular buffer being assigned. + +--- + ```cpp void set_buffer(void* buffer) ``` diff --git a/include/etl/circular_buffer.h b/include/etl/circular_buffer.h index 4c8290c4..b0586cfb 100644 --- a/include/etl/circular_buffer.h +++ b/include/etl/circular_buffer.h @@ -900,6 +900,53 @@ namespace etl return pbuffer[(out + index) % buffer_size]; } + //************************************************************************* + /// Assigns an iterator range to the circular buffer. + //************************************************************************* + void assign(iterator first, const iterator& last) + { + assign_range(first, last); + } + + //************************************************************************* + /// Assigns a const iterator range to the circular buffer. + //************************************************************************* + void assign(const_iterator first, const const_iterator& last) + { + assign_range(first, last); + } + + //************************************************************************* + /// Assigns a range to the circular buffer. + //************************************************************************* + template + typename etl::enable_if::value, void>::type assign(TIterator first, const TIterator& last) + { + clear(); + push(first, last); + } + + //************************************************************************* + /// Assigns 'n' copies of a value to the circular buffer. + //************************************************************************* + void assign(size_type n, const_reference value) + { + if (n == 0U) + { + clear(); + return; + } + + const T value_copy(value); + + clear(); + + while (n-- != 0U) + { + push(value_copy); + } + } + //************************************************************************* /// push. /// Adds an item to the buffer. @@ -1036,6 +1083,43 @@ namespace etl protected: + //************************************************************************* + /// Assigns a circular buffer iterator range. + //************************************************************************* + template + void assign_range(TIterator first, const TIterator& last) + { + const bool is_self_range = (&first.container() == this) && (&last.container() == this); + + if (!is_self_range) + { + clear(); + push(first, last); + } + else + { + const size_type first_index = static_cast(first.get_index()); + const size_type last_index = static_cast(last.get_index()); + + while (out != first_index) + { + pop(); + } + + while (in != last_index) + { + if (in == 0U) + { + in = buffer_size; + } + + --in; + pbuffer[in].~T(); + ETL_DECREMENT_DEBUG_COUNT; + } + } + } + //************************************************************************* /// Protected constructor. //************************************************************************* @@ -1127,6 +1211,24 @@ namespace etl { } + //************************************************************************* + /// Constructor, with size. + //************************************************************************* + explicit circular_buffer(size_t initial_size) + : icircular_buffer(reinterpret_cast(buffer.raw), MAX_SIZE) + { + this->assign(initial_size, T()); + } + + //************************************************************************* + /// Constructor, from initial size and value. + //************************************************************************* + circular_buffer(size_t initial_size, typename etl::icircular_buffer::const_reference value) + : icircular_buffer(reinterpret_cast(buffer.raw), MAX_SIZE) + { + this->assign(initial_size, value); + } + //************************************************************************* /// Constructor. /// Constructs a buffer from an iterator range. @@ -1274,6 +1376,24 @@ namespace etl { } + //************************************************************************* + /// Constructor, with size. + //************************************************************************* + explicit circular_buffer_ext(size_t initial_size, void* buffer, size_t max_size) + : icircular_buffer(reinterpret_cast(buffer), max_size) + { + this->assign(initial_size, T()); + } + + //************************************************************************* + /// Constructor, from initial size and value. + //************************************************************************* + circular_buffer_ext(size_t initial_size, typename etl::icircular_buffer::const_reference value, void* buffer, size_t max_size) + : icircular_buffer(reinterpret_cast(buffer), max_size) + { + this->assign(initial_size, value); + } + //************************************************************************* /// Constructor. /// Constructs a buffer from an iterator range. diff --git a/test/test_circular_buffer.cpp b/test/test_circular_buffer.cpp index 2bbf9feb..cdbd162d 100644 --- a/test/test_circular_buffer.cpp +++ b/test/test_circular_buffer.cpp @@ -67,6 +67,123 @@ namespace CHECK(data.crbegin() == data.crend()); } + //************************************************************************* + TEST(test_constructor_with_size) + { + etl::circular_buffer data(5U); + + CHECK_EQUAL(5U, data.size()); + CHECK(std::all_of(data.begin(), data.end(), [](int value) { return value == 0; })); + } + + //************************************************************************* + TEST(test_constructor_with_excess_size) + { + etl::circular_buffer data(SIZE + 3U); + + CHECK(data.full()); + CHECK_EQUAL(SIZE, data.size()); + CHECK(std::all_of(data.begin(), data.end(), [](int value) { return value == 0; })); + } + + //************************************************************************* + TEST(test_constructor_with_size_and_value) + { + const Ndc value("value"); + Data data(5U, value); + + CHECK_EQUAL(5U, data.size()); + CHECK(std::all_of(data.begin(), data.end(), [&value](const Ndc& item) { return item == value; })); + } + + //************************************************************************* + TEST(test_constructor_with_excess_size_and_value) + { + const Ndc value("value"); + Data data(SIZE + 3U, value); + + CHECK(data.full()); + CHECK_EQUAL(SIZE, data.size()); + CHECK(std::all_of(data.begin(), data.end(), [&value](const Ndc& item) { return item == value; })); + } + + //************************************************************************* + TEST(test_assign_size_and_value) + { + Data data; + data.push(Ndc("old")); + + const Ndc value("value"); + data.assign(5U, value); + + CHECK_EQUAL(5U, data.size()); + CHECK(std::all_of(data.begin(), data.end(), [&value](const Ndc& item) { return item == value; })); + + data.assign(0U, value); + CHECK(data.empty()); + + data.assign(SIZE + 3U, value); + CHECK(data.full()); + CHECK_EQUAL(SIZE, data.size()); + CHECK(std::all_of(data.begin(), data.end(), [&value](const Ndc& item) { return item == value; })); + } + + //************************************************************************* + TEST(test_assign_range) + { + Compare input{Ndc("0"), Ndc("1"), Ndc("2"), Ndc("3"), Ndc("4"), Ndc("5"), Ndc("6"), + Ndc("7"), Ndc("8"), Ndc("9"), Ndc("10"), Ndc("11"), Ndc("12")}; + Compare expected(input.end() - SIZE, input.end()); + Data data; + data.push(Ndc("old")); + + data.assign(input.begin(), input.end()); + + CHECK(data.full()); + CHECK_EQUAL(expected.size(), data.size()); + CHECK(std::equal(expected.begin(), expected.end(), data.begin())); + + data.assign(input.begin(), input.begin()); + CHECK(data.empty()); + } + + //************************************************************************* + TEST(test_assign_self_range) + { + Compare input{Ndc("0"), Ndc("1"), Ndc("2"), Ndc("3"), Ndc("4"), Ndc("5"), Ndc("6"), + Ndc("7"), Ndc("8"), Ndc("9"), Ndc("10"), Ndc("11"), Ndc("12")}; + Compare expected(input.end() - SIZE, input.end()); + Data data(input.begin(), input.end()); + + data.assign(data.begin(), data.end()); + + CHECK(data.full()); + CHECK_EQUAL(expected.size(), data.size()); + CHECK(std::equal(expected.begin(), expected.end(), data.begin())); + + Data::const_iterator first = data.cbegin() + 2; + Data::const_iterator last = data.cend() - 2; + Compare partial_expected(expected.begin() + 2, expected.end() - 2); + + data.assign(first, last); + + CHECK_EQUAL(partial_expected.size(), data.size()); + CHECK(std::equal(partial_expected.begin(), partial_expected.end(), data.begin())); + } + + //************************************************************************* + TEST(test_assign_aliased_value) + { + Compare input{Ndc("0"), Ndc("1"), Ndc("2"), Ndc("3"), Ndc("4")}; + Data data(input.begin(), input.end()); + + const Ndc expected(data.front()); + data.assign(7U, data.front()); + + CHECK_EQUAL(7U, data.size()); + CHECK(std::all_of(data.begin(), data.end(), [&expected](const Ndc& item) { return item == expected; })); + } + #if ETL_HAS_INITIALIZER_LIST //************************************************************************* TEST(test_constructor_from_literal) diff --git a/test/test_circular_buffer_external_buffer.cpp b/test/test_circular_buffer_external_buffer.cpp index cd0c40cd..425d92b0 100644 --- a/test/test_circular_buffer_external_buffer.cpp +++ b/test/test_circular_buffer_external_buffer.cpp @@ -78,6 +78,131 @@ namespace CHECK(data.crbegin() == data.crend()); } + //************************************************************************* + TEST(test_constructor_with_size) + { + etl::uninitialized_buffer_of buffer1; + etl::circular_buffer_ext data(5U, buffer1.raw, SIZE); + + CHECK_EQUAL(5U, data.size()); + CHECK(std::all_of(data.begin(), data.end(), [](int value) { return value == 0; })); + } + + //************************************************************************* + TEST(test_constructor_with_excess_size) + { + etl::uninitialized_buffer_of buffer1; + etl::circular_buffer_ext data(SIZE + 3U, buffer1.raw, SIZE); + + CHECK(data.full()); + CHECK_EQUAL(SIZE, data.size()); + CHECK(std::all_of(data.begin(), data.end(), [](int value) { return value == 0; })); + } + + //************************************************************************* + TEST(test_constructor_with_size_and_value) + { + Buffer_t buffer1; + const Ndc value("value"); + Data data(5U, value, buffer1.raw, SIZE); + + CHECK_EQUAL(5U, data.size()); + CHECK(std::all_of(data.begin(), data.end(), [&value](const Ndc& item) { return item == value; })); + } + + //************************************************************************* + TEST(test_constructor_with_excess_size_and_value) + { + Buffer_t buffer1; + const Ndc value("value"); + Data data(SIZE + 3U, value, buffer1.raw, SIZE); + + CHECK(data.full()); + CHECK_EQUAL(SIZE, data.size()); + CHECK(std::all_of(data.begin(), data.end(), [&value](const Ndc& item) { return item == value; })); + } + + //************************************************************************* + TEST(test_assign_size_and_value) + { + Buffer_t buffer1; + Data data(buffer1.raw, SIZE); + data.push(Ndc("old")); + + const Ndc value("value"); + data.assign(5U, value); + + CHECK_EQUAL(5U, data.size()); + CHECK(std::all_of(data.begin(), data.end(), [&value](const Ndc& item) { return item == value; })); + + data.assign(0U, value); + CHECK(data.empty()); + + data.assign(SIZE + 3U, value); + CHECK(data.full()); + CHECK_EQUAL(SIZE, data.size()); + CHECK(std::all_of(data.begin(), data.end(), [&value](const Ndc& item) { return item == value; })); + } + + //************************************************************************* + TEST(test_assign_range) + { + Buffer_t buffer1; + Compare input{Ndc("0"), Ndc("1"), Ndc("2"), Ndc("3"), Ndc("4"), Ndc("5"), Ndc("6"), + Ndc("7"), Ndc("8"), Ndc("9"), Ndc("10"), Ndc("11"), Ndc("12")}; + Compare expected(input.end() - SIZE, input.end()); + Data data(buffer1.raw, SIZE); + data.push(Ndc("old")); + + data.assign(input.begin(), input.end()); + + CHECK(data.full()); + CHECK_EQUAL(expected.size(), data.size()); + CHECK(std::equal(expected.begin(), expected.end(), data.begin())); + + data.assign(input.begin(), input.begin()); + CHECK(data.empty()); + } + + //************************************************************************* + TEST(test_assign_self_range) + { + Buffer_t buffer1; + Compare input{Ndc("0"), Ndc("1"), Ndc("2"), Ndc("3"), Ndc("4"), Ndc("5"), Ndc("6"), + Ndc("7"), Ndc("8"), Ndc("9"), Ndc("10"), Ndc("11"), Ndc("12")}; + Compare expected(input.end() - SIZE, input.end()); + Data data(input.begin(), input.end(), buffer1.raw, SIZE); + + data.assign(data.begin(), data.end()); + + CHECK(data.full()); + CHECK_EQUAL(expected.size(), data.size()); + CHECK(std::equal(expected.begin(), expected.end(), data.begin())); + + Data::const_iterator first = data.cbegin() + 2; + Data::const_iterator last = data.cend() - 2; + Compare partial_expected(expected.begin() + 2, expected.end() - 2); + + data.assign(first, last); + + CHECK_EQUAL(partial_expected.size(), data.size()); + CHECK(std::equal(partial_expected.begin(), partial_expected.end(), data.begin())); + } + + //************************************************************************* + TEST(test_assign_aliased_value) + { + Buffer_t buffer1; + Compare input{Ndc("0"), Ndc("1"), Ndc("2"), Ndc("3"), Ndc("4")}; + Data data(input.begin(), input.end(), buffer1.raw, SIZE); + + const Ndc expected(data.front()); + data.assign(7U, data.front()); + + CHECK_EQUAL(7U, data.size()); + CHECK(std::all_of(data.begin(), data.end(), [&expected](const Ndc& item) { return item == expected; })); + } + //************************************************************************* TEST(test_set_buffer_after_default_constructor) {