Skip to main content
deleted 7 characters in body
Source Link
Deduplicator
  • 19.9k
  • 1
  • 32
  • 65
#define BOOST_TEST_MODULE DequeTest
#include <boost/test/included/unit_test.hpp>
#include <deque>

#include "Deque.h"

BOOST_AUTO_TEST_CASE(TestConstructors) {
    Deque<int> dq1;
    BOOST_CHECK(dq1.size() == 0);

    Deque<int> dq2{1, 2, 3, 4, 5};
    BOOST_CHECK(dq2.size() == 5);
    BOOST_CHECK(dq2[0] == 1 && dq2[4] == 5);

    Deque<int> dq3(dq2);
    BOOST_CHECK(dq3.size() == dq2.size());
    BOOST_CHECK(dq3[0] == dq2[0] && dq3[4] == dq2[4]);

    Deque<int> dq4(std::move(dq2));
    BOOST_CHECK(dq4.size() == 5);
    BOOST_CHECK(dq2.size() == 0);
}

BOOST_AUTO_TEST_CASE(TestPushPop) {
    Deque<int> dq;
    dq.push_back(1);
    dq.push_front(2);
    BOOST_CHECK(dq.size() == 2);
    BOOST_CHECK(dq[0] == 2 && dq[1] == 1);

    dq.pop_back();
    BOOST_CHECK(dq.size() == 1 && dq[0] == 2);

    dq.pop_front();
    BOOST_CHECK(dq.empty());

    for (int i = 1; i <= 100; ++i) {
        dq.push_front(i);
        BOOST_CHECK(dq.size() == i);
    }

    for (int i = 99; i >= 0; --i) {
        dq.pop_back();
        BOOST_CHECK(dq.size() == i);
    }
}

BOOST_AUTO_TEST_CASE(TestIterators) {
    Deque<int> dq;

    for (int i = 0; i < 100; ++i) {
        dq.push_back(i);
    }

    Deque<int> dq2;
    for (auto it = dq.begin(); it != dq.end(); ++it) {
        dq2.push_back(*it);
    }

    BOOST_CHECK(dq == dq2);

    Deque<int> rev;
    for (auto it = dq.rbegin(); it != dq.rend(); ++it) {
        rev.push_back(*it);
    }

    for (int i = 0, j = rev.size() - 1; i < j; i++, j--) {
        swap(rev[i], rev[j]);
    }

    BOOST_CHECK(dq == rev);
}

BOOST_AUTO_TEST_CASE(TestBoundaryInsertionAndDeletion) {
    Deque<int> dq;
    dq.insert(dq.begin(), 1);
    BOOST_CHECK(dq.size() == 1 && dq[0] == 1);

    dq.insert(dq.end(), 2);
    BOOST_CHECK(dq.size() == 2 && dq[1] == 2);

    dq.erase(dq.begin());
    BOOST_CHECK(dq.size() == 1 && dq[0] == 2);

    dq.erase(dq.begin());
    BOOST_CHECK(dq.empty());
}

BOOST_AUTO_TEST_CASE(TestLargeDataHandling) {
    Deque<int> dq;
    const std::size_t large_size = 100000;
    for (std::size_t i = 0; i < large_size; ++i) {
        dq.push_back(i);
    }
    BOOST_CHECK(dq.size() == large_size);
    BOOST_CHECK(dq[large_size - 1] == large_size - 1);
    for (std::size_t i = 0; i < large_size; ++i) {
        dq.pop_front();
    }
    BOOST_CHECK(dq.empty());
}

BOOST_AUTO_TEST_CASE(TestConsistencyAfterMultipleOperations) {
    Deque<int> dq;
    for (int i = 0; i < 1000; ++i) {
        dq.push_back(i);
        dq.push_front(-i);
    }
    BOOST_CHECK(dq.size() == 2000);
    for (int i = 0; i < 1000; ++i) {
        BOOST_CHECK(dq[i] == -999 + i);
        BOOST_CHECK(dq[1999 - i] == 999 - i);
    }
    for (int i = 0; i < 1000; ++i) {
        dq.pop_back();
        dq.pop_front();
    }
    BOOST_CHECK(dq.empty());
}

BOOST_AUTO_TEST_CASE(TestEmplacement) {
    Deque<std::pair<int, std::string>> dq;

    dq.emplace_back(1, "one");
    BOOST_CHECK(dq.back().first == 1 && dq.back().second == "one");

    dq.emplace_front(0, "zero");
    BOOST_CHECK(dq.front().first == 0 && dq.front().second == "zero");

    BOOST_CHECK(dq.size() == 2);
    BOOST_CHECK(dq[0].first == 0 && dq[1].first == 1);
}

BOOST_AUTO_TEST_CASE(TestNonIntTypes) {
    Deque<std::string> dq;
    dq.push_back("Hello");
    dq.push_back("World");

    BOOST_CHECK(dq.size() == 2);
    BOOST_CHECK(dq[0] == "Hello" && dq[1] == "World");
}

BOOST_AUTO_TEST_CASE(TestStringLargeDataHandling) {
    Deque<std::string> dq;
    const std::size_t large_size = 10000;
    for (std::size_t i = 0; i < large_size; ++i) {
        dq.push_back("String " + std::to_string(i));
    }

    BOOST_CHECK(dq.size() == large_size);
    BOOST_CHECK(dq[large_size - 1] ==
                "String " + std::to_string(large_size - 1));

    for (std::size_t i = 0; i < large_size; ++i) {
        dq.pop_front();
    }

    BOOST_CHECK(dq.empty());
}
 
```
#define BOOST_TEST_MODULE DequeTest
#include <boost/test/included/unit_test.hpp>
#include <deque>

#include "Deque.h"

BOOST_AUTO_TEST_CASE(TestConstructors) {
    Deque<int> dq1;
    BOOST_CHECK(dq1.size() == 0);

    Deque<int> dq2{1, 2, 3, 4, 5};
    BOOST_CHECK(dq2.size() == 5);
    BOOST_CHECK(dq2[0] == 1 && dq2[4] == 5);

    Deque<int> dq3(dq2);
    BOOST_CHECK(dq3.size() == dq2.size());
    BOOST_CHECK(dq3[0] == dq2[0] && dq3[4] == dq2[4]);

    Deque<int> dq4(std::move(dq2));
    BOOST_CHECK(dq4.size() == 5);
    BOOST_CHECK(dq2.size() == 0);
}

BOOST_AUTO_TEST_CASE(TestPushPop) {
    Deque<int> dq;
    dq.push_back(1);
    dq.push_front(2);
    BOOST_CHECK(dq.size() == 2);
    BOOST_CHECK(dq[0] == 2 && dq[1] == 1);

    dq.pop_back();
    BOOST_CHECK(dq.size() == 1 && dq[0] == 2);

    dq.pop_front();
    BOOST_CHECK(dq.empty());

    for (int i = 1; i <= 100; ++i) {
        dq.push_front(i);
        BOOST_CHECK(dq.size() == i);
    }

    for (int i = 99; i >= 0; --i) {
        dq.pop_back();
        BOOST_CHECK(dq.size() == i);
    }
}

BOOST_AUTO_TEST_CASE(TestIterators) {
    Deque<int> dq;

    for (int i = 0; i < 100; ++i) {
        dq.push_back(i);
    }

    Deque<int> dq2;
    for (auto it = dq.begin(); it != dq.end(); ++it) {
        dq2.push_back(*it);
    }

    BOOST_CHECK(dq == dq2);

    Deque<int> rev;
    for (auto it = dq.rbegin(); it != dq.rend(); ++it) {
        rev.push_back(*it);
    }

    for (int i = 0, j = rev.size() - 1; i < j; i++, j--) {
        swap(rev[i], rev[j]);
    }

    BOOST_CHECK(dq == rev);
}

BOOST_AUTO_TEST_CASE(TestBoundaryInsertionAndDeletion) {
    Deque<int> dq;
    dq.insert(dq.begin(), 1);
    BOOST_CHECK(dq.size() == 1 && dq[0] == 1);

    dq.insert(dq.end(), 2);
    BOOST_CHECK(dq.size() == 2 && dq[1] == 2);

    dq.erase(dq.begin());
    BOOST_CHECK(dq.size() == 1 && dq[0] == 2);

    dq.erase(dq.begin());
    BOOST_CHECK(dq.empty());
}

BOOST_AUTO_TEST_CASE(TestLargeDataHandling) {
    Deque<int> dq;
    const std::size_t large_size = 100000;
    for (std::size_t i = 0; i < large_size; ++i) {
        dq.push_back(i);
    }
    BOOST_CHECK(dq.size() == large_size);
    BOOST_CHECK(dq[large_size - 1] == large_size - 1);
    for (std::size_t i = 0; i < large_size; ++i) {
        dq.pop_front();
    }
    BOOST_CHECK(dq.empty());
}

BOOST_AUTO_TEST_CASE(TestConsistencyAfterMultipleOperations) {
    Deque<int> dq;
    for (int i = 0; i < 1000; ++i) {
        dq.push_back(i);
        dq.push_front(-i);
    }
    BOOST_CHECK(dq.size() == 2000);
    for (int i = 0; i < 1000; ++i) {
        BOOST_CHECK(dq[i] == -999 + i);
        BOOST_CHECK(dq[1999 - i] == 999 - i);
    }
    for (int i = 0; i < 1000; ++i) {
        dq.pop_back();
        dq.pop_front();
    }
    BOOST_CHECK(dq.empty());
}

BOOST_AUTO_TEST_CASE(TestEmplacement) {
    Deque<std::pair<int, std::string>> dq;

    dq.emplace_back(1, "one");
    BOOST_CHECK(dq.back().first == 1 && dq.back().second == "one");

    dq.emplace_front(0, "zero");
    BOOST_CHECK(dq.front().first == 0 && dq.front().second == "zero");

    BOOST_CHECK(dq.size() == 2);
    BOOST_CHECK(dq[0].first == 0 && dq[1].first == 1);
}

BOOST_AUTO_TEST_CASE(TestNonIntTypes) {
    Deque<std::string> dq;
    dq.push_back("Hello");
    dq.push_back("World");

    BOOST_CHECK(dq.size() == 2);
    BOOST_CHECK(dq[0] == "Hello" && dq[1] == "World");
}

BOOST_AUTO_TEST_CASE(TestStringLargeDataHandling) {
    Deque<std::string> dq;
    const std::size_t large_size = 10000;
    for (std::size_t i = 0; i < large_size; ++i) {
        dq.push_back("String " + std::to_string(i));
    }

    BOOST_CHECK(dq.size() == large_size);
    BOOST_CHECK(dq[large_size - 1] ==
                "String " + std::to_string(large_size - 1));

    for (std::size_t i = 0; i < large_size; ++i) {
        dq.pop_front();
    }

    BOOST_CHECK(dq.empty());
}
 
```
#define BOOST_TEST_MODULE DequeTest
#include <boost/test/included/unit_test.hpp>
#include <deque>

#include "Deque.h"

BOOST_AUTO_TEST_CASE(TestConstructors) {
    Deque<int> dq1;
    BOOST_CHECK(dq1.size() == 0);

    Deque<int> dq2{1, 2, 3, 4, 5};
    BOOST_CHECK(dq2.size() == 5);
    BOOST_CHECK(dq2[0] == 1 && dq2[4] == 5);

    Deque<int> dq3(dq2);
    BOOST_CHECK(dq3.size() == dq2.size());
    BOOST_CHECK(dq3[0] == dq2[0] && dq3[4] == dq2[4]);

    Deque<int> dq4(std::move(dq2));
    BOOST_CHECK(dq4.size() == 5);
    BOOST_CHECK(dq2.size() == 0);
}

BOOST_AUTO_TEST_CASE(TestPushPop) {
    Deque<int> dq;
    dq.push_back(1);
    dq.push_front(2);
    BOOST_CHECK(dq.size() == 2);
    BOOST_CHECK(dq[0] == 2 && dq[1] == 1);

    dq.pop_back();
    BOOST_CHECK(dq.size() == 1 && dq[0] == 2);

    dq.pop_front();
    BOOST_CHECK(dq.empty());

    for (int i = 1; i <= 100; ++i) {
        dq.push_front(i);
        BOOST_CHECK(dq.size() == i);
    }

    for (int i = 99; i >= 0; --i) {
        dq.pop_back();
        BOOST_CHECK(dq.size() == i);
    }
}

BOOST_AUTO_TEST_CASE(TestIterators) {
    Deque<int> dq;

    for (int i = 0; i < 100; ++i) {
        dq.push_back(i);
    }

    Deque<int> dq2;
    for (auto it = dq.begin(); it != dq.end(); ++it) {
        dq2.push_back(*it);
    }

    BOOST_CHECK(dq == dq2);

    Deque<int> rev;
    for (auto it = dq.rbegin(); it != dq.rend(); ++it) {
        rev.push_back(*it);
    }

    for (int i = 0, j = rev.size() - 1; i < j; i++, j--) {
        swap(rev[i], rev[j]);
    }

    BOOST_CHECK(dq == rev);
}

BOOST_AUTO_TEST_CASE(TestBoundaryInsertionAndDeletion) {
    Deque<int> dq;
    dq.insert(dq.begin(), 1);
    BOOST_CHECK(dq.size() == 1 && dq[0] == 1);

    dq.insert(dq.end(), 2);
    BOOST_CHECK(dq.size() == 2 && dq[1] == 2);

    dq.erase(dq.begin());
    BOOST_CHECK(dq.size() == 1 && dq[0] == 2);

    dq.erase(dq.begin());
    BOOST_CHECK(dq.empty());
}

BOOST_AUTO_TEST_CASE(TestLargeDataHandling) {
    Deque<int> dq;
    const std::size_t large_size = 100000;
    for (std::size_t i = 0; i < large_size; ++i) {
        dq.push_back(i);
    }
    BOOST_CHECK(dq.size() == large_size);
    BOOST_CHECK(dq[large_size - 1] == large_size - 1);
    for (std::size_t i = 0; i < large_size; ++i) {
        dq.pop_front();
    }
    BOOST_CHECK(dq.empty());
}

BOOST_AUTO_TEST_CASE(TestConsistencyAfterMultipleOperations) {
    Deque<int> dq;
    for (int i = 0; i < 1000; ++i) {
        dq.push_back(i);
        dq.push_front(-i);
    }
    BOOST_CHECK(dq.size() == 2000);
    for (int i = 0; i < 1000; ++i) {
        BOOST_CHECK(dq[i] == -999 + i);
        BOOST_CHECK(dq[1999 - i] == 999 - i);
    }
    for (int i = 0; i < 1000; ++i) {
        dq.pop_back();
        dq.pop_front();
    }
    BOOST_CHECK(dq.empty());
}

BOOST_AUTO_TEST_CASE(TestEmplacement) {
    Deque<std::pair<int, std::string>> dq;

    dq.emplace_back(1, "one");
    BOOST_CHECK(dq.back().first == 1 && dq.back().second == "one");

    dq.emplace_front(0, "zero");
    BOOST_CHECK(dq.front().first == 0 && dq.front().second == "zero");

    BOOST_CHECK(dq.size() == 2);
    BOOST_CHECK(dq[0].first == 0 && dq[1].first == 1);
}

BOOST_AUTO_TEST_CASE(TestNonIntTypes) {
    Deque<std::string> dq;
    dq.push_back("Hello");
    dq.push_back("World");

    BOOST_CHECK(dq.size() == 2);
    BOOST_CHECK(dq[0] == "Hello" && dq[1] == "World");
}

BOOST_AUTO_TEST_CASE(TestStringLargeDataHandling) {
    Deque<std::string> dq;
    const std::size_t large_size = 10000;
    for (std::size_t i = 0; i < large_size; ++i) {
        dq.push_back("String " + std::to_string(i));
    }

    BOOST_CHECK(dq.size() == large_size);
    BOOST_CHECK(dq[large_size - 1] ==
                "String " + std::to_string(large_size - 1));

    for (std::size_t i = 0; i < large_size; ++i) {
        dq.pop_front();
    }

    BOOST_CHECK(dq.empty());
}
Removed unused copy_blocks() member function
Source Link
jdav22
  • 381
  • 2
  • 10
template <typename T>
class Deque {
  public:
    using iterator = Iterator<T, T>;
    using const_iterator = Iterator<T, const T>;
    using reverse_iterator = std::reverse_iterator<iterator>;
    using const_reverse_iterator = std::reverse_iterator<const_iterator>;

    constexpr Deque()
        : front_blocks{}, back_blocks{}, front_block_idx{0},
          front_idx_in_block{0}, back_block_idx{0},
          back_idx_in_block{0}, m_size{0} {
        back_blocks.push_back(new T[BLOCK_SIZE]);
    }

    ~Deque() {
        clear();
        for (T* block : front_blocks) {
            delete[] block;
        }

        for (T* block : back_blocks) {
            delete[] block;
        }
    }

    constexpr Deque(std::initializer_list<T> items) : Deque() {
        assign(items.begin(), items.end());
    }

    constexpr Deque(const Deque& other) : Deque() {
        // with this implementation, the underlying representation
        // of values in the deque may be different, but the values
        // themselves and the order remain the same
        assign(other.cbegin(), other.cend());
    }

    constexpr Deque& operator=(const Deque& other) {
        Deque temp{other};
        swap(temp);

        return *this;
    }

    constexpr Deque(Deque&& other) noexcept : Deque() {
        swap(other);
    }

    constexpr Deque& operator=(Deque&& other) noexcept {
        swap(other);
        return *this;
    }

    constexpr bool operator==(const Deque& other) {
        if (size() != other.size())
            return false;

        for (std::size_t idx = 0; idx < size(); ++idx) {
            if ((*this)[idx] != other[idx])
                return false;
        }

        return true;
    }

    T& front() {
        return (*this)[0];
    }
    T& back() {
        return (*this)[m_size - 1];
    }

    template <typename... Args>
    constexpr void emplace_front(Args&&... args) {
        decrement(front_idx_in_block, front_block_idx);
        expand_front_if_needed();

        T* block = get_block(front_block_idx, front_blocks, back_blocks);
        new (std::addressof(block[front_idx_in_block]))
            T(std::forward<Args>(args)...);
        ++m_size;
    }

    constexpr void push_front(const T& value) {
        emplace_front(value);
    }

    constexpr void push_front(T&& value) {
        emplace_front(std::move(value));
    }

    constexpr void pop_front() {
        T* block = get_block(front_block_idx, front_blocks, back_blocks);
        std::destroy_at(std::addressof(block[front_idx_in_block]));

        increment(front_idx_in_block, front_block_idx);
        m_size--;
    }

    template <typename... Args>
    constexpr void emplace_back(Args&&... args) {
        T* block = get_block(back_block_idx, front_blocks, back_blocks);
        new (std::addressof(block[back_idx_in_block]))
            T(std::forward<Args>(args)...);

        increment(back_idx_in_block, back_block_idx);
        expand_back_if_needed();

        ++m_size;
    }

    constexpr void push_back(const T& value) {
        emplace_back(value);
    }

    constexpr void push_back(T&& value) {
        emplace_back(std::move(value));
    }

    constexpr void pop_back() {
        decrement(back_idx_in_block, back_block_idx);

        T* block = get_block(back_block_idx, front_blocks, back_blocks);
        std::destroy_at(std::addressof(block[back_idx_in_block]));

        --m_size;
    }

    constexpr T& operator[](std::size_t idx) const {
        auto [cur_block, idx_within_cur_block] = idx_to_block_position(idx);
        return get_block(cur_block, front_blocks,
                         back_blocks)[idx_within_cur_block];
    }

    constexpr T& operator[](std::size_t idx) {
        auto [cur_block, idx_within_cur_block] = idx_to_block_position(idx);
        return get_block(cur_block, front_blocks,
                         back_blocks)[idx_within_cur_block];
    }

    constexpr std::size_t size() const noexcept {
        return m_size;
    }

    constexpr bool empty() const noexcept {
        return m_size == 0;
    }

    constexpr void swap(Deque& other) noexcept {
        using std::swap;

        swap(front_blocks, other.front_blocks);
        swap(back_blocks, other.back_blocks);
        swap(front_block_idx, other.front_block_idx);
        swap(front_idx_in_block, other.front_idx_in_block);
        swap(back_block_idx, other.back_block_idx);
        swap(back_idx_in_block, other.back_idx_in_block);
        swap(m_size, other.m_size);
    }

    /* Iterator support */

    iterator begin() {
        return iterator(front_blocks, back_blocks, front_block_idx,
                        front_idx_in_block, 0);
    }

    iterator end() {
        return iterator(front_blocks, back_blocks, back_block_idx,
                        back_idx_in_block, size());
    }

    const_iterator cbegin() const {
        return const_iterator(front_blocks, back_blocks, front_block_idx,
                              front_idx_in_block, 0);
    }

    const_iterator cend() const {
        return const_iterator(front_blocks, back_blocks, back_block_idx,
                              back_idx_in_block, size());
    }

    reverse_iterator rbegin() {
        return reverse_iterator(end());
    }

    reverse_iterator rend() {
        return reverse_iterator(begin());
    }

    const_reverse_iterator crbegin() const {
        return const_reverse_iterator(cend());
    }
    const_reverse_iterator crend() const {
        return const_reverse_iterator(cbegin());
    }

    template <typename InputIt>
    void assign(InputIt begin, InputIt end) {
        for (auto&& it = begin; it != end; ++it) {
            push_back(*it);
        }
    }

    iterator insert(iterator pos, const T& value) {
        return insert(pos, 1, value);
    }

    iterator insert(iterator pos, std::size_t count, const T& value) {
        if (count == 0)
            return pos;

        int pos_idx = std::distance(begin(), pos);
        int new_size = m_size + count;

        // Ensure there is enough space for the new elements
        while (idx_to_block_position(new_size - 1).first >=
               back_blocks.size()) {
            back_blocks.push_back(new T[BLOCK_SIZE]);
        }

        // Move existing elements to make space for the new elements
        for (int i = int(m_size) - 1; i >= pos_idx; --i) {
            move_deque_element(i, i + count);
        }

        // Construct new elements in place
        for (std::size_t i = 0; i < count; ++i) {
            new (std::addressof((*this)[pos_idx + i])) T(value);
        }

        m_size = new_size;
        return iterator(front_blocks, back_blocks,
                        idx_to_block_position(pos_idx).first,
                        idx_to_block_position(pos_idx).second, pos_idx);
    }

    iterator erase(iterator pos) {
        return erase(pos, 1);
    }

    iterator erase(iterator pos, int count) {
        if (count == 0)
            return pos;

        int pos_idx = std::distance(begin(), pos);
        int new_size = m_size - count;

        // destroy elements being erased
        for (int i = pos_idx; i < pos_idx + count; ++i) {
            auto [block_idx, idx_in_block] = idx_to_block_position(i);
            T* block = get_block(block_idx, front_blocks, back_blocks);
            std::destroy_at(std::addressof(block[block_idx]));
        }

        // move elements back
        for (int i = pos_idx + count; i < pos_idx + 2 * count; ++i) {
            move_deque_element(i, i - count);
        }

        m_size = new_size;
        return iterator(front_blocks, back_blocks,
                        idx_to_block_position(pos_idx).first,
                        idx_to_block_position(pos_idx).second, pos_idx);
    }

    void clear() noexcept {
        for (auto&& elem : *this) {
            elem.~T();
        }
        m_size = 0;
    }

  private:
    std::vector<T*> front_blocks, back_blocks;
    int front_block_idx, front_idx_in_block;
    int back_block_idx, back_idx_in_block;

    std::size_t m_size;

    constexpr void expand_front_if_needed() {
        if (get_front_block_vector_idx(front_block_idx) >=
            front_blocks.size()) {
            front_blocks.push_back(new T[BLOCK_SIZE]);
        }
    }

    constexpr void expand_back_if_needed() {
        if (back_block_idx >= back_blocks.size()) {
            back_blocks.push_back(new T[BLOCK_SIZE]);
        }
    }

    void copy_blocks(const std::vector<T*>& src, std::vector<T*>& dest) {
        try {
            for (std::size_t idx = 0; idx < src.size(); ++idx) {
                dest[idx] = new T[BLOCK_SIZE];
                std::uninitialized_copy(src[idx], src[idx] + BLOCK_SIZE,
                                        dest[idx]);
            }
        } catch (...) {
            for (T* block : dest) {
                delete[] block;
            }

            throw;
        }
    }

    constexpr std::pair<int, int>
    idx_to_block_position(std::size_t idx) const noexcept {
        int num_blocks = idx / BLOCK_SIZE;
        int cur_block = front_block_idx + num_blocks;

        int offset = idx % BLOCK_SIZE;
        int idx_within_cur_block = front_idx_in_block + offset;

        if (idx_within_cur_block >= BLOCK_SIZE) {
            cur_block += 1;
            idx_within_cur_block %= BLOCK_SIZE;
        }

        return {cur_block, idx_within_cur_block};
    }

    void move_deque_element(int src_idx, int dest_idx) {
        auto [src_block, src_block_idx] = idx_to_block_position(src_idx);
        auto [dest_block, dest_block_idx] = idx_to_block_position(dest_idx);

        T* src = get_block(src_block, front_blocks, back_blocks);
        T* dest = get_block(dest_block, front_blocks, back_blocks);

        new (std::addressof(dest[dest_block_idx]))
            T(std::move(src[src_block_idx]));
        std::destroy_at(std::addressof(src[src_block_idx]));
    }
};
template <typename T>
class Deque {
  public:
    using iterator = Iterator<T, T>;
    using const_iterator = Iterator<T, const T>;
    using reverse_iterator = std::reverse_iterator<iterator>;
    using const_reverse_iterator = std::reverse_iterator<const_iterator>;

    constexpr Deque()
        : front_blocks{}, back_blocks{}, front_block_idx{0},
          front_idx_in_block{0}, back_block_idx{0},
          back_idx_in_block{0}, m_size{0} {
        back_blocks.push_back(new T[BLOCK_SIZE]);
    }

    ~Deque() {
        clear();
        for (T* block : front_blocks) {
            delete[] block;
        }

        for (T* block : back_blocks) {
            delete[] block;
        }
    }

    constexpr Deque(std::initializer_list<T> items) : Deque() {
        assign(items.begin(), items.end());
    }

    constexpr Deque(const Deque& other) : Deque() {
        // with this implementation, the underlying representation
        // of values in the deque may be different, but the values
        // themselves and the order remain the same
        assign(other.cbegin(), other.cend());
    }

    constexpr Deque& operator=(const Deque& other) {
        Deque temp{other};
        swap(temp);

        return *this;
    }

    constexpr Deque(Deque&& other) noexcept : Deque() {
        swap(other);
    }

    constexpr Deque& operator=(Deque&& other) noexcept {
        swap(other);
        return *this;
    }

    constexpr bool operator==(const Deque& other) {
        if (size() != other.size())
            return false;

        for (std::size_t idx = 0; idx < size(); ++idx) {
            if ((*this)[idx] != other[idx])
                return false;
        }

        return true;
    }

    T& front() {
        return (*this)[0];
    }
    T& back() {
        return (*this)[m_size - 1];
    }

    template <typename... Args>
    constexpr void emplace_front(Args&&... args) {
        decrement(front_idx_in_block, front_block_idx);
        expand_front_if_needed();

        T* block = get_block(front_block_idx, front_blocks, back_blocks);
        new (std::addressof(block[front_idx_in_block]))
            T(std::forward<Args>(args)...);
        ++m_size;
    }

    constexpr void push_front(const T& value) {
        emplace_front(value);
    }

    constexpr void push_front(T&& value) {
        emplace_front(std::move(value));
    }

    constexpr void pop_front() {
        T* block = get_block(front_block_idx, front_blocks, back_blocks);
        std::destroy_at(std::addressof(block[front_idx_in_block]));

        increment(front_idx_in_block, front_block_idx);
        m_size--;
    }

    template <typename... Args>
    constexpr void emplace_back(Args&&... args) {
        T* block = get_block(back_block_idx, front_blocks, back_blocks);
        new (std::addressof(block[back_idx_in_block]))
            T(std::forward<Args>(args)...);

        increment(back_idx_in_block, back_block_idx);
        expand_back_if_needed();

        ++m_size;
    }

    constexpr void push_back(const T& value) {
        emplace_back(value);
    }

    constexpr void push_back(T&& value) {
        emplace_back(std::move(value));
    }

    constexpr void pop_back() {
        decrement(back_idx_in_block, back_block_idx);

        T* block = get_block(back_block_idx, front_blocks, back_blocks);
        std::destroy_at(std::addressof(block[back_idx_in_block]));

        --m_size;
    }

    constexpr T& operator[](std::size_t idx) const {
        auto [cur_block, idx_within_cur_block] = idx_to_block_position(idx);
        return get_block(cur_block, front_blocks,
                         back_blocks)[idx_within_cur_block];
    }

    constexpr T& operator[](std::size_t idx) {
        auto [cur_block, idx_within_cur_block] = idx_to_block_position(idx);
        return get_block(cur_block, front_blocks,
                         back_blocks)[idx_within_cur_block];
    }

    constexpr std::size_t size() const noexcept {
        return m_size;
    }

    constexpr bool empty() const noexcept {
        return m_size == 0;
    }

    constexpr void swap(Deque& other) noexcept {
        using std::swap;

        swap(front_blocks, other.front_blocks);
        swap(back_blocks, other.back_blocks);
        swap(front_block_idx, other.front_block_idx);
        swap(front_idx_in_block, other.front_idx_in_block);
        swap(back_block_idx, other.back_block_idx);
        swap(back_idx_in_block, other.back_idx_in_block);
        swap(m_size, other.m_size);
    }

    /* Iterator support */

    iterator begin() {
        return iterator(front_blocks, back_blocks, front_block_idx,
                        front_idx_in_block, 0);
    }

    iterator end() {
        return iterator(front_blocks, back_blocks, back_block_idx,
                        back_idx_in_block, size());
    }

    const_iterator cbegin() const {
        return const_iterator(front_blocks, back_blocks, front_block_idx,
                              front_idx_in_block, 0);
    }

    const_iterator cend() const {
        return const_iterator(front_blocks, back_blocks, back_block_idx,
                              back_idx_in_block, size());
    }

    reverse_iterator rbegin() {
        return reverse_iterator(end());
    }

    reverse_iterator rend() {
        return reverse_iterator(begin());
    }

    const_reverse_iterator crbegin() const {
        return const_reverse_iterator(cend());
    }
    const_reverse_iterator crend() const {
        return const_reverse_iterator(cbegin());
    }

    template <typename InputIt>
    void assign(InputIt begin, InputIt end) {
        for (auto&& it = begin; it != end; ++it) {
            push_back(*it);
        }
    }

    iterator insert(iterator pos, const T& value) {
        return insert(pos, 1, value);
    }

    iterator insert(iterator pos, std::size_t count, const T& value) {
        if (count == 0)
            return pos;

        int pos_idx = std::distance(begin(), pos);
        int new_size = m_size + count;

        // Ensure there is enough space for the new elements
        while (idx_to_block_position(new_size - 1).first >=
               back_blocks.size()) {
            back_blocks.push_back(new T[BLOCK_SIZE]);
        }

        // Move existing elements to make space for the new elements
        for (int i = int(m_size) - 1; i >= pos_idx; --i) {
            move_deque_element(i, i + count);
        }

        // Construct new elements in place
        for (std::size_t i = 0; i < count; ++i) {
            new (std::addressof((*this)[pos_idx + i])) T(value);
        }

        m_size = new_size;
        return iterator(front_blocks, back_blocks,
                        idx_to_block_position(pos_idx).first,
                        idx_to_block_position(pos_idx).second, pos_idx);
    }

    iterator erase(iterator pos) {
        return erase(pos, 1);
    }

    iterator erase(iterator pos, int count) {
        if (count == 0)
            return pos;

        int pos_idx = std::distance(begin(), pos);
        int new_size = m_size - count;

        // destroy elements being erased
        for (int i = pos_idx; i < pos_idx + count; ++i) {
            auto [block_idx, idx_in_block] = idx_to_block_position(i);
            T* block = get_block(block_idx, front_blocks, back_blocks);
            std::destroy_at(std::addressof(block[block_idx]));
        }

        // move elements back
        for (int i = pos_idx + count; i < pos_idx + 2 * count; ++i) {
            move_deque_element(i, i - count);
        }

        m_size = new_size;
        return iterator(front_blocks, back_blocks,
                        idx_to_block_position(pos_idx).first,
                        idx_to_block_position(pos_idx).second, pos_idx);
    }

    void clear() noexcept {
        for (auto&& elem : *this) {
            elem.~T();
        }
        m_size = 0;
    }

  private:
    std::vector<T*> front_blocks, back_blocks;
    int front_block_idx, front_idx_in_block;
    int back_block_idx, back_idx_in_block;

    std::size_t m_size;

    constexpr void expand_front_if_needed() {
        if (get_front_block_vector_idx(front_block_idx) >=
            front_blocks.size()) {
            front_blocks.push_back(new T[BLOCK_SIZE]);
        }
    }

    constexpr void expand_back_if_needed() {
        if (back_block_idx >= back_blocks.size()) {
            back_blocks.push_back(new T[BLOCK_SIZE]);
        }
    }

    void copy_blocks(const std::vector<T*>& src, std::vector<T*>& dest) {
        try {
            for (std::size_t idx = 0; idx < src.size(); ++idx) {
                dest[idx] = new T[BLOCK_SIZE];
                std::uninitialized_copy(src[idx], src[idx] + BLOCK_SIZE,
                                        dest[idx]);
            }
        } catch (...) {
            for (T* block : dest) {
                delete[] block;
            }

            throw;
        }
    }

    constexpr std::pair<int, int>
    idx_to_block_position(std::size_t idx) const noexcept {
        int num_blocks = idx / BLOCK_SIZE;
        int cur_block = front_block_idx + num_blocks;

        int offset = idx % BLOCK_SIZE;
        int idx_within_cur_block = front_idx_in_block + offset;

        if (idx_within_cur_block >= BLOCK_SIZE) {
            cur_block += 1;
            idx_within_cur_block %= BLOCK_SIZE;
        }

        return {cur_block, idx_within_cur_block};
    }

    void move_deque_element(int src_idx, int dest_idx) {
        auto [src_block, src_block_idx] = idx_to_block_position(src_idx);
        auto [dest_block, dest_block_idx] = idx_to_block_position(dest_idx);

        T* src = get_block(src_block, front_blocks, back_blocks);
        T* dest = get_block(dest_block, front_blocks, back_blocks);

        new (std::addressof(dest[dest_block_idx]))
            T(std::move(src[src_block_idx]));
        std::destroy_at(std::addressof(src[src_block_idx]));
    }
};
template <typename T>
class Deque {
  public:
    using iterator = Iterator<T, T>;
    using const_iterator = Iterator<T, const T>;
    using reverse_iterator = std::reverse_iterator<iterator>;
    using const_reverse_iterator = std::reverse_iterator<const_iterator>;

    constexpr Deque()
        : front_blocks{}, back_blocks{}, front_block_idx{0},
          front_idx_in_block{0}, back_block_idx{0},
          back_idx_in_block{0}, m_size{0} {
        back_blocks.push_back(new T[BLOCK_SIZE]);
    }

    ~Deque() {
        clear();
        for (T* block : front_blocks) {
            delete[] block;
        }

        for (T* block : back_blocks) {
            delete[] block;
        }
    }

    constexpr Deque(std::initializer_list<T> items) : Deque() {
        assign(items.begin(), items.end());
    }

    constexpr Deque(const Deque& other) : Deque() {
        // with this implementation, the underlying representation
        // of values in the deque may be different, but the values
        // themselves and the order remain the same
        assign(other.cbegin(), other.cend());
    }

    constexpr Deque& operator=(const Deque& other) {
        Deque temp{other};
        swap(temp);

        return *this;
    }

    constexpr Deque(Deque&& other) noexcept : Deque() {
        swap(other);
    }

    constexpr Deque& operator=(Deque&& other) noexcept {
        swap(other);
        return *this;
    }

    constexpr bool operator==(const Deque& other) {
        if (size() != other.size())
            return false;

        for (std::size_t idx = 0; idx < size(); ++idx) {
            if ((*this)[idx] != other[idx])
                return false;
        }

        return true;
    }

    T& front() {
        return (*this)[0];
    }
    T& back() {
        return (*this)[m_size - 1];
    }

    template <typename... Args>
    constexpr void emplace_front(Args&&... args) {
        decrement(front_idx_in_block, front_block_idx);
        expand_front_if_needed();

        T* block = get_block(front_block_idx, front_blocks, back_blocks);
        new (std::addressof(block[front_idx_in_block]))
            T(std::forward<Args>(args)...);
        ++m_size;
    }

    constexpr void push_front(const T& value) {
        emplace_front(value);
    }

    constexpr void push_front(T&& value) {
        emplace_front(std::move(value));
    }

    constexpr void pop_front() {
        T* block = get_block(front_block_idx, front_blocks, back_blocks);
        std::destroy_at(std::addressof(block[front_idx_in_block]));

        increment(front_idx_in_block, front_block_idx);
        m_size--;
    }

    template <typename... Args>
    constexpr void emplace_back(Args&&... args) {
        T* block = get_block(back_block_idx, front_blocks, back_blocks);
        new (std::addressof(block[back_idx_in_block]))
            T(std::forward<Args>(args)...);

        increment(back_idx_in_block, back_block_idx);
        expand_back_if_needed();

        ++m_size;
    }

    constexpr void push_back(const T& value) {
        emplace_back(value);
    }

    constexpr void push_back(T&& value) {
        emplace_back(std::move(value));
    }

    constexpr void pop_back() {
        decrement(back_idx_in_block, back_block_idx);

        T* block = get_block(back_block_idx, front_blocks, back_blocks);
        std::destroy_at(std::addressof(block[back_idx_in_block]));

        --m_size;
    }

    constexpr T& operator[](std::size_t idx) const {
        auto [cur_block, idx_within_cur_block] = idx_to_block_position(idx);
        return get_block(cur_block, front_blocks,
                         back_blocks)[idx_within_cur_block];
    }

    constexpr T& operator[](std::size_t idx) {
        auto [cur_block, idx_within_cur_block] = idx_to_block_position(idx);
        return get_block(cur_block, front_blocks,
                         back_blocks)[idx_within_cur_block];
    }

    constexpr std::size_t size() const noexcept {
        return m_size;
    }

    constexpr bool empty() const noexcept {
        return m_size == 0;
    }

    constexpr void swap(Deque& other) noexcept {
        using std::swap;

        swap(front_blocks, other.front_blocks);
        swap(back_blocks, other.back_blocks);
        swap(front_block_idx, other.front_block_idx);
        swap(front_idx_in_block, other.front_idx_in_block);
        swap(back_block_idx, other.back_block_idx);
        swap(back_idx_in_block, other.back_idx_in_block);
        swap(m_size, other.m_size);
    }

    /* Iterator support */

    iterator begin() {
        return iterator(front_blocks, back_blocks, front_block_idx,
                        front_idx_in_block, 0);
    }

    iterator end() {
        return iterator(front_blocks, back_blocks, back_block_idx,
                        back_idx_in_block, size());
    }

    const_iterator cbegin() const {
        return const_iterator(front_blocks, back_blocks, front_block_idx,
                              front_idx_in_block, 0);
    }

    const_iterator cend() const {
        return const_iterator(front_blocks, back_blocks, back_block_idx,
                              back_idx_in_block, size());
    }

    reverse_iterator rbegin() {
        return reverse_iterator(end());
    }

    reverse_iterator rend() {
        return reverse_iterator(begin());
    }

    const_reverse_iterator crbegin() const {
        return const_reverse_iterator(cend());
    }
    const_reverse_iterator crend() const {
        return const_reverse_iterator(cbegin());
    }

    template <typename InputIt>
    void assign(InputIt begin, InputIt end) {
        for (auto&& it = begin; it != end; ++it) {
            push_back(*it);
        }
    }

    iterator insert(iterator pos, const T& value) {
        return insert(pos, 1, value);
    }

    iterator insert(iterator pos, std::size_t count, const T& value) {
        if (count == 0)
            return pos;

        int pos_idx = std::distance(begin(), pos);
        int new_size = m_size + count;

        // Ensure there is enough space for the new elements
        while (idx_to_block_position(new_size - 1).first >=
               back_blocks.size()) {
            back_blocks.push_back(new T[BLOCK_SIZE]);
        }

        // Move existing elements to make space for the new elements
        for (int i = int(m_size) - 1; i >= pos_idx; --i) {
            move_deque_element(i, i + count);
        }

        // Construct new elements in place
        for (std::size_t i = 0; i < count; ++i) {
            new (std::addressof((*this)[pos_idx + i])) T(value);
        }

        m_size = new_size;
        return iterator(front_blocks, back_blocks,
                        idx_to_block_position(pos_idx).first,
                        idx_to_block_position(pos_idx).second, pos_idx);
    }

    iterator erase(iterator pos) {
        return erase(pos, 1);
    }

    iterator erase(iterator pos, int count) {
        if (count == 0)
            return pos;

        int pos_idx = std::distance(begin(), pos);
        int new_size = m_size - count;

        // destroy elements being erased
        for (int i = pos_idx; i < pos_idx + count; ++i) {
            auto [block_idx, idx_in_block] = idx_to_block_position(i);
            T* block = get_block(block_idx, front_blocks, back_blocks);
            std::destroy_at(std::addressof(block[block_idx]));
        }

        // move elements back
        for (int i = pos_idx + count; i < pos_idx + 2 * count; ++i) {
            move_deque_element(i, i - count);
        }

        m_size = new_size;
        return iterator(front_blocks, back_blocks,
                        idx_to_block_position(pos_idx).first,
                        idx_to_block_position(pos_idx).second, pos_idx);
    }

    void clear() noexcept {
        for (auto&& elem : *this) {
            elem.~T();
        }
        m_size = 0;
    }

  private:
    std::vector<T*> front_blocks, back_blocks;
    int front_block_idx, front_idx_in_block;
    int back_block_idx, back_idx_in_block;

    std::size_t m_size;

    constexpr void expand_front_if_needed() {
        if (get_front_block_vector_idx(front_block_idx) >=
            front_blocks.size()) {
            front_blocks.push_back(new T[BLOCK_SIZE]);
        }
    }

    constexpr void expand_back_if_needed() {
        if (back_block_idx >= back_blocks.size()) {
            back_blocks.push_back(new T[BLOCK_SIZE]);
        }
    }

    constexpr std::pair<int, int>
    idx_to_block_position(std::size_t idx) const noexcept {
        int num_blocks = idx / BLOCK_SIZE;
        int cur_block = front_block_idx + num_blocks;

        int offset = idx % BLOCK_SIZE;
        int idx_within_cur_block = front_idx_in_block + offset;

        if (idx_within_cur_block >= BLOCK_SIZE) {
            cur_block += 1;
            idx_within_cur_block %= BLOCK_SIZE;
        }

        return {cur_block, idx_within_cur_block};
    }

    void move_deque_element(int src_idx, int dest_idx) {
        auto [src_block, src_block_idx] = idx_to_block_position(src_idx);
        auto [dest_block, dest_block_idx] = idx_to_block_position(dest_idx);

        T* src = get_block(src_block, front_blocks, back_blocks);
        T* dest = get_block(dest_block, front_blocks, back_blocks);

        new (std::addressof(dest[dest_block_idx]))
            T(std::move(src[src_block_idx]));
        std::destroy_at(std::addressof(src[src_block_idx]));
    }
};
Source Link
jdav22
  • 381
  • 2
  • 10

C++ Deque Implementation

Took a shot at implementing std::deque in C++. I aimed for amortized O(1) push_front and push_back, O(1) pop_front and pop_back, and O(1) random access.

I do this by maintaining two vectors of pointers to fixed-size blocks of size 16, called front_blocks and back_blocks. Initially, push_front operations add to the blocks in front_blocks, and push_back operations add to the blocks in back_blocks. However, I also keep track of the front_block_idx and the back_block_idx, to denote which block the current front and current back are in.

These indices can either be 0 or positive, meaning that the current front/back is in back_blocks, or they can be negative, meaning the current front/back is in front_blocks. This was the cleanest way I could think of to get the desired complexities while keeping with the fixed-size block concept from the STL and keeping iterators valid when possible.

I did not aim for this to be a 100% copy of the STL, however, so please let me know if I am missing any crucial functionality, but I did not attempt to get the exact same typedefs as the STL, or all of the possible overloads for all the methods, etc.

The Deque class is below:

template <typename T>
class Deque {
  public:
    using iterator = Iterator<T, T>;
    using const_iterator = Iterator<T, const T>;
    using reverse_iterator = std::reverse_iterator<iterator>;
    using const_reverse_iterator = std::reverse_iterator<const_iterator>;

    constexpr Deque()
        : front_blocks{}, back_blocks{}, front_block_idx{0},
          front_idx_in_block{0}, back_block_idx{0},
          back_idx_in_block{0}, m_size{0} {
        back_blocks.push_back(new T[BLOCK_SIZE]);
    }

    ~Deque() {
        clear();
        for (T* block : front_blocks) {
            delete[] block;
        }

        for (T* block : back_blocks) {
            delete[] block;
        }
    }

    constexpr Deque(std::initializer_list<T> items) : Deque() {
        assign(items.begin(), items.end());
    }

    constexpr Deque(const Deque& other) : Deque() {
        // with this implementation, the underlying representation
        // of values in the deque may be different, but the values
        // themselves and the order remain the same
        assign(other.cbegin(), other.cend());
    }

    constexpr Deque& operator=(const Deque& other) {
        Deque temp{other};
        swap(temp);

        return *this;
    }

    constexpr Deque(Deque&& other) noexcept : Deque() {
        swap(other);
    }

    constexpr Deque& operator=(Deque&& other) noexcept {
        swap(other);
        return *this;
    }

    constexpr bool operator==(const Deque& other) {
        if (size() != other.size())
            return false;

        for (std::size_t idx = 0; idx < size(); ++idx) {
            if ((*this)[idx] != other[idx])
                return false;
        }

        return true;
    }

    T& front() {
        return (*this)[0];
    }
    T& back() {
        return (*this)[m_size - 1];
    }

    template <typename... Args>
    constexpr void emplace_front(Args&&... args) {
        decrement(front_idx_in_block, front_block_idx);
        expand_front_if_needed();

        T* block = get_block(front_block_idx, front_blocks, back_blocks);
        new (std::addressof(block[front_idx_in_block]))
            T(std::forward<Args>(args)...);
        ++m_size;
    }

    constexpr void push_front(const T& value) {
        emplace_front(value);
    }

    constexpr void push_front(T&& value) {
        emplace_front(std::move(value));
    }

    constexpr void pop_front() {
        T* block = get_block(front_block_idx, front_blocks, back_blocks);
        std::destroy_at(std::addressof(block[front_idx_in_block]));

        increment(front_idx_in_block, front_block_idx);
        m_size--;
    }

    template <typename... Args>
    constexpr void emplace_back(Args&&... args) {
        T* block = get_block(back_block_idx, front_blocks, back_blocks);
        new (std::addressof(block[back_idx_in_block]))
            T(std::forward<Args>(args)...);

        increment(back_idx_in_block, back_block_idx);
        expand_back_if_needed();

        ++m_size;
    }

    constexpr void push_back(const T& value) {
        emplace_back(value);
    }

    constexpr void push_back(T&& value) {
        emplace_back(std::move(value));
    }

    constexpr void pop_back() {
        decrement(back_idx_in_block, back_block_idx);

        T* block = get_block(back_block_idx, front_blocks, back_blocks);
        std::destroy_at(std::addressof(block[back_idx_in_block]));

        --m_size;
    }

    constexpr T& operator[](std::size_t idx) const {
        auto [cur_block, idx_within_cur_block] = idx_to_block_position(idx);
        return get_block(cur_block, front_blocks,
                         back_blocks)[idx_within_cur_block];
    }

    constexpr T& operator[](std::size_t idx) {
        auto [cur_block, idx_within_cur_block] = idx_to_block_position(idx);
        return get_block(cur_block, front_blocks,
                         back_blocks)[idx_within_cur_block];
    }

    constexpr std::size_t size() const noexcept {
        return m_size;
    }

    constexpr bool empty() const noexcept {
        return m_size == 0;
    }

    constexpr void swap(Deque& other) noexcept {
        using std::swap;

        swap(front_blocks, other.front_blocks);
        swap(back_blocks, other.back_blocks);
        swap(front_block_idx, other.front_block_idx);
        swap(front_idx_in_block, other.front_idx_in_block);
        swap(back_block_idx, other.back_block_idx);
        swap(back_idx_in_block, other.back_idx_in_block);
        swap(m_size, other.m_size);
    }

    /* Iterator support */

    iterator begin() {
        return iterator(front_blocks, back_blocks, front_block_idx,
                        front_idx_in_block, 0);
    }

    iterator end() {
        return iterator(front_blocks, back_blocks, back_block_idx,
                        back_idx_in_block, size());
    }

    const_iterator cbegin() const {
        return const_iterator(front_blocks, back_blocks, front_block_idx,
                              front_idx_in_block, 0);
    }

    const_iterator cend() const {
        return const_iterator(front_blocks, back_blocks, back_block_idx,
                              back_idx_in_block, size());
    }

    reverse_iterator rbegin() {
        return reverse_iterator(end());
    }

    reverse_iterator rend() {
        return reverse_iterator(begin());
    }

    const_reverse_iterator crbegin() const {
        return const_reverse_iterator(cend());
    }
    const_reverse_iterator crend() const {
        return const_reverse_iterator(cbegin());
    }

    template <typename InputIt>
    void assign(InputIt begin, InputIt end) {
        for (auto&& it = begin; it != end; ++it) {
            push_back(*it);
        }
    }

    iterator insert(iterator pos, const T& value) {
        return insert(pos, 1, value);
    }

    iterator insert(iterator pos, std::size_t count, const T& value) {
        if (count == 0)
            return pos;

        int pos_idx = std::distance(begin(), pos);
        int new_size = m_size + count;

        // Ensure there is enough space for the new elements
        while (idx_to_block_position(new_size - 1).first >=
               back_blocks.size()) {
            back_blocks.push_back(new T[BLOCK_SIZE]);
        }

        // Move existing elements to make space for the new elements
        for (int i = int(m_size) - 1; i >= pos_idx; --i) {
            move_deque_element(i, i + count);
        }

        // Construct new elements in place
        for (std::size_t i = 0; i < count; ++i) {
            new (std::addressof((*this)[pos_idx + i])) T(value);
        }

        m_size = new_size;
        return iterator(front_blocks, back_blocks,
                        idx_to_block_position(pos_idx).first,
                        idx_to_block_position(pos_idx).second, pos_idx);
    }

    iterator erase(iterator pos) {
        return erase(pos, 1);
    }

    iterator erase(iterator pos, int count) {
        if (count == 0)
            return pos;

        int pos_idx = std::distance(begin(), pos);
        int new_size = m_size - count;

        // destroy elements being erased
        for (int i = pos_idx; i < pos_idx + count; ++i) {
            auto [block_idx, idx_in_block] = idx_to_block_position(i);
            T* block = get_block(block_idx, front_blocks, back_blocks);
            std::destroy_at(std::addressof(block[block_idx]));
        }

        // move elements back
        for (int i = pos_idx + count; i < pos_idx + 2 * count; ++i) {
            move_deque_element(i, i - count);
        }

        m_size = new_size;
        return iterator(front_blocks, back_blocks,
                        idx_to_block_position(pos_idx).first,
                        idx_to_block_position(pos_idx).second, pos_idx);
    }

    void clear() noexcept {
        for (auto&& elem : *this) {
            elem.~T();
        }
        m_size = 0;
    }

  private:
    std::vector<T*> front_blocks, back_blocks;
    int front_block_idx, front_idx_in_block;
    int back_block_idx, back_idx_in_block;

    std::size_t m_size;

    constexpr void expand_front_if_needed() {
        if (get_front_block_vector_idx(front_block_idx) >=
            front_blocks.size()) {
            front_blocks.push_back(new T[BLOCK_SIZE]);
        }
    }

    constexpr void expand_back_if_needed() {
        if (back_block_idx >= back_blocks.size()) {
            back_blocks.push_back(new T[BLOCK_SIZE]);
        }
    }

    void copy_blocks(const std::vector<T*>& src, std::vector<T*>& dest) {
        try {
            for (std::size_t idx = 0; idx < src.size(); ++idx) {
                dest[idx] = new T[BLOCK_SIZE];
                std::uninitialized_copy(src[idx], src[idx] + BLOCK_SIZE,
                                        dest[idx]);
            }
        } catch (...) {
            for (T* block : dest) {
                delete[] block;
            }

            throw;
        }
    }

    constexpr std::pair<int, int>
    idx_to_block_position(std::size_t idx) const noexcept {
        int num_blocks = idx / BLOCK_SIZE;
        int cur_block = front_block_idx + num_blocks;

        int offset = idx % BLOCK_SIZE;
        int idx_within_cur_block = front_idx_in_block + offset;

        if (idx_within_cur_block >= BLOCK_SIZE) {
            cur_block += 1;
            idx_within_cur_block %= BLOCK_SIZE;
        }

        return {cur_block, idx_within_cur_block};
    }

    void move_deque_element(int src_idx, int dest_idx) {
        auto [src_block, src_block_idx] = idx_to_block_position(src_idx);
        auto [dest_block, dest_block_idx] = idx_to_block_position(dest_idx);

        T* src = get_block(src_block, front_blocks, back_blocks);
        T* dest = get_block(dest_block, front_blocks, back_blocks);

        new (std::addressof(dest[dest_block_idx]))
            T(std::move(src[src_block_idx]));
        std::destroy_at(std::addressof(src[src_block_idx]));
    }
};

The Iterator class this uses:

template <typename T, typename ElementType>
class Iterator {
  public:
    using difference_type = std::ptrdiff_t;
    using value_type = T;
    using pointer = T*;
    using reference = T&;
    using iterator_category = std::random_access_iterator_tag;

    Iterator(const std::vector<T*>& front_blocks,
             const std::vector<T*>& back_blocks,
             int block_idx,
             int idx_within_block,
             int deque_idx)
        : front_blocks{front_blocks},
          back_blocks{back_blocks}, block_idx{block_idx},
          idx_within_block{idx_within_block}, deque_idx{deque_idx} {
    }

    ElementType& operator*() const {
        return *current();
    }
    ElementType* operator->() const {
        return current();
    }

    Iterator& operator++() {
        increment(idx_within_block, block_idx);
        return *this;
    }

    Iterator operator++(int) {
        Iterator temp = *this;
        ++(*this);
        return temp;
    }

    Iterator& operator--() {
        decrement(idx_within_block, block_idx);
        return *this;
    }

    Iterator operator--(int) {
        Iterator temp = *this;
        --(*this);

        return temp;
    }

    Iterator& operator+=(difference_type n) {
        deque_idx += n;
        shift_indices(block_idx, idx_within_block, n);

        return *this;
    }

    Iterator& operator-=(difference_type n) {
        deque_idx -= n;
        shift_indices(block_idx, idx_within_block, -n);

        return *this;
    }

    Iterator operator+(difference_type n) const {
        Iterator temp = *this;
        return temp += n;
    }

    Iterator operator-(difference_type n) const {
        Iterator temp = *this;
        return temp -= n;
    }

    difference_type operator-(const Iterator& other) const {
        return this->deque_idx - other.deque_idx;
    }

    bool operator==(const Iterator& other) const {
        return front_blocks == other.front_blocks and
               back_blocks == other.back_blocks and
               block_idx == other.block_idx and
               idx_within_block == other.idx_within_block;
    }

    bool operator!=(const Iterator& other) const {
        return !(*this == other);
    }

  private:
    const std::vector<T*>&front_blocks, &back_blocks;
    int block_idx, idx_within_block, deque_idx;

    ElementType* current() const {
        return get_block(block_idx, front_blocks, back_blocks) +
               idx_within_block;
    }
};

Some utility functions/constants (along with the headers used for the whole class):


#include <algorithm>
#include <cstddef>
#include <exception>
#include <memory>
#include <stdexcept>
#include <utility>

static constexpr std::size_t BLOCK_SIZE = 16;

constexpr int get_front_block_vector_idx(int block_idx) {
    if (block_idx >= 0) {
        throw std::out_of_range("only works with negative values");
    }

    return abs(block_idx) - 1;
}

constexpr void increment(int& idx_in_block, int& block_idx) noexcept {
    idx_in_block++;
    if (idx_in_block == BLOCK_SIZE) {
        idx_in_block = 0;
        block_idx++;
    }
}

constexpr void decrement(int& idx_in_block, int& block_idx) noexcept {
    if (idx_in_block > 0) {
        idx_in_block--;
    } else {
        idx_in_block = BLOCK_SIZE - 1;
        block_idx -= 1;
    }
}

template <typename T>
constexpr T* get_block(int block_idx,
                       const std::vector<T*>& front_blocks,
                       const std::vector<T*>& back_blocks) {
    if (block_idx >= 0) {
        return back_blocks[block_idx];
    } else {
        return front_blocks[get_front_block_vector_idx(block_idx)];
    }
}

constexpr void shift_indices(int& block_idx, int& idx_within_block, int shift) {
    // Calculate the total index relative to the start of the deque
    int total_idx = block_idx * BLOCK_SIZE + idx_within_block + shift;

    // Update block_idx and idx_within_block based on the new total index
    block_idx = total_idx / BLOCK_SIZE;
    idx_within_block = total_idx % BLOCK_SIZE;

    // Handle negative indices, ensuring block_idx and idx_within_block are
    // always valid
    while (idx_within_block < 0) {
        idx_within_block += BLOCK_SIZE;
        block_idx--;
    }
}

And finally some unit tests:

#define BOOST_TEST_MODULE DequeTest
#include <boost/test/included/unit_test.hpp>
#include <deque>

#include "Deque.h"

BOOST_AUTO_TEST_CASE(TestConstructors) {
    Deque<int> dq1;
    BOOST_CHECK(dq1.size() == 0);

    Deque<int> dq2{1, 2, 3, 4, 5};
    BOOST_CHECK(dq2.size() == 5);
    BOOST_CHECK(dq2[0] == 1 && dq2[4] == 5);

    Deque<int> dq3(dq2);
    BOOST_CHECK(dq3.size() == dq2.size());
    BOOST_CHECK(dq3[0] == dq2[0] && dq3[4] == dq2[4]);

    Deque<int> dq4(std::move(dq2));
    BOOST_CHECK(dq4.size() == 5);
    BOOST_CHECK(dq2.size() == 0);
}

BOOST_AUTO_TEST_CASE(TestPushPop) {
    Deque<int> dq;
    dq.push_back(1);
    dq.push_front(2);
    BOOST_CHECK(dq.size() == 2);
    BOOST_CHECK(dq[0] == 2 && dq[1] == 1);

    dq.pop_back();
    BOOST_CHECK(dq.size() == 1 && dq[0] == 2);

    dq.pop_front();
    BOOST_CHECK(dq.empty());

    for (int i = 1; i <= 100; ++i) {
        dq.push_front(i);
        BOOST_CHECK(dq.size() == i);
    }

    for (int i = 99; i >= 0; --i) {
        dq.pop_back();
        BOOST_CHECK(dq.size() == i);
    }
}

BOOST_AUTO_TEST_CASE(TestIterators) {
    Deque<int> dq;

    for (int i = 0; i < 100; ++i) {
        dq.push_back(i);
    }

    Deque<int> dq2;
    for (auto it = dq.begin(); it != dq.end(); ++it) {
        dq2.push_back(*it);
    }

    BOOST_CHECK(dq == dq2);

    Deque<int> rev;
    for (auto it = dq.rbegin(); it != dq.rend(); ++it) {
        rev.push_back(*it);
    }

    for (int i = 0, j = rev.size() - 1; i < j; i++, j--) {
        swap(rev[i], rev[j]);
    }

    BOOST_CHECK(dq == rev);
}

BOOST_AUTO_TEST_CASE(TestBoundaryInsertionAndDeletion) {
    Deque<int> dq;
    dq.insert(dq.begin(), 1);
    BOOST_CHECK(dq.size() == 1 && dq[0] == 1);

    dq.insert(dq.end(), 2);
    BOOST_CHECK(dq.size() == 2 && dq[1] == 2);

    dq.erase(dq.begin());
    BOOST_CHECK(dq.size() == 1 && dq[0] == 2);

    dq.erase(dq.begin());
    BOOST_CHECK(dq.empty());
}

BOOST_AUTO_TEST_CASE(TestLargeDataHandling) {
    Deque<int> dq;
    const std::size_t large_size = 100000;
    for (std::size_t i = 0; i < large_size; ++i) {
        dq.push_back(i);
    }
    BOOST_CHECK(dq.size() == large_size);
    BOOST_CHECK(dq[large_size - 1] == large_size - 1);
    for (std::size_t i = 0; i < large_size; ++i) {
        dq.pop_front();
    }
    BOOST_CHECK(dq.empty());
}

BOOST_AUTO_TEST_CASE(TestConsistencyAfterMultipleOperations) {
    Deque<int> dq;
    for (int i = 0; i < 1000; ++i) {
        dq.push_back(i);
        dq.push_front(-i);
    }
    BOOST_CHECK(dq.size() == 2000);
    for (int i = 0; i < 1000; ++i) {
        BOOST_CHECK(dq[i] == -999 + i);
        BOOST_CHECK(dq[1999 - i] == 999 - i);
    }
    for (int i = 0; i < 1000; ++i) {
        dq.pop_back();
        dq.pop_front();
    }
    BOOST_CHECK(dq.empty());
}

BOOST_AUTO_TEST_CASE(TestEmplacement) {
    Deque<std::pair<int, std::string>> dq;

    dq.emplace_back(1, "one");
    BOOST_CHECK(dq.back().first == 1 && dq.back().second == "one");

    dq.emplace_front(0, "zero");
    BOOST_CHECK(dq.front().first == 0 && dq.front().second == "zero");

    BOOST_CHECK(dq.size() == 2);
    BOOST_CHECK(dq[0].first == 0 && dq[1].first == 1);
}

BOOST_AUTO_TEST_CASE(TestNonIntTypes) {
    Deque<std::string> dq;
    dq.push_back("Hello");
    dq.push_back("World");

    BOOST_CHECK(dq.size() == 2);
    BOOST_CHECK(dq[0] == "Hello" && dq[1] == "World");
}

BOOST_AUTO_TEST_CASE(TestStringLargeDataHandling) {
    Deque<std::string> dq;
    const std::size_t large_size = 10000;
    for (std::size_t i = 0; i < large_size; ++i) {
        dq.push_back("String " + std::to_string(i));
    }

    BOOST_CHECK(dq.size() == large_size);
    BOOST_CHECK(dq[large_size - 1] ==
                "String " + std::to_string(large_size - 1));

    for (std::size_t i = 0; i < large_size; ++i) {
        dq.pop_front();
    }

    BOOST_CHECK(dq.empty());
}

```