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 <jwellbelove@users.noreply.github.com>
This commit is contained in:
Drew Rife 2026-08-22 15:10:38 -04:00 committed by GitHub
parent 1a72c88d47
commit ffd64f374b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 398 additions and 0 deletions

View File

@ -69,11 +69,19 @@ const_reverse_iterator reverse_iterator<const_iterator>
```cpp
circular_buffer<typename T, size_t SIZE>();
circular_buffer<typename T, size_t SIZE>(size_t initial_size);
circular_buffer<typename T, size_t SIZE>(size_t initial_size, const_reference value);
circular_buffer<typename T, size_t SIZE>(const etl::circular_buffer<typename T, size_t SIZE>& other);
circular_buffer<typename T, size_t SIZE>(etl::circular_buffer<typename T, size_t SIZE>&& 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 <typename TIterator>
circular_buffer_ext(TIterator first, const TIterator& last, void* buffer, size_t max_size)
@ -89,6 +101,10 @@ circular_buffer_ext(std::initializer_list<T> 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 <typename TIterator>
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)
```

View File

@ -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 TIterator>
typename etl::enable_if<!etl::is_integral<TIterator>::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 <typename TIterator>
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<size_type>(first.get_index());
const size_type last_index = static_cast<size_type>(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<T>(reinterpret_cast<T*>(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<T>::const_reference value)
: icircular_buffer<T>(reinterpret_cast<T*>(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<T>(reinterpret_cast<T*>(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<T>::const_reference value, void* buffer, size_t max_size)
: icircular_buffer<T>(reinterpret_cast<T*>(buffer), max_size)
{
this->assign(initial_size, value);
}
//*************************************************************************
/// Constructor.
/// Constructs a buffer from an iterator range.

View File

@ -67,6 +67,123 @@ namespace
CHECK(data.crbegin() == data.crend());
}
//*************************************************************************
TEST(test_constructor_with_size)
{
etl::circular_buffer<int, SIZE> 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<int, SIZE> 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)

View File

@ -78,6 +78,131 @@ namespace
CHECK(data.crbegin() == data.crend());
}
//*************************************************************************
TEST(test_constructor_with_size)
{
etl::uninitialized_buffer_of<int, SIZE + 1> buffer1;
etl::circular_buffer_ext<int> 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<int, SIZE + 1> buffer1;
etl::circular_buffer_ext<int> 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)
{