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;
    }
}

