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!!
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!
#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);
}
};