Generic Doubly Linked-list implementation in C++ I am just learning about C++ templates and generic types, I decided it would be nice to try to create a generic container class as a challenge and test my knowledge in the process. Here is what I have come up with.

#include &lt;iostream&gt;
#include &lt;cstdlib&gt;
#include &lt;cassert&gt;

template &lt;typename __T&gt;

class List{
private:
    struct node{
        __T data;
        node* next;
        node* prev;
    };
    typedef long long unsigned int c_size;
    node* head;
    node* tail;
    node* current;
    c_size m_S;

public:
    List()
        :head{NULL},tail{NULL},current{NULL},m_S{0}
    {}

    List(std::initializer_list&lt;__T&gt; __l)
        :head{NULL},tail{NULL},current{NULL},m_S{0}
    {
        for(auto const&amp; elem:__l){
            insert_back(elem);
        }
    }
    void insert_front(__T _d){
        ++m_S;
        node* new_node{new node};
        new_node-&gt;data = _d;
        new_node-&gt;next = head;
        new_node-&gt;prev = NULL;

        if (head != NULL){
            head-&gt;prev = new_node;
        }
        else
        {
            tail = new_node;
        }
        head = new_node;

    }

    void print(){
        if (m_S == 0) return;
        node *current = head;
        while (current-&gt;next != NULL){
            std::cout &lt;&lt; current-&gt;data &lt;&lt; &quot;, &quot;;
            current = current-&gt;next;
        }
        std::cout &lt;&lt; current-&gt;data &lt;&lt; '\n';
    }

    void insert_back(__T _d){
        ++m_S;
        node* new_node = new node;
        new_node-&gt;data = _d;
        new_node-&gt;next = NULL;
        node* last = head;

        if (head == NULL) {
            new_node-&gt;prev = NULL;
            head = new_node;
            tail = new_node;
        }
        else{
            tail-&gt;next = new_node;
            new_node-&gt;prev = tail;
            tail = new_node;
        }
    }

    constexpr __T first(){
        assert(head != NULL &amp;&amp; &quot;Calling .front() on an empty list&quot;);
        return head-&gt;data;
    }
    constexpr __T last(){
        assert(tail != NULL &amp;&amp; &quot;Calling. last() on an empty list&quot;);
        return tail-&gt;data;
    }


    constexpr c_size size(){return m_S;}


    void remove_all(){
        current = head;
        while(current-&gt;next != NULL){
            current = current-&gt;next;
            delete current;
        }
        head = NULL;
        tail = NULL;
        current = NULL;
    }

};



Thanks in advance !
Identifiers that start with a double underscore, or a single underscore followed by a capital letter, are reserved for use by the implementation. Your use of __T could conflict with this use.
Spacing is important. The blank line between template &lt;typename __T&gt; and the following class List line makes it harder to recognize the class as a template class.
What is the purpose of the current member? It is unnecessary.
c_size should probably be std::size_t, not unsigned long long. Because it is a private typedef, users of your class cannot access the type and can only declare a variable to hold the returned value using auto.
You don't declare the copy constructor, copy assignment operator, move constructor, nor move assignment operators. This will cause problems with assignments of one List object to another.
insert_head and insert_back are very similar functions, but have been written somewhat differently. The implementation for insert_back can be made shorter by making it similar to insert_head. In particular, the assignment to new_node-&gt;prev can be done before the if (using new_node-&gt;prev = tail;, because when head is NULL, tail should also be), while the common tail = new_node; can be done after.
first, last, and size should also have the const modifier (e.g., constexpr c_size size() const.
remove_all will crash because you access current-&gt;next after delete current, nor do you delete the first (head) element.