Doubly linked std::list simple implementation I've implemented a simple doubly linked list structure. I would appreciate all criticism relevant to code, style, flow, and so forth.

How to implement end iterator and pop_back/pop_front properly? Where memory may leak?

P.S.I didn't make it template, because this is simple version version.

#include &lt;cstddef&gt;
#include &lt;iterator&gt;

using T = int;

class List {
/// Nodes
public:
    class Node {
    public:
        T data;
        Node *next;
        Node *prev;

    public:
        explicit Node(Node *next_, Node *prev_) : next(next_), prev(prev_) {}

        explicit Node(const T &amp;_data, Node *next_, Node *prev_) :
                data(_data), next(next_), prev(prev_) {}

        ~Node() {}
    };
///

/// Iterators
public:
    class Iterator : public std::iterator&lt;std::bidirectional_iterator_tag, T&gt; {
    public:
        Node *position;
        Node *tail;

    public:
        Iterator(Node *node) : position(node) {}

        Iterator(Node *node, Node *_tail) : position(node), tail(_tail) {
        }

        Iterator &amp;operator++() noexcept {
            position = position-&gt;next;
            return *this;
        }

        Iterator operator++(int) noexcept {
            auto old = *this;
            position = position-&gt;next;
            return old;
        }

        Iterator &amp;operator--() noexcept {
            if (position == nullptr) position = tail;
            else
                position = position-&gt;prev;
            return *this;
        }

        Iterator operator--(int) noexcept {
            auto old = *this;
            if (position == nullptr) position = tail;
            else
                position = position-&gt;prev;
            return old;
        }

        T &amp;operator*() const noexcept {
            return (position-&gt;data);
        }

        bool operator!=(Iterator other) noexcept {
            return position != other.position;
        }

        bool operator==(Iterator other) noexcept {
            return position == other.position;
        }


        ~Iterator() {}
    };
///

/// list
private:
    Node *head = nullptr;
    Node *tail = nullptr;
    size_t size_ = 0;

public:
    List() {}

    void push_back(const T &amp;_data) {
        Node *node = new Node(_data, nullptr, tail);
        if (tail != nullptr) {
            node-&gt;prev = tail;
            tail-&gt;next = node;
            tail = node;
        } else {
            head = tail = node;
        }
        ++size_;
    }

    void push_front(const T &amp;_data) {
        Node *node = new Node(_data, head, nullptr);
        if (head != nullptr) {
            head-&gt;prev = node;
            node-&gt;next = head;
            head = node;
        } else {
            head = tail = node;
        }
        ++size_;
    }

    void pop_back() {
        tail = tail-&gt;prev;
        if (tail != nullptr) {
            tail-&gt;next = nullptr;
        }
        --size_;
    }

    void pop_front() {
        head = head-&gt;next;
        if (head != nullptr) {
            head-&gt;prev = nullptr;
        }
        --size_;
    }


    size_t size() {
        return size_;
    }

    size_t size() const {
        return size_;
    }

    Iterator begin() {
        return Iterator(head, tail);
    }

    Iterator begin() const {
        return Iterator(head, tail);
    }

    Iterator end() {
        Iterator iter(nullptr, tail);
        return iter;
    }

    Iterator end() const {
        Iterator iter(nullptr, tail);
        return iter;
    }

    bool empty() const {
        return head == nullptr;
    }

    ~List() {
        Node * tmp = nullptr;
        while (head)
        {
            tmp = head;
            head = head-&gt;next;
            delete tmp;
        }
        head = tail = nullptr;
    }
};
Take care to be explicit about compiler generated functions

Effectively your List class violates the Rule of Three/Five/Zero, thus the compiler generated copy/move constructors will create unexpected side effects by implementing a shallow copy mechanism.

Let's suppose you have code like 

int main() {
    List l1;
    List l2;

    l1.push_back(5);
    l1.push_back(42);
    l1.push_back(21);

    l2 = l1; // Copy the list
    *l1.begin() = 80; // Change the original list

    for(auto value : l2) {
        std::cout &lt;&lt; value &lt;&lt; std::endl;
    }
}


The output is 

80
42
21


I doubt you really intended that. See the Live Demo please.

Why not making your class a template?

using T = int;


looks strange.

Why not making the List class a template like

template&lt;typename T&gt;
class List {
    // ...
};


Compiles and works the same way here

Prefer smart pointers instead of raw pointers

You should prefer using smart pointers instead of managing new and delete yourself.

I have evolved the demo from above with changing the relevant parts

class Node;
class Node {
public:
    T data;
    std::shared_ptr&lt;Node&gt; next;
    std::shared_ptr&lt;Node&gt; prev;

public:
    explicit Node(std::shared_ptr&lt;Node&gt; next_, std::shared_ptr&lt;Node&gt; prev_) : next(next_), prev(prev_) {}

    explicit Node(const T &amp;_data, std::shared_ptr&lt;Node&gt; next_, std::shared_ptr&lt;Node&gt; prev_) :
            data(_data), next(next_), prev(prev_) {}

    ~Node() {}
};




class Iterator : public std::iterator&lt;std::bidirectional_iterator_tag, T&gt; {
public:
    std::weak_ptr&lt;Node&gt; position;
    std::weak_ptr&lt;Node&gt; tail;

public:
    Iterator(std::shared_ptr&lt;Node&gt; node) : position(node) {}

    Iterator(std::shared_ptr&lt;Node&gt;node, std::shared_ptr&lt;Node&gt;_tail) : position(node), tail(_tail) {
    }

    Iterator &amp;operator++() noexcept {
        position = position.lock()-&gt;next;
        return *this;
    }

    Iterator operator++(int) noexcept {
        auto old = *this;
        position = position-&gt;next;
        return old;
    }

    Iterator &amp;operator--() noexcept {
        if (position == nullptr) position = tail;
        else
            position = position-&gt;prev;
        return *this;
    }

    Iterator operator--(int) noexcept {
        auto old = *this;
        if (position == nullptr) position = tail;
        else
            position = position-&gt;prev;
        return old;
    }

    T &amp;operator*() const noexcept {
        return (position.lock()-&gt;data);
    }

    bool operator!=(Iterator other) noexcept {
        return position.lock().get() != other.position.lock().get();
    }

    bool operator==(Iterator other) noexcept {
        return position.lock().get() == other.position.lock().get();
    }


    ~Iterator() {}
};


Solve the shallow copy problem

As mentioned in my 1st point you have a problem with The Rule of Three/Five. Your List class will be shallow copied. So you should provide the relevant operations.
This will be a lot easier using smart pointers as mentioned in my previous point

List(const List&lt;T&gt;&amp; rhs) {
    copy(rhs);
}

List(List&lt;T&gt;&amp;&amp; rhs) {
    move(std::move(rhs));
}

List&amp; operator=(const List&lt;T&gt;&amp; rhs) {
    copy(rhs);
    return *this;
}

List&amp; operator=(List&lt;T&gt;&amp;&amp; rhs) {
    move(std::move(rhs));
    return *this;
}


void copy(const List&lt;T&gt;&amp; rhs) {
    clear();
    for(auto value : rhs) {
        push_back(value);
    }
}

void move(List&lt;T&gt;&amp;&amp; rhs) {
    head = std::move(rhs.head);
    tail = std::move(rhs.tail);
}

void clear() {
    std::shared_ptr&lt;Node&gt; temp = head;
    std::shared_ptr&lt;Node&gt; next;
    do {
        if(temp) {
            next = temp-&gt;next;
        }
        temp = nullptr;
    } while(next);
}

~List() {
}


Output is as expected for the example mentioned in the 1st paragraph:

5
42
21


Again you can check the improved code here.