this is an update of the implementation after I followed the feedback from Iterator for traversing a tree. After that I did changed quite a bit on the design and decided to ask for another round of review. Hope this is allowed.
First is on what I want to achieve and the tests I made to ensure it is correct. For this, please help review to see if the test cases are good.
main.cpp
#include "tree.h"
#include <cassert>
#include <exception>
#include <iostream>
void traverse(Tree<std::string> root) {
for (auto cur : root) {
auto parent = cur.GetParent();
std::cout << "traversed from " << (parent ? parent->val : "NULL") << " to " << cur.val << std::endl;
}
}
int main() {
Tree<std::string> tA = Tree<std::string>("A");
Tree<std::string> tB = Tree<std::string>("B", &tA);
Tree<std::string> tC = Tree<std::string>("C", &tA);
Tree<std::string> tD = Tree<std::string>("D", &tB);
Tree<std::string> tE = Tree<std::string>("E", &tB);
Tree<std::string> tF = Tree<std::string>("F", &tB);
traverse(tA);
try {
std::next(std::next(tF.begin()));
} catch (std::exception& e) {
std::cout << e.what() << std::endl;
}
auto it = tA.begin();
assert(it->val == "A");
++it;
assert(it->val == "C");
auto it2 = it++;
assert(it->val == "B");
assert(it2->val == "C");
assert(it == tB.begin());
assert(it2 == tC.begin());
assert(it != it2);
auto newNode = Tree<std::string>("A");
assert(newNode != tA);
return 0;
}
What this iterator does is to iterate through its subtree. I hope to get a critical review of the design of the interface.
tree.h
#include <iterator>
#include <stack>
#include <vector>
template <typename T>
class Tree {
public:
class iterator {
public:
using iterator_category = std::forward_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = T*;
using reference = T&;
public:
iterator() = default;
iterator(Tree<T>* root);
Tree<T>& operator*() const;
Tree<T>* operator->() const;
iterator& operator++();
iterator const operator++(int);
bool operator==(iterator const& other) const;
bool operator!=(iterator const& other) const;
Tree<T>* cur;
private:
std::stack<Tree<T>*> s_;
};
public:
explicit Tree(T const& val, Tree<T>* parent = nullptr);
Tree<T>* GetParent() const;
std::vector<Tree<T>*> const GetChildren() const;
iterator begin();
iterator end();
bool operator==(Tree<T> const& root) const;
bool operator!=(Tree<T> const& root) const;
T val;
private:
Tree<T>* parent_ = nullptr;
std::vector<Tree<T>*> children_;
};
If you have little time, you can skip the review on the implementation, but I will attach it here as well.
Also tree.h, under the snippet above.
template <typename T>
Tree<T>::iterator::iterator(Tree<T>* root)
: cur(root) {}
template <typename T>
Tree<T>& Tree<T>::iterator::operator*() const {
return *cur;
}
template <typename T>
Tree<T>* Tree<T>::iterator::operator->() const {
return cur;
}
template <typename T>
typename Tree<T>::iterator& Tree<T>::iterator::operator++() {
if (cur == nullptr) {
throw std::out_of_range("No more nodes for traversal");
}
for (auto& child : cur->children_) {
s_.push(child);
}
if (s_.empty()) {
cur = nullptr;
} else {
cur = s_.top(); s_.pop();
}
return *this;
}
template <typename T>
typename Tree<T>::iterator const Tree<T>::iterator::operator++(int) {
Tree<T>::iterator tmp(*this);
++*this;
return tmp;
}
template <typename T>
bool Tree<T>::iterator::operator==(Tree<T>::iterator const& other) const {
return *cur == *other.cur;
}
template <typename T>
bool Tree<T>::iterator::operator!=(Tree<T>::iterator const& other) const {
return !(cur == other.cur);
}
template <typename T>
Tree<T>::Tree(T const& val, Tree<T>* const parent)
: val(val), parent_(parent) {
if (parent) {
parent->children_.push_back(this);
}
}
template <typename T>
Tree<T>* Tree<T>::GetParent() const {
return parent_;
}
template <typename T>
std::vector<Tree<T>*> const Tree<T>::GetChildren() const {
return children_;
}
template <typename T>
typename Tree<T>::iterator Tree<T>::begin() {
return iterator(this);
}
template <typename T>
typename Tree<T>::iterator Tree<T>::end() {
return iterator();
}
template <typename T>
bool Tree<T>::operator==(Tree<T> const& other) const {
return val == other.val &&
parent_ == other.parent_ &&
children_ == other.children_;
}
template <typename T>
bool Tree<T>::operator!=(Tree<T> const& other) const {
return !(*this == other);
}
This is compiled using
g++ main.cc -o tree -std=c++17 -Wall -static -O3 -m64 -lm
Finally, even if you don't have the answer to the design, I still hope you can throw me some questions such that I can ponder them.