Based on the previous question, I have utilized most of the answers.
- The code is now fully compatible with the STL by implementing forward iterators to store and retrieve values.
- Using raw pointers instead of smart pointers
The code is working as expected. I would like to know if my implementation is correct and how I can improve it further.
#include <iostream>
#include <iterator>
#include <algorithm>
template<typename T>
class List
{
struct Node
{
Node* next;
T value;
};
public:
// in order to make this class a fully compatible with the STL
// we need to implement iteration like in STL
class const_iterator : public std::iterator<std::forward_iterator_tag, T, std::size_t, T const*, T const&>
// Random access and bidirectional movement are not efficient in a single linked list,
// so we choose a simple forward iterator that can store and retrieve values
{
public:
// Traits(copy n paste) from STL containers
typedef std::forward_iterator_tag iterator_category;
typedef T value_type;
typedef ptrdiff_t difference_type;
typedef T const* pointer;
typedef T const& reference;
const_iterator(Node* current_node = nullptr)
: m_current_position(current_node)
{}
// operators
reference operator*()
{
return m_current_position->value;
}
pointer operator ->()
{
return std::pointer_traits<pointer>::pointer_to(**this);
}
const_iterator& operator++()
{
m_current_position = m_current_position->next;
return *this;
}
const_iterator operator++(int)
{
auto previous = *this;
++*this;
return previous;
}
bool operator ==(const_iterator const& rhs)
{
return m_current_position == rhs.m_current_position;
}
bool operator !=(const_iterator const& rhs)
{
return !(*this == rhs);
}
private:
Node* m_current_position;
};
// repeat for non-constant iterator
class iterator : public std::iterator<std::forward_iterator_tag, T, std::size_t, T*, T&>
{
public:
typedef std::forward_iterator_tag iterator_category;
typedef T value_type;
typedef ptrdiff_t difference_type;
typedef T* pointer;
typedef T& reference;
iterator(Node* current_node = nullptr)
: m_current_position(current_node)
{}
reference operator*()
{
return m_current_position->value;
}
pointer operator ->()
{
return std::pointer_traits<pointer>::pointer_to(**this);
}
iterator& operator++()
{
m_current_position = m_current_position->next;
return *this;
}
iterator operator++(int)
{
auto previous = *this;
++*this;
return previous;
}
bool operator ==(iterator const& rhs)
{
return m_current_position == rhs.m_current_position;
}
bool operator !=(iterator const& rhs)
{
return !(*this == rhs);
}
Node* m_current_position;
};
public:
List();
List(List<T> const& rhs);
~List();
iterator begin();
iterator end();
const_iterator cbegin() const;
const_iterator cend() const;
void push_front(T const& value);
void push_back(T const& value);
iterator erase(iterator it);
void clear();
void pop_front();
size_t size() const;
bool empty() const;
List& operator =(List<T> const& rhs);
private:
Node* m_head;
Node* m_tail;
std::size_t m_size;
};
template <typename T>
List<T>::List()
: m_head(nullptr), m_tail(nullptr), m_size()
{
}
template <typename T>
List<T>::List(List<T> const& rhs)
: list()
{
*this = rhs;
}
template <typename T>
List<T>::~List()
{
clear();
}
template <typename T>
typename List<T>::iterator List<T>::begin()
{
return iterator(m_head);
}
template <typename T>
typename List<T>::iterator List<T>::end()
{
return iterator(nullptr);
}
template <typename T>
typename List<T>::const_iterator List<T>::cbegin() const
{
return const_iterator(m_head);
}
template <typename T>
typename List<T>::const_iterator List<T>::cend() const
{
return const_iterator(nullptr);
}
template <typename T>
void List<T>::push_front(T const& value)
{
auto node = new Node;
node->next = m_head;
node->value = value;
m_head = node;
if (m_tail == nullptr)
{
m_tail = node;
}
m_size++;
}
template <typename T>
void List<T>::push_back(T const& value)
{
if (m_tail != nullptr)
{
auto at = iterator(m_tail);
auto node = new Node;
node->next = at.m_current_position->next;
node->value = value;
at.m_current_position->next = node;
m_size++;
if (node->next == nullptr)
{
m_tail = node;
}
}
else
{
push_front(value);
}
}
template <typename T>
typename List<T>::iterator List<T>::erase(typename List<T>::iterator it)
{
auto temp = it.m_current_position->next;
it.m_current_position->next = temp->next;
m_size--;
if (it.m_current_position->next == nullptr)
{
m_tail = it.m_current_position;
}
delete temp;
return iterator(it.m_current_position->next);
}
template <typename T>
void List<T>::clear()
{
while (!empty())
{
pop_front();
}
}
template <typename T>
void List<T>::pop_front()
{
auto temp = m_head;
m_head = temp->next;
if (m_head == nullptr)
m_tail = nullptr;
m_size--;
delete temp;
}
template <typename T>
std::size_t List<T>::size() const
{
return m_size;
}
template <typename T>
bool List<T>::empty() const
{
return m_size == 0;
}
template <typename T>
List<T>& List<T>::operator =(List<T> const& rhs)
{
clear();
for (auto it = rhs.cbegin(); it != rhs.cend(); ++it)
{
push_back(*it);
}
return *this;
}
template <typename T>
void print(List<T> const& list)
{
for (auto it = list.cbegin(); it != list.cend(); ++it)
std::cout << *it << " ";
std::cout << std::endl;
}
int main()
{
List<int> list;
// testing push_back and push_front
list.push_back(3);
list.push_back(4);
list.push_front(2);
list.push_front(1);
// testing assign contructors
List<int> list2;
list2 = list;
// testing for-range
for (const auto& i : list)
std::cout << i << ' ';
std::cout << "\n\n list size: " << list.size() << "\n\n";
// testing iterators
List<int>::iterator it;
it = list.begin();
list.erase(++it);
print(list);
it = list.begin();
*it = 10;
print(list);
std::cout << "\n\n list size: " << list.size() << "\n\n";
// testing clear
list.clear();
if (list.empty())
std::cout << "list is empty and its size: " << list.size() << std::endl;
// testing STL
std::fill(list2.begin(), list2.end(), 100);
std::copy(list2.begin(), list2.end(), std::ostream_iterator<int>(std::cout, " "));
}