16 Commits

Author SHA1 Message Date
dc188b9d2f github-actions: fix CI not detecting failed tests 2025-05-12 20:19:20 +02:00
718ce9b26a Implement ci 2025-05-12 20:09:03 +02:00
6389462ce3 test/non-swappable-ring-buffer: add test to confirm non-swappable containers can be used 2025-05-12 16:02:30 +02:00
7859656ec2 ring-buffer: fix move constructor from container not actually moving 2025-05-12 16:02:10 +02:00
3dc81a4e8e bump version to 1.0.1 2025-05-12 15:37:45 +02:00
349a24bfdb test/constexpr-array-shim: add required import of <cstddef> for size_t 2025-05-12 15:35:58 +02:00
010362d1f6 test: add additional tests around swapping and iterators 2025-05-12 15:30:46 +02:00
ab1a477757 ring-buffer-iterator: remove unnecessary conditional noexcept
container_ptr is a built-in pointer type, and will always be noexcept to
swap.
2025-05-12 15:30:25 +02:00
4a019ad63c fixes ring-buffer-iterator swap() 2025-05-12 15:29:28 +02:00
746672a6c1 ring-buffer: add support for swapping ring buffers 2025-05-12 15:28:07 +02:00
1a8b098ed0 ring-buffer-iterator: fix postfix increment/decrement
this silently did the wrong thing on g++ (precedence of postfix ++/--)
and didn't compile on clang++. The correct solution is to
prefix-increment, which has no precedence issues and calls the correct
function. Postfix-increment with correct precedence would have resulted
in a stack overflow anyway.
2025-05-12 15:20:33 +02:00
3bad408674 CMake: detect/features.cmake: fix try_compile if CXX_STANDARD or CXX_EXTENSIONS unset 2025-05-12 14:17:15 +02:00
d9314150c2 constexpr_array: fix compile error in constexpr_array shim 2021-03-10 13:55:23 +01:00
5a6309dc28 ring_buffer_iterator: add it1 - it2 operator 2021-03-10 13:46:27 +01:00
795a4c3be7 ring_buffer_iterator: whitespace changes 2021-03-10 13:46:07 +01:00
5a021c46d0 add MIT license 2021-02-09 21:44:50 +01:00
11 changed files with 312 additions and 24 deletions

77
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,77 @@
name: CI
on:
push:
branches:
- master
pull_request:
branches:
- master
workflow_dispatch:
jobs:
build-and-test-cpp-unix:
strategy:
matrix:
# os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest]
cpp_standard: [c++11, c++14, c++17, c++20]
compiler: [gcc, clang]
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up cmake
uses: jwlawson/actions-setup-cmake@v2
- name: Setup build
run:
cmake -DRING_BUFFER_BUILD_TESTS=ON -S . -B build \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_CXX_STANDARD=${{ matrix.cpp_standard }} \
-DCMAKE_CXX_COMPILER=$([[ ${{ matrix.compiler }} == "gcc" ]] && echo "g++" || echo "clang++") \
-DCMAKE_C_COMPILER=$([[ ${{ matrix.compiler }} == "gcc" ]] && echo "gcc" || echo "clang") \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DCMAKE_CXX_FLAGS="-Wall -Wextra -Wpedantic" \
-DCMAKE_C_FLAGS="-Wall -Wextra -Wpedantic"
- name: Build
run: cmake --build build --config Debug
- name: Run tests
run: |
# search for all executables in build/test/test-* and run them
find build/test/ -type f -executable -name 'test-*' | (fail=0; while read test; do echo "Running test: $test"; "$test" || fail=$((fail+1)); done; exit $fail)
build-and-test-c99-unix:
strategy:
matrix:
# os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest]
compiler: [gcc, clang]
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up cmake
uses: jwlawson/actions-setup-cmake@v2
- name: Setup build
run:
cmake -DRING_BUFFER_BUILD_TESTS=ON -S . -B build \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_STANDARD=99 \
-DCMAKE_C_COMPILER=$([[ ${{ matrix.compiler }} == "gcc" ]] && echo "gcc" || echo "clang") \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DCMAKE_CXX_FLAGS="-Wall -Wextra -Wpedantic" \
-DCMAKE_C_FLAGS="-Wall -Wextra -Wpedantic"
- name: Build
run: cmake --build build --config Debug
- name: Run tests
run: |
# search for all executables in build/test/test-* and run them
find build/test/ -type f -executable -name 'test-*' | (fail=0; while read test; do echo "Running test: $test"; "$test" || fail=$((fail+1)); done; exit $fail)

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) [year] [fullname]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,8 +1,5 @@
try_compile(cxx-least-11 ${CMAKE_BINARY_DIR}/detect ${CMAKE_SOURCE_DIR}/detect/cxxstd.cpp
COMPILE_DEFINITIONS -DCXX_STANDARD=201103
CXX_STANDARD ${CMAKE_CXX_STANDARD}
CXX_STANDARD_REQUIRED ${CMAKE_CXX_STANDARD_REQUIRED}
CXX_EXTENSIONS ${CMAKE_CXX_EXTENSIONS}
)
if(NOT cxx-least-11)
@ -16,20 +13,13 @@ endif()
try_compile(cxx-least-14 ${CMAKE_BINARY_DIR}/detect ${CMAKE_SOURCE_DIR}/detect/cxxstd.cpp
COMPILE_DEFINITIONS -DCXX_STANDARD=201402
CXX_STANDARD ${CMAKE_CXX_STANDARD}
CXX_STANDARD_REQUIRED ${CMAKE_CXX_STANDARD_REQUIRED}
CXX_EXTENSIONS ${CMAKE_CXX_EXTENSIONS}
)
if(cxx-least-14)
set(RING_BUFFER_CONSTEXPR ON)
message(STATUS "Enabling RING_BUFFER_CONSTEXPR (C++14 and up)")
endif()
try_compile(constexpr-destructors-compile ${CMAKE_BINARY_DIR}/detect ${CMAKE_SOURCE_DIR}/detect/constexpr_destructors.cpp
CXX_STANDARD ${CMAKE_CXX_STANDARD}
CXX_STANDARD_REQUIRED ${CMAKE_CXX_STANDARD_REQUIRED}
CXX_EXTENSIONS ${CMAKE_CXX_EXTENSIONS}
)
try_compile(constexpr-destructors-compile ${CMAKE_BINARY_DIR}/detect ${CMAKE_SOURCE_DIR}/detect/constexpr_destructors.cpp)
if(constexpr-destructors-compile)
set(RING_BUFFER_CONSTEXPR_DESTRUCTORS ON)
message(STATUS "Enabling RING_BUFFER_CONSTEXPR_DESTRUCTORS (C++20 and up)")

View File

@ -41,6 +41,10 @@ public:
return {*container_ptr, front, index - n};
}
CONSTEXPR difference_type operator-(const ring_buffer_iterator& other) const NOEXCEPT {
return index - other.index;
}
CONSTEXPR reference operator*() COND_NOEXCEPT(noexcept(container_ptr->begin()) && noexcept(container_ptr->size())) {
return *(container_ptr->begin() + (front + index) % container_ptr->size());
}
@ -57,7 +61,7 @@ public:
}
CONSTEXPR ring_buffer_iterator operator++(int) NOEXCEPT {
ring_buffer_iterator old = *this;
*this++;
++*this;
return old;
}
CONSTEXPR ring_buffer_iterator& operator--() NOEXCEPT {
@ -66,7 +70,7 @@ public:
}
CONSTEXPR ring_buffer_iterator operator--(int) NOEXCEPT {
ring_buffer_iterator old = *this;
*this--;
--*this;
return old;
}
@ -90,12 +94,12 @@ public:
return index >= other.index;
}
CONSTEXPR friend ring_buffer_iterator operator+( difference_type n, const ring_buffer_iterator& it) NOEXCEPT {
CONSTEXPR friend ring_buffer_iterator operator+(difference_type n, const ring_buffer_iterator& it) NOEXCEPT {
return it + n;
}
CONSTEXPR friend void swap(ring_buffer_iterator& a, ring_buffer_iterator& b) COND_NOEXCEPT(noexcept(std::swap(a.container, b.container))) {
CONSTEXPR friend void swap(ring_buffer_iterator& a, ring_buffer_iterator& b) NOEXCEPT {
using std::swap;
swap(a.container, b.container);
swap(a.container_ptr, b.container_ptr);
swap(a.front, b.front);
swap(a.index, b.index);
}
@ -137,6 +141,10 @@ public:
return {*container_ptr, front, index - n};
}
CONSTEXPR difference_type operator-(const ring_buffer_const_iterator& other) const NOEXCEPT {
return index - other.index;
}
CONSTEXPR reference operator*() const COND_NOEXCEPT(noexcept(container_ptr->cbegin()) && noexcept(container_ptr->size())) {
return *(container_ptr->cbegin() + (front + index) % container_ptr->size());
}
@ -153,7 +161,7 @@ public:
}
CONSTEXPR ring_buffer_const_iterator operator++(int) NOEXCEPT {
ring_buffer_const_iterator old = *this;
*this++;
++*this;
return old;
}
CONSTEXPR ring_buffer_const_iterator& operator--() NOEXCEPT {
@ -162,7 +170,7 @@ public:
}
CONSTEXPR ring_buffer_const_iterator operator--(int) NOEXCEPT {
ring_buffer_const_iterator old = *this;
*this--;
--*this;
return old;
}
@ -186,12 +194,12 @@ public:
return index >= other.index;
}
CONSTEXPR friend ring_buffer_const_iterator operator+( difference_type n, const ring_buffer_const_iterator& it) NOEXCEPT {
CONSTEXPR friend ring_buffer_const_iterator operator+(difference_type n, const ring_buffer_const_iterator& it) NOEXCEPT {
return it + n;
}
CONSTEXPR friend void swap(ring_buffer_const_iterator& a, ring_buffer_const_iterator& b) COND_NOEXCEPT(noexcept(std::swap(a.container, b.container))) {
CONSTEXPR friend void swap(ring_buffer_const_iterator& a, ring_buffer_const_iterator& b) NOEXCEPT {
using std::swap;
swap(a.container, b.container);
swap(a.container_ptr, b.container_ptr);
swap(a.front, b.front);
swap(a.index, b.index);
}

View File

@ -6,6 +6,14 @@
#include <ring-buffer-config.h>
#include <ring-buffer-iterator.h>
namespace detail {
using std::swap;
template <typename T>
CONSTEXPR void adl_swap(T& a, T&b ) COND_NOEXCEPT(noexcept(swap(a, b))) {
swap(a, b);
}
}
template <typename Container>
class basic_ring_buffer {
public:
@ -22,7 +30,7 @@ public:
basic_ring_buffer() = default;
~basic_ring_buffer() = default;
CONSTEXPR basic_ring_buffer(const Container& other) COND_NOEXCEPT(noexcept(Container(other))) : container(other) {}
CONSTEXPR basic_ring_buffer(Container&& other) COND_NOEXCEPT(noexcept(Container(other))) : container(other) {}
CONSTEXPR basic_ring_buffer(Container&& other) COND_NOEXCEPT(noexcept(Container(std::move(other)))) : container(std::move(other)) {}
basic_ring_buffer(const basic_ring_buffer& other) = default;
basic_ring_buffer(basic_ring_buffer&& other) = default;
basic_ring_buffer& operator=(const basic_ring_buffer& other) = default;
@ -71,6 +79,11 @@ public:
CONSTEXPR const_iterator cend() const COND_NOEXCEPT(noexcept(container.size())) {
return {container, front_index, container.size()};
}
CONSTEXPR friend void swap(basic_ring_buffer& a, basic_ring_buffer& b) COND_NOEXCEPT(noexcept(detail::adl_swap(a.container, b.container))) {
using std::swap;
swap(a.container, b.container);
swap(a.front_index, b.front_index);
}
};
template <typename T, size_t N>

View File

@ -1,6 +1,6 @@
{
"name": "cxx-ring-buffer",
"version": "1.0.0",
"version": "1.0.1",
"build": {
"includeDir": "include",
"srcFilter":

View File

@ -1,11 +1,12 @@
#ifndef _CONSTEXPR_ARRAY_SHIM_H
#define _CONSTEXPR_ARRAY_SHIM_H
#include <cstddef>
#include <array>
#if defined( __cpp_lib_array_constexpr) && __cpp_lib_array_constexpr >= 201606L
template <typename T, size_t N>
using constexpr_array = array;
using constexpr_array = std::array<T, N>;
#else
template <typename T, size_t N>
struct constexpr_array {

24
test/iterator-ops.cpp Normal file
View File

@ -0,0 +1,24 @@
#include <vector>
#include <cassert>
#include <ring-buffer.h>
int main() {
ring_buffer<int, 4> buf;
buf.push_back(1);
buf.push_back(2);
buf.push_back(3);
buf.push_back(4);
buf.push_back(5);
buf.push_back(6);
{
auto it = buf.begin();
++it;
it++;
assert(*it == 5);
--it;
it--;
assert(*it == 3);
}
}

View File

@ -0,0 +1,77 @@
#include <vector>
#include <cassert>
#include <ring-buffer.h>
struct non_swappable_array {
using value_type = int;
using size_type = size_t;
int data[4];
// non-copyable
non_swappable_array() = default;
non_swappable_array(const non_swappable_array&) = delete;
non_swappable_array& operator=(const non_swappable_array&) = delete;
size_t size() const {
return 4;
}
int * begin() {
return std::begin(data);
}
int * end() {
return std::end(data);
}
const int * cbegin() const {
return std::cbegin(data);
}
const int * cend() const {
return std::cend(data);
}
int& operator[](size_t i) {
return data[i];
}
const int& operator[](size_t i) const {
return data[i];
}
// note: no swap implementation
};
int main() {
basic_ring_buffer<non_swappable_array> buf;
buf.push_back(1);
buf.push_back(2);
buf.push_back(3);
buf.push_back(4);
buf.push_back(5);
buf.push_back(6);
{
std::vector<int> expected{13, 14, 15, 16};
std::vector<int> actual;
for (int& i : buf) {
i += 10;
actual.push_back(i);
}
assert(actual == expected);
}
buf.push_back(7);
{
std::vector<int> expected{24, 25, 26, 17};
std::vector<int> actual;
for (int& i : buf) {
i += 10;
actual.push_back(i);
}
assert(actual == expected);
}
}

View File

@ -0,0 +1,33 @@
#include <vector>
#include <cassert>
#include <ring-buffer.h>
#include <utility>
int main() {
ring_buffer<int, 4> buf1;
buf1.push_back(1);
buf1.push_back(2);
buf1.push_back(3);
buf1.push_back(4);
buf1.push_back(5);
buf1.push_back(6);
ring_buffer<int, 4> buf2;
buf2.push_back(10);
buf2.push_back(20);
buf2.push_back(30);
buf2.push_back(40);
buf2.push_back(50);
buf2.push_back(60);
auto it1 = buf1.begin();
auto it2 = buf2.begin();
std::swap(it1, it2);
{
assert(*it1 == 30);
assert(*it2 == 3);
}
}

44
test/swap-ring-buffer.cpp Normal file
View File

@ -0,0 +1,44 @@
#include <vector>
#include <cassert>
#include <ring-buffer.h>
#include <utility>
int main() {
ring_buffer<int, 4> buf1;
buf1.push_back(1);
buf1.push_back(2);
buf1.push_back(3);
buf1.push_back(4);
buf1.push_back(5);
buf1.push_back(6);
ring_buffer<int, 4> buf2;
buf2.push_back(10);
buf2.push_back(20);
buf2.push_back(30);
buf2.push_back(40);
buf2.push_back(50);
buf2.push_back(60);
std::swap(buf1, buf2);
{
std::vector<int> expected{30, 40, 50, 60};
std::vector<int> actual;
for (int& i : buf1) {
actual.push_back(i);
}
assert(actual == expected);
}
{
std::vector<int> expected{3, 4, 5, 6};
std::vector<int> actual;
for (int& i : buf2) {
actual.push_back(i);
}
assert(actual == expected);
}
}