The following is my attempt at writing a linked list that attempts to emulate the behavior of std::list. Any feedback would be appreciated.
#pragma once
#include <algorithm>
#include <cstddef>
#include <initializer_list>
#include <iterator>
#include <stdexcept>
#include <utility>
//a list is composed of links that contain pointers to previous
//and successive links
template<typename T>
struct Link {
T val;
Link<T>* prev;
Link<T>* succ;
void swap(Link* other)
{
using std::swap;
swap(val, other->val);
std::swap(prev, other->prev);
std::swap(succ, other->succ);
}
Link(const T& v = T{}, Link<T>* p = nullptr, Link<T>* s = nullptr)
:val{ v }, prev{ p }, succ{ s } {
}
};
template<typename T>
void hook(Link<T>* a, Link<T>* b)
{
a->succ = b;
b->prev = a;
}
template<typename T>
struct List_iterator {
private:
//mutable so we can increment a const_iterator
mutable Link<T>* curr;
public:
//List is a friend so that it can access curr without curr being public
template<typename T>
friend class List;
explicit List_iterator(Link<T>* link) :curr{ link } {}
T& operator*() { return curr->val; }
const T& operator*() const { return curr->val; }
Link<T>* operator->() { return curr; }
Link<T>* const operator->() const { return curr; }
//increment and decrement operators are overloaded so that
//pre- and post- increments and decrements are possible
List_iterator& operator++()
{
if (!curr) throw std::runtime_error("Cannot increment null iterator\n");
curr = curr->succ; return *this;
}
List_iterator operator++(int)
{
if (!curr) throw std::runtime_error("Cannot increment null iterator\n");
auto temp = *this;
operator++();
return temp;
}
const List_iterator& operator++() const
{
if (!curr) throw std::runtime_error("Cannot increment null iterator\n");
curr = curr->succ; return *this;
}
const List_iterator operator++(int) const
{
if (!curr) throw std::runtime_error("Cannot increment null iterator\n");
auto temp = *this;
operator++();
return temp;
}
List_iterator& operator--()
{
if (!curr) throw std::runtime_error("Cannot decrement null iterator\n");
curr = curr->prev; return *this;
}
List_iterator operator--(int)
{
if (!curr) throw std::runtime_error("Cannot decrement null iterator\n");
auto temp = *this;
operator--();
return temp;
}
const List_iterator& operator--() const
{
if (!curr) throw std::runtime_error("Cannot decrement null iterator\n");
curr = curr->prev; return *this;
}
const List_iterator operator--(int) const
{
if (!curr) throw std::runtime_error("Cannot decrement null iterator\n");
auto temp = *this;
operator--();
return temp;
}
//had to define an advance b/c std::advance wouldn't work
//even after adding an iterator tag
void advance(std::size_t n) const;
bool operator==(const List_iterator& other) const { return curr == other.curr; }
bool operator!=(const List_iterator& other) const { return curr != other.curr; }
explicit operator bool() const { return curr; }
};
template<typename T>
void List_iterator<T>::advance(std::size_t n) const
{
while (n--) operator++();
}
template<typename T>
class List;
template<typename T>
void merge(List<T>& sub_a, List<T>& sub_b, List<T>& list);
template<typename T>
void sort_helper(List<T>& list);
template<typename T>
class List {
Link<T>* first;
Link<T>* last;
std::size_t sz;
void cleanup();
List_iterator<T> insert_first_element(Link<T>* new_link);
void insert_back_unchecked(Link<T>* new_link);
void insert_front_unchecked(Link<T>* new_link);
List_iterator<T> insert_unchecked(const List_iterator<T> pos, Link<T>* new_link);
public:
using size_type = std::size_t;
using iterator = List_iterator<T>;
using const_iterator = const List_iterator<T>;
//all ctors must call default ctor first so conditions
//are set for insertion and deletion functions
List() : first{ new Link<T>{} }, last{ first }, sz{ 0 } {}
List(std::initializer_list<T>lst)
:List(lst.begin(), lst.end()) {}
//Initialize list w/ values in range [f, l)
template<typename In>
List(In f, In l);
List(const List& other)
:List(other.begin(), other.end()) {}
List& operator=(const List& other);
List(List&& other) noexcept;
List& operator=(List&& other) noexcept;
~List() { cleanup(); }
bool empty() const noexcept { return sz == 0; }
void clear() noexcept
{
if (sz == 0) return;
List temp;
swap(temp);
}
void swap(List& other) noexcept
{
std::swap(first, other.first);
std::swap(last, other.last);
std::swap(sz, other.sz);
}
size_type size() const noexcept { return sz; }
iterator begin() noexcept { return iterator(first); }
iterator end() noexcept { return iterator(last); }
const_iterator begin() const noexcept { return const_iterator(first); }
const_iterator end() const noexcept { return const_iterator(last); }
const_iterator cbegin() const noexcept { return const_iterator(first); }
const_iterator cend() const noexcept { return const_iterator(last); }
iterator insert(const_iterator p, const T& v);
iterator erase(iterator p);
T& front() { return first->val; }
T& back() { return last->prev->val; }
const T& front() const { return first->val; }
const T& back() const { return last->prev->val; }
void push_back(const T& v);
void push_front(const T& v);
template<typename... U>
iterator emplace(const_iterator pos, U&&... args);
template<typename... U>
void emplace_back(U&&... args);
template<typename... U>
void emplace_front(U&&... args);
void resize(size_type new_size, T val = T{});
//transfer elements from one list to another
void splice(const_iterator pos, List& other);
void splice(const_iterator pos, List&& other) { splice(pos, other); }
void reverse() noexcept;
//remove consecutive duplicate elements
void unique();
bool operator==(const List& other);
bool operator!=(const List& other) { return !(*this == other); }
void remove(const T& value);
template<typename Pred>
void remove_if(Pred pred);
//uses a merge sort
void sort() { sort_helper(*this); }
};
template<typename T>
template<typename In>
List<T>::List(In f, In l)
:List()
{
while (f != l) { push_back(*f++); }
}
template<typename T>
typename List<T> & List<T>::operator=(const List & other)
{
List<T> temp{ other };
swap(temp);
return *this;
}
template<typename T>
List<T>::List(List && other) noexcept
:first{ other.first }, last{ other.last }, sz{ other.sz }
{
other.first = other.last = nullptr;
other.sz = 0;
}
template<typename T>
typename List<T> & List<T>::operator=(List && other) noexcept
{
swap(other);
return *this;
}
template<typename T>
void List<T>::cleanup()
{
Link<T>* link;
while (first)
{
link = first;
first = first->succ;
delete link;
}
}
template<typename T>
typename List<T>::iterator List<T>::insert_first_element(Link<T>* new_link)
{
first = new_link;
hook(new_link, last);
++sz;
return iterator(first);
}
template<typename T>
void List<T>::insert_back_unchecked(Link<T>* new_link)
{
hook(last->prev, new_link);
hook(new_link, last);
++sz;
}
template<typename T>
void List<T>::insert_front_unchecked(Link<T>* new_link)
{
hook(new_link, first);
first = new_link;
++sz;
}
template<typename T>
List_iterator<T> List<T>::insert_unchecked(const_iterator pos, Link<T>* new_link)
{
if (pos == begin()) insert_front_unchecked(new_link);
else {
hook(pos.curr->prev, new_link);
hook(new_link, pos.curr);
++sz;
}
return iterator(new_link);
}
template<typename T>
void sort_helper(List<T> & list)
{
if (list.size() < 2) return;
auto iter_split = list.begin();
iter_split.advance(list.size() / 2);
List<T> a{ list.begin(), iter_split };
List<T> b{ iter_split, list.end() };
sort_helper(a);
sort_helper(b);
merge(a, b, list);
}
//inserts a Link with value v before p
//undefined behaviour if an invalid iterator is specified
template<typename T>
inline typename List<T>::iterator List<T>::insert(const_iterator p, const T & v)
{
Link<T>* new_link = new Link<T>{ v };
if (empty()) { return insert_first_element(new_link); }
else return insert_unchecked(p, new_link);
}
template<typename T>
typename List<T>::iterator List<T>::erase(iterator p)
{
if (empty() || p == end()) return end();
auto ret_val = p.curr->succ;
if (p.curr->prev) hook(p.curr->prev, ret_val);
else {
first = ret_val;
first->prev = nullptr;
}
delete p.curr;
--sz;
return iterator(ret_val);
}
template<typename T>
inline void List<T>::push_back(const T & v)
{
Link<T>* new_link = new Link<T>{ v };
if (empty()) insert_first_element(new_link);
else insert_back_unchecked(new_link);
}
template<typename T>
inline void List<T>::push_front(const T & v)
{
Link<T>* new_link = new Link<T>{ v };
if (empty()) insert_first_element(new_link);
else insert_front_unchecked(new_link);
}
template<typename T>
void List<T>::resize(size_type new_size, T val)
{
//store old sz b/c erase will decrement sz so the loop will execute less times than it should
const auto old_sz = sz;
for (auto i = new_size; i < old_sz; ++i) erase(--end());
for (auto i = sz; i < new_size; ++i) push_back(val);
}
template<typename T>
void List<T>::splice(const_iterator pos, List & other)
{
if (other.sz == 0) return;
if (pos.curr->prev) hook(pos.curr->prev, other.first);
else first = other.first;
hook(other.last->prev, pos.curr);
other.first = other.last = nullptr;
sz += other.sz;
other.sz = 0;
}
template<typename T>
void List<T>::reverse() noexcept
{
using std::swap;
auto b = begin();
auto e = end();
while (b != e && b != --e) { swap(*b++, *e); }
}
template<typename T>
void List<T>::unique()
{
auto iter = end();
while (iter != begin())
{
if (*iter == iter.curr->prev->val) {
iter = iterator(erase(iter).curr->prev);
}
else --iter;
}
}
template<typename T>
bool List<T>::operator==(const List & other)
{
if (sz != other.sz) return false;
auto m_begin = begin();
auto o_begin = other.begin();
while (m_begin != end())
{
if (*m_begin++ != *o_begin++) return false;
}
return true;
}
template<typename T>
void List<T>::remove(const T& value)
{
auto iter = begin();
while (iter != end())
{
if (*iter == value) iter = erase(iter);
else ++iter;
}
}
template<typename T>
void merge(List<T> & a, List<T> & b, List<T> & list)
{
auto a_iter = a.begin();
auto b_iter = b.begin();
auto list_iter = list.begin();
while (a_iter != a.end() && b_iter != b.end())
{
if (*a_iter <= *b_iter)
{
*list_iter = *a_iter;
++a_iter;
}
else
{
*list_iter = *b_iter;
++b_iter;
}
++list_iter;
}
while (a_iter != a.end()) { *list_iter++ = *a_iter++; }
while (b_iter != b.end()) { *list_iter++ = *b_iter++; }
}
template<typename T>
template<typename ...U>
inline typename List<T>::iterator List<T>::emplace(const_iterator pos, U && ...args)
{
Link<T>* new_link = new Link<T>{ T( std::forward<U>(args)... ), pos.curr->prev, pos.curr };
if (empty()) { return insert_first_element(new_link); }
else return insert_unchecked(pos, new_link);
}
template<typename T>
template<typename ...U>
inline void List<T>::emplace_back(U && ...args)
{
Link<T>* new_link = new Link<T>{ T( std::forward<U>(args)...) };
if (empty()) insert_first_element(new_link);
else insert_back_unchecked(new_link);
}
template<typename T>
template<typename ...U>
inline void List<T>::emplace_front(U && ...args)
{
Link<T>* new_link = new Link<T>{ T(std::forward<U>(args)...) };
if (empty()) insert_first_element(new_link);
else insert_front_unchecked(new_link);
}
template<typename T>
template<typename Pred>
void List<T>::remove_if(Pred pred)
{
auto iter = begin();
while (iter != end())
{
if (pred(*iter)) iter = erase(iter);
else ++iter;
}
}