Program Listing for File buffer.hpp

Return to documentation for file (include/util/buffer.hpp)

// SPDX-FileCopyrightText: 2021 Christian Göhring <mostsig@gmail.com>
// SPDX-License-Identifier: MIT

#ifndef THAT_THIS_UTIL_BUFFER_HEADER_IS_ALREADY_INCLUDED
#define THAT_THIS_UTIL_BUFFER_HEADER_IS_ALREADY_INCLUDED

#include <array>
#include <cstddef>
#include <memory>
#include <vector>

namespace util {

namespace detail {
template <bool IsConst, class T, std::size_t N, class Allocator>
class buffer_iterator;
}  // namespace detail

template <class T, std::size_t N = 16, class Allocator = std::allocator<T>>
class buffer {
public:
    using allocator_type = Allocator;
    using difference_type = std::ptrdiff_t;
    using size_type = std::size_t;
    using value_type = T;

    using iterator = detail::buffer_iterator<false, T, N, Allocator>;
    using const_iterator = detail::buffer_iterator<true, T, N, Allocator>;
    using reference = value_type&;
    using const_reference = const value_type&;
    using pointer = value_type*;
    using const_pointer = const value_type*;

    buffer() = default;
    ~buffer() = default;
    buffer(const buffer&) = default;
    buffer(buffer&&) noexcept = default;
    auto operator=(const buffer&) -> buffer& = default;
    auto operator=(buffer&&) noexcept -> buffer& = default;

    buffer(const std::initializer_list<T>& list);

    auto at(size_type pos) -> reference;
    auto at(size_type pos) const -> const_reference;
    auto operator[](size_type pos) -> reference;
    auto operator[](size_type pos) const -> const_reference;
    auto front() -> reference;
    auto front() const -> const_reference;
    auto back() -> reference;
    auto back() const -> const_reference;
    auto stack_data() noexcept -> pointer;
    auto stack_data() const noexcept -> const_pointer;
    auto heap_data() noexcept -> pointer;
    auto heap_data() const noexcept -> const_pointer;

    auto begin() noexcept -> iterator;
    auto begin() const noexcept -> const_iterator;
    auto cbegin() const noexcept -> const_iterator;
    auto end() noexcept -> iterator;
    auto end() const noexcept -> const_iterator;
    auto cend() const noexcept -> const_iterator;
    auto rbegin() noexcept -> iterator;
    auto rbegin() const noexcept -> const_iterator;
    auto crbegin() const noexcept -> const_iterator;
    auto rend() noexcept -> iterator;
    auto rend() const noexcept -> const_iterator;
    auto crend() const noexcept -> const_iterator;

    auto empty() const noexcept -> bool;
    auto size() const noexcept -> size_type;
    auto max_size() const noexcept -> size_type;

    void resize(size_type n, const value_type& val);
    auto capacity() const noexcept -> size_type;

private:
    std::size_t stack_pos = 0U;      // position of the next available slot on the stack
    std::array<T, N> stack = {T()};  // fixed-size container of stack elements
    std::vector<T, Allocator> heap;  // dynamically growing container of heap elements
};

namespace detail {

template <bool IsConst, class T, std::size_t N, class Allocator>
class buffer_iterator {
public:
    using iterator_category = std::forward_iterator_tag;
    using difference_type = std::ptrdiff_t;
    using value_type = T;
    using reference = typename std::conditional<IsConst, const value_type&, value_type&>::type;
    using pointer = typename std::conditional<IsConst, const value_type*, value_type*>::type;
    using buffer_type = util::buffer<T, N, Allocator>;
    using buffer_pointer =
        typename std::conditional<IsConst, const buffer_type*, buffer_type*>::type;
    using size_type = typename buffer_type::size_type;

    explicit buffer_iterator(size_type pos, const buffer_pointer& buffer_ptr) noexcept
        : pos(pos), buffer_ptr(buffer_ptr) {}

    auto operator*() const -> reference { return buffer_ptr->operator[](pos); }
    auto operator->() -> pointer { return buffer_ptr->operator[](pos); }

    auto operator++() -> buffer_iterator& {
        pos++;
        return *this;
    }

    auto operator++(int) -> buffer_iterator {
        buffer_iterator tmp = *this;
        ++(*this);
        return tmp;
    }

    friend auto operator==(const buffer_iterator& lhs, const buffer_iterator& rhs) -> bool {
        return lhs.pos == rhs.pos && lhs.buffer_ptr == rhs.buffer_ptr;
    };
    friend auto operator!=(const buffer_iterator& lhs, const buffer_iterator& rhs) -> bool {
        return lhs.pos != rhs.pos || lhs.buffer_ptr != rhs.buffer_ptr;
    };

private:
    size_type pos = 0;
    buffer_pointer buffer_ptr;
};

// assert if a buffer_iterator is trivially copy constructible
static_assert(std::is_trivially_copy_constructible<buffer<int>::iterator>::value, "");

// assert if a buffer_const_iterator is trivially copy constructible
static_assert(std::is_trivially_copy_constructible<buffer<int>::const_iterator>::value, "");

}  // namespace detail

template <class T, std::size_t N, class Allocator>
buffer<T, N, Allocator>::buffer(const std::initializer_list<T>& list) {
    if (list.size() <= N) {
        std::copy(list.begin(), list.end(), stack.begin());
        stack_pos = list.size();
    } else {
        std::copy(list.begin(), list.begin() + N, stack.begin());
        heap.reserve(list.size() - N);
        std::copy(list.begin() + N, list.end(), std::back_inserter(heap));
        stack_pos = N;
    }
}

template <class T, std::size_t N, class Allocator>
auto buffer<T, N, Allocator>::at(size_type pos) -> reference {
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) @see Effective C++ by Scott Meyers
    return const_cast<reference>(const_cast<const buffer<T, N, Allocator>*>(this)->at(pos));
}

template <class T, std::size_t N, class Allocator>
auto buffer<T, N, Allocator>::at(size_type pos) const -> const_reference {
    if (pos >= size()) {
        throw std::out_of_range{"pos is out of range"};
    }

    if (pos < N) {
        return stack.at(pos);
    }
    return heap.at(pos - N);
}

template <class T, std::size_t N, class Allocator>
auto buffer<T, N, Allocator>::operator[](size_type pos) -> reference {
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) @see Effective C++ by Scott Meyers
    return const_cast<reference>(const_cast<const buffer<T, N, Allocator>*>(this)->operator[](pos));
}

template <class T, std::size_t N, class Allocator>
auto buffer<T, N, Allocator>::operator[](size_type pos) const -> const_reference {
    if (pos < N) {
        return stack[pos];
    }
    return heap[pos - N];
}

template <class T, std::size_t N, class Allocator>
auto buffer<T, N, Allocator>::front() -> reference {
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) @see Effective C++ by Scott Meyers
    return const_cast<reference>(const_cast<const buffer<T, N, Allocator>*>(this)->front());
}

template <class T, std::size_t N, class Allocator>
auto buffer<T, N, Allocator>::front() const -> const_reference {
    return stack[0];
}

template <class T, std::size_t N, class Allocator>
auto buffer<T, N, Allocator>::back() -> reference {
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) @see Effective C++ by Scott Meyers
    return const_cast<reference>(const_cast<const buffer<T, N, Allocator>*>(this)->back());
}

template <class T, std::size_t N, class Allocator>
auto buffer<T, N, Allocator>::back() const -> const_reference {
    if (stack_pos == N) {
        return heap.back();
    }

    return stack[stack_pos - 1];
}

template <class T, std::size_t N, class Allocator>
auto buffer<T, N, Allocator>::stack_data() noexcept -> pointer {
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) @see Effective C++ by Scott Meyers
    return const_cast<pointer>(const_cast<const buffer<T, N, Allocator>*>(this)->stack_data());
}

template <class T, std::size_t N, class Allocator>
auto buffer<T, N, Allocator>::stack_data() const noexcept -> const_pointer {
    return stack.data();
}

template <class T, std::size_t N, class Allocator>
auto buffer<T, N, Allocator>::heap_data() noexcept -> pointer {
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) @see Effective C++ by Scott Meyers
    return const_cast<pointer>(const_cast<const buffer<T, N, Allocator>*>(this)->heap_data());
}

template <class T, std::size_t N, class Allocator>
auto buffer<T, N, Allocator>::heap_data() const noexcept -> const_pointer {
    return heap.data();
}

template <class T, std::size_t N, class Allocator>
auto buffer<T, N, Allocator>::begin() noexcept -> iterator {
    return iterator(0, this);
}

template <class T, std::size_t N, class Allocator>
auto buffer<T, N, Allocator>::begin() const noexcept -> const_iterator {
    return const_iterator(0, this);
}

template <class T, std::size_t N, class Allocator>
auto buffer<T, N, Allocator>::cbegin() const noexcept -> const_iterator {
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) calls similar const-method
    return const_cast<const buffer*>(this)->begin();
}

template <class T, std::size_t N, class Allocator>
auto buffer<T, N, Allocator>::end() noexcept -> iterator {
    return iterator(size(), this);
}

template <class T, std::size_t N, class Allocator>
auto buffer<T, N, Allocator>::end() const noexcept -> const_iterator {
    return const_iterator(size(), this);
}

template <class T, std::size_t N, class Allocator>
auto buffer<T, N, Allocator>::cend() const noexcept -> const_iterator {
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) calls similar const-method
    return const_cast<const buffer*>(this)->end();
}

template <class T, std::size_t N, class Allocator>
auto buffer<T, N, Allocator>::empty() const noexcept -> bool {
    return stack_pos == 0;
}

template <class T, std::size_t N, class Allocator>
auto buffer<T, N, Allocator>::size() const noexcept -> size_type {
    if (stack_pos < N) {
        return stack_pos;
    }
    return N + heap.size();
}

template <class T, std::size_t N, class Allocator>
auto buffer<T, N, Allocator>::max_size() const noexcept -> size_type {
    return stack.max_size() + heap.max_size();
}

}  // namespace util

#endif  // THAT_THIS_UTIL_BUFFER_HEADER_IS_ALREADY_INCLUDED