I'm working on a stack implementation using a linked list, but I have a strong feeling that I overcomplicated my solution. I would appreciate it if you review this code and give me any suggestions on code and style.
#pragma once
#include <functional>
#include <iostream>
#include <type_traits>
#include <stdexcept>
template <typename T>
class StackList
{
private:
class Node
{
T data;
Node * next;
Node(Node * next, const T & data) :next(next), data(data) {};
friend class StackList<T>;
};
int size_ = 0;
Node * head_ = nullptr;
Node * tail_ = nullptr;
void AddToTail(T& data);
public:
StackList() = default;
StackList(StackList & other);
StackList(StackList && other);
StackList & operator=(StackList & other);
StackList & operator=(StackList && other);
~StackList() { EmptyList(); }
void EmptyList();
void push(const T & data);
T pop();
const T& operator[](int count) const;
T& operator[](int count) { return const_cast<T &>(static_cast<const StackList &>(*this).operator[](count)); };
int size() { return size_; }
void Traverse(std::function<void(T&)> lamda) const;
void Traverse(std::function<void(T&)> lamda){ (static_cast<const StackList &>(*this).Traverse(lamda)); }
template <typename T>
friend std::ostream & operator<<(std::ostream & os, StackList<T> & stack);
};
template <typename T>
void StackList<T>::AddToTail(T& data)
{
if (head_ == nullptr)
head_ = tail_ = new Node(nullptr, data);
else
{
tail_->next = new Node(nullptr, data);
tail_ = tail_->next;
}
}
template <typename T>
StackList<T>::StackList(StackList & other)
{
std::function<void(T&)> lamda = [&](T& data) {this->AddToTail(data); this->size_++; };
other.Traverse(lamda);
}
template <typename T>
StackList<T>::StackList(StackList && other) : head_(other.head_), tail_(other.tail_), size_(other.size_)
{
other.head_ = 0;
other.tail_ = 0;
other.size_ = 0;
}
template <typename T>
StackList<T> & StackList<T>::operator=(StackList<T> & other)
{
if (this != &other)
{
if (other.size_ == 0)
EmptyList();
else
{
if (size_ >= other.size_)
{
Node * current = head_;
std::function<void(T&)> lamda = [&](T& data) {current->data = data; tail_ = current; current = current->next; };
other.Traverse(lamda);
while (current != nullptr)
{
Node * save = current->next;
delete current;
current = save;
}
}
else
{
Node * current = other.head_;
std::function<void(T&)> lamda = [&](T& data) {data = current->data; current = current->next; };
Traverse(lamda);
while (current != nullptr)
{
AddToTail(current->data);
current = current->next;
}
}
tail_->next = nullptr;
size_ = other.size_;
}
}
return *this;
}
template <typename T>
StackList<T> & StackList<T>::operator=(StackList<T> && other)
{
if (this != &other)
{
head_ = other.head_;
tail_ = other.tail_;
size_ = other.size_;
other.head_ = 0;
other.tail_ = 0;
other.size_ = 0;
}
return *this;
}
template <typename T>
const T& StackList<T>::operator[](int count) const
{
if (count > size_ - 1 && count < 0)
throw std::invalid_argument("Out of range index!");
Node * search = head_;
for (int i = 0; i < count; i++)
search = search->next;
return search->data;
}
template <typename T>
void StackList<T>::EmptyList()
{
while (head_ != nullptr)
pop();
}
template <typename T>
void StackList<T>::push(const T & data)
{
head_ = new Node(head_, data);
if (tail_ == nullptr)
tail_ = head_;
size_++;
}
template <typename T>
T StackList<T>::pop()
{
if (size_ > 0)
{
T retval = head_->data;
Node * temp = head_->next;
delete head_;
head_ = temp;
size_--;
if (size_ == 0)
head_ = tail_ = nullptr;
return retval;
}
else
{
throw std::invalid_argument("Pop of empty list");
}
}
template <typename T>
void StackList<T>::Traverse(std::function<void(T&)> lamda) const
{
Node * cur = head_;
while (cur != nullptr)
{
lamda(cur->data);
cur = cur->next;
}
}
template <typename T>
std::ostream & operator<<(std::ostream & os, StackList<T> & stack)
{
std::function<void(T&)> lamda = [&](T& data) { os << data << std::endl; };
std::ios_base::fmtflags f(os.flags());
os << "Stack of " << typeid(T).name() << ", size = " << stack.size() << std::endl;
stack.Traverse(lamda);
os.flags(f);
return os;
}