Skip to main content
Added RTW tag to also reinforce the OP's intention of implementing `std::forward_list<>`
Link
added 167 characters in body
Source Link

PPS.S This is for a library I am implementing for myself, as a way of learning how to do things. That's the reason for the variables which begin with underscore _.

PPS. Performance is also of great concern!

P.S. Performance is also of great concern!

PS. This is for a library I am implementing for myself, as a way of learning how to do things. That's the reason for the variables which begin with underscore _.

PPS. Performance is also of great concern!

Source Link

Singly-linked list implementation with iterators

Taking into account everything you guys told me last time I posted, I came up with this implementation of a singly-linked list, in which the first and last elements are known.

I tried to make the implementation as STL-friendly as possible, so I also implemented iterators. It's my first time with those, so there might be bugs that I have missed. Also the operator-> overload for the iterators... I have no idea if what I've done is correct.

I hope someone with experience in this can share some knowledge!!

P.S. Performance is also of great concern!

#include <stdexcept>
#include <iostream>
#include <initializer_list>
#include <cstddef>
#include <iterator>
#include <utility>
#include <type_traits>

template<class T>
class slist 
{
public:     //TYPE ALIASES
    using value_type      = T;
    using size_type       = std::size_t;
    using reference       = T&;
    using const_reference = const T&;
    using pointer         = T*;
    using const_pointer   = const T*;
    
private:    //WAY TOO LONG TO HAVE IN MULTIPLE FUNCTIONS
    template<class Iterator>
    using is_correct_iterator = std::enable_if_t
    <
        std::is_same<T, typename std::iterator_traits<Iterator>::value_type>::value      &&
        std::is_base_of<std::input_iterator_tag, typename std::iterator_traits<Iterator>::iterator_category>::value
    >;

private:    //PRIVATE MEMBER VARIABLES
    struct node
    {
        T key;
        node* next;
    } *_root, *_tail;

    std::size_t _size;

public:     //ITERATOR CLASSES
    class iterator : public std::iterator<std::forward_iterator_tag, T>
    {
    protected:
        node* _node;

        friend class slist<T>;

        //only for slist<T> to access
        iterator(node* new_node) : _node(new_node) {}
    
    public:
        constexpr iterator() = default;
        constexpr iterator(const iterator&) = default;
        constexpr iterator(iterator&&)      = default;

        iterator& operator=(const iterator&) = default;
        iterator& operator=(iterator&&)      = default;

        iterator& operator++()    { _node = _node->next; return *this; }
        iterator  operator++(int) { iterator temp(_node); operator++(); return temp; }

        bool operator!=(const iterator& rhs) const { return _node != rhs._node; }
        bool operator==(const iterator& rhs) const { return _node == rhs._node; }
        bool operator>(const iterator& rhs)  const { return _node > rhs._node; }
        bool operator<(const iterator& rhs)  const { return _node < rhs._node; }
        bool operator>=(const iterator& rhs) const { return _node >= rhs._node; }
        bool operator<=(const iterator& rhs) const { return _node <= rhs._node; }

        T& operator*()  { return _node->key; }
        T* operator->() { return **this; } //no idea what im doing here
    };

    class const_iterator : public iterator
    {
    private:
        friend class slist<T>;

    public:
        using iterator::iterator; //inherit them constructors

        const_iterator& operator++()    { _node = _node->next; return *this; }
        const_iterator  operator++(int) { const_iterator temp(_node); operator++(); return temp; }

        T operator*()   const { return _node->key; }
        T* operator->() const { return **this; } //still no idea
    };

public:     //ACTUAL slist<T> CLASS
    //CONSTRUCTORS
    slist() : _root(nullptr), _tail(nullptr), _size(0) {}

    template<class Iterator, class = is_correct_iterator<Iterator>>
    slist(Iterator first, Iterator last) { assign(first, last); }

    slist(const std::size_t& count, const T& val) { assign(count, val); }

    slist(const std::initializer_list<T>& ilist) : slist(ilist.begin(), ilist.end()) {}

    slist& operator=(const std::initializer_list<T>& ilist)
    {
        assign(ilist.begin(), ilist.end());

        return *this;
    }

    //COPY/MOVE CONSTRUCTORS AND ASSIGNMENT
    slist(const slist& rhs)
        : _root(nullptr), _tail(nullptr), _size(0)
    {
        for (const auto& i : rhs)
            emplace_back(i);
    }

    slist(slist&& rhs)
        : _root(nullptr), _tail(nullptr), _size(0)
    {
        swap(rhs);
    }

    slist& operator=(const slist& rhs)
    {
        if (this == &rhs)
            return *this;

        clear();

        for (const auto& i : rhs)
            emplace_back(i);
        
        return *this;
    }

    slist& operator=(slist&& rhs)
    {
        if (this == &rhs)
            return *this;

        _root = nullptr;
        _tail = nullptr;
        _size = 0;
        swap(rhs);
        
        return *this;
    }

    //DESTRUCTOR
    ~slist() { clear(); }

    //INSERT OPERATIONS
    void push_front(T& new_key)  { emplace_front(new_key); }
    void push_front(T&& new_key) { emplace_front(std::move(new_key)); }
    
    void push_back(T& new_key)  { emplace_back(new_key); }
    void push_back(T&& new_key) { emplace_back(std::move(new_key)); }

    template<class... Args>
    void emplace_front(Args&&... args)
    {
        _root = new node{ { std::forward<Args>(args)... }, _root };

        if (!_root->next)
            _tail = _root;

        ++_size;
    }

    template<class... Args>
    void emplace_back(Args&&... args)
    {
        node* new_node = new node{ { std::forward<Args>(args)... }, nullptr };

        if (!_tail)
        {
            _tail = new_node;
            _root = new_node;
        }
        else
        {
            _tail->next = new_node;
            _tail = _tail->next;
        }
        ++_size;
    }

    void insert_after(const iterator& it, const T& new_key) { emplace_after(it, new_key); }
    void insert_after(const iterator& it, T&& new_key)      { emplace_after(it, std::move(new_key)); }

    template<class... Args>
    void emplace_after(const iterator& it, Args&&... args)
    {
        if (!it._node)
            throw std::out_of_range("List is empty! Use emplace_back() or emplace_front() instead!");

        it._node->next = new node{ { std::forward<Args>(args)... }, it._node->next };;

        ++_size;
    }

    //ASSIGN
    void assign(const std::size_t& count, const T& val)
    {
        clear();
        for (std::size_t i = 0; i < count; ++i)
            emplace_back(val);
    }

    template<class Iterator, class = is_correct_iterator<Iterator>>
    void assign(Iterator first, Iterator last)
    {
        clear();
        for (; first != last; ++first)
            emplace_back(*first);
    }

    void assign(const std::initializer_list<T>& ilist)
    {
        clear();
        assign(ilist.begin(), ilist.end());
    }

    //DELETE OPERATIONS
    void pop_front() 
    { 
        if (!_root)
            throw std::out_of_range("Can't pop from an empty list!!");

        if (_size == 1)
        {
            _root = nullptr;
            _tail = nullptr;
        }
        else
        {
            node* temp_root = _root;
            _root = _root->next;
            delete temp_root;
        }
        --_size;
    }

    void pop_back()  //O(n)
    { 
        if (!_root)
            throw std::out_of_range("Can't pop from an empty list!!");

        if (_size == 1)
        {
            _root = nullptr;
            _tail = nullptr;
        }
        else
        {
            node* before_tail = _root;

            while (before_tail->next->next)
                before_tail = before_tail->next;
            
            before_tail->next = nullptr; //so _tail = nullptr
        
            _tail = before_tail; //last node is equal to one before last

            delete before_tail->next; //delete _tail
        }
        --_size;
    }

    void erase_after(const iterator& it)
    {
        if (!_root)
            throw std::out_of_range("Can't erase from an empty list!!");
        
        node* temp = it._node->next;
        
        it._node->next = temp->next;

        delete temp;

        --_size;
    }

    void clear() 
    {
        while (!empty())
            pop_front();
    }

    //CAPACITY FUNCTIONS
    constexpr std::size_t size()  const noexcept { return _size; }
    constexpr bool        empty() const noexcept { return !_size; }
    
    //ACCESS FUNCTIONS
    T& front() { return _root->key; }
    T& back()  { return _tail->key; }

    const T& front() const { return _root->key; }
    const T& back()  const { return _tail->key; }

    //ITERATORS
    iterator begin() noexcept { return iterator(_root); }
    iterator end()   noexcept { return iterator(nullptr); }

    const_iterator begin() const noexcept { return const_iterator(const_cast<node*>(_root)); }
    const_iterator end()   const noexcept { return const_iterator(nullptr); }

    const_iterator cbegin() const noexcept { return const_iterator(_root); }
    const_iterator cend()   const noexcept { return const_iterator(nullptr); }

    //INDEXING
    T& operator[](const std::size_t& index)
    {
        if (index < 0 || index >= _size)
            throw std::out_of_range("Index out of bounds!");

        if (index == 0)
            return _root->key;

        if (index == _size - 1)
            return _tail->key;

        iterator it(_root);
        std::advance(it, index);

        return *it;
    }

    //ALGORITHMS
    void swap(slist& rhs)
    {
        std::swap(_root, rhs._root);
        std::swap(_tail, rhs._tail);
        std::swap(_size, rhs._size);
    }
};