По воводу вашего вопроса - присвоение last->next = node; делается для того, чтобы для текущего последнего элемента списка указать что есть следующий элемент (чтобы он был виден при обходе в print).
Код некорректен.
1) Нельзя отделять определения шаблонных классов от реализации. Если так делать, то всегда прийдётся включать как .h так .cpp файл. Иначе компилятор не сможет найти реализацию для конкретного шаблона.
2) Я бы не стал пихать все поля в один класс. Так, count, first и last - относятся ко всему списку, а next и prev - только к элементу списка.
3) Вывод вообще к списку не имеет никакого отношения, как и то, откуда получается элемент.
4) Есть std::list
Вот исправленный, компилирующийся код.
#ifndef LIST_H#define LIST_H#include <stddef.h>template<class T>class List{private:struct Node{ Node *prev; // Предыдущий элемент Node *next; // Следующий элемент T data; // Значение};Node *first; // Первый элементNode *last; // Последний элементsize_t total; // Число элементов в спискеpublic:List();void print() const;size_t size() const;void push_back(const T &val);};#include <iostream>using std::cout;using std::endl;template<class T>List<T>::List():first(NULL), last(NULL), total(0){}template<class T>void List<T>::push_back(const T &data){Node *node = new Node();node->data = data;node->next = NULL;if (this->last != NULL){ node->prev = this->last; this->last->next = node;}else{ node->prev = NULL; this->first = node;}this->last = node;total++;}template<class T>void List<T>::print() const{cout << "Printing list content" << endl;Node *node = this->first;while (node != NULL){ cout << node->data << endl; node = node->next;}}template<class T>size_t List<T>::size() const{return this->total;}#endif /* LIST_H */