Transforming trees in C++ - c++

I have an heterogeneous n-ary tree made by different kind of nodes, for example:
class Node { }
class Subnode1 : public Node
{
}
class Subnode2 : public Node
{
private:
unique_ptr<Subnode1> child;
public:
Subnode1* getChild() { return child.get(); }
}
class Subnode4 : public Subnode2 { }
class Subnode3 : public Node
{
private:
list<unique_ptr<Subnode2>> children;
public:
// getter
}
// etc
The structure could contain any amount of types of nodes and it will be extended in the future.
I implemented a way to visit the tree which allows me to customize what to do when each node is visited which is basically implemented in this way:
void genericVisit(Node* node) {
if (dynamic_cast<Subnode2>(node))
visit(static_cast<Subnode2*>(node));
// etc
}
virtual void visit(Subnode2* node) {
if (node->getChild())
genericVisit(node->getChild());
}
// etc
It works nicely to traverse the tree but now I have the requirement to be able to replace subtrees with other subtrees so I'm thinking about the best approach to follow without altering the structure too much.
The best solution would have been to let getter return directly unique_ptr<Subnode2>& so that I do the visit on the unique pointers and I'm able to change the content of the smart pointer while visiting it. This would work but unique_ptr is not polymorphic, and there is no way to dispatch the call to the most specialized method, eg:
void genericVisit(unique_ptr<Node>& node) { // NON WORKING CODE
if (dynamic_cast<unique_ptr<Subnode2>&>(node))
visit(static_cast<unique_ptr<Subnode2>&>(node));
// etc
}
So I was wondering which could be the best solution to the problem, minding that while traversing the tree, as long as I change the content of the current subtree without changing the reference to that node in the parent, (which would be allowed by using an unique_ptr) I don't see any problem.

Because I thought a comment was not going to be enough to convey all that information:
#Jack you can (Sean Parent: should) use shared_pointer<const Node> there, and copy as required for changes. Immutability for the win! That said, perhaps you can do without the inheritance altogether? Make things simpler and potentially more efficient – sehe 6 hours ago
I thought I'd demonstrate both aspects a little, in reverse order:
On using static polymorphism for the trees: let's define a tree where leaf nodes can be strings or userdefined structs:
struct Data {
double x,y,z;
};
using Node = make_recursive_variant<std::string, Data, std::vector<recursive_variant_> >::type;
using Nodes = std::vector<Node>;
The sample tree used in all the tests below is now defined as:
Node tree = Nodes {
"hello",
Data { 1,2,3 },
Nodes {
"more nested",
Nodes {
Data { 2,3,4 },
Data { 3,4,5 },
Data { 4,5,6 },
},
"nodes"
}
};
Boost Variant lends itself very naturally for transformation via visitation. For example, let's write a visitor that reverses all nodes in the tree, so including the strings and Data{x,y,z} -> Data{z,y,x}:
struct reverser : static_visitor<Node> {
Node operator()(Node const& tree) const { return apply_visitor(*this, tree); }
Node operator()(std::string const& s) const { return std::string(s.rbegin(), s.rend()); }
Node operator()(Data const& d) const { return Data {d.z, d.y, d.x}; }
Node operator()(Nodes const& children) const {
Nodes revchildren;
std::transform(children.rbegin(), children.rend(), back_inserter(revchildren), *this);
return revchildren;
}
};
Node reverse(Node const& tree) {
return reverser()(tree);
}
See the simplified demo at this stage Live On Coliru
I added a print visitor so you can see what the result is
It even roundtrip tests the tree (tree == reverse(reverse(tree)))
Now you'll notice that the trees under 1. have value semantics, meaning that the reverse operation results in a completely separate copy of the tree.
I've went on and added a similar SharedTree:
using SNode = boost::shared_ptr<
boost::make_recursive_variant<
std::string, Data, std::vector<boost::shared_ptr<boost::recursive_variant_>
> >::type>;
using Node = SNode::element_type;
using Nodes = std::vector<SNode>;
When you manipulate copies of these trees, both copies change because they share nodes (the demo program below adds a node to one SharedTree and observes that the other also changes)
Now comes the Sean Parent approach: using shared_ptr<T const>
namespace CowTree {
using SNode = boost::shared_ptr<
boost::make_recursive_variant<
std::string, Data, std::vector<boost::shared_ptr<boost::recursive_variant_ const>
> >::type const>;
using Node = SNode::element_type;
using Nodes = std::vector<SNode>;
}
The beauty of this is that it's cheap to make copies, but you cannot possibly modify a node: you always have to deep-copy a subtree to modify any aspect of it's value. To demonstrate this, I've made a transformation that just uppercases string leaf nodes, leaving everything else unchanged:
template <typename SNode, typename Nodes = std::vector<SNode> >
struct upper_caser : boost::static_visitor<SNode> {
SNode operator()(SNode const& tree) const {
return apply_visitor(boost::bind(*this, boost::ref(tree), _1), *tree);
}
// dispatch
SNode operator()(SNode const&, std::string const& value) const {
std::string xformed;
std::transform(value.begin(), value.end(), back_inserter(xformed), [](uint8_t c) { return std::toupper(c); });
return boost::make_shared<typename SNode::element_type>(xformed);
}
SNode operator()(SNode const& node, Nodes const& children) const {
Nodes xformed; // TODO optimize
std::transform(children.begin(), children.end(), back_inserter(xformed), *this);
return (equal(children, xformed))
? node
: boost::make_shared<typename SNode::element_type>(xformed);
}
template <typename V> SNode operator()(SNode const& node, V const&) const {
return node;
}
};
template <typename SNode>
SNode ucase(SNode const& tree) {
return upper_caser<SNode>()(tree);
}
As you can see the transformation is completely generic and will work with the SharedTree just as well as the CowTree - because no value is ever mutated. Obviously, using the CowTree is much safer because it guarantees immutability.
The test program actively checks that
the original cow tree doesn't change when we get it's ucased transform.
it also checks that the subtrees that haven't changed, are indeed shared (haven't been cloned)
Full Program
Live On Coliru
#include <boost/variant.hpp>
namespace Tree {
struct Data {
double x,y,z;
};
using Node = boost::make_recursive_variant<std::string, Data, std::vector<boost::recursive_variant_> >::type;
using Nodes = std::vector<Node>;
namespace Operations {
struct reverser : boost::static_visitor<Node> {
Node operator()(Node const& tree) const { return apply_visitor(*this, tree); }
Node operator()(Data const& d) const { return Data {d.z, d.y, d.x}; }
Node operator()(std::string const& s) const { return std::string(s.rbegin(), s.rend()); }
Node operator()(Nodes const& children) const {
Nodes revchildren;
std::transform(children.rbegin(), children.rend(), back_inserter(revchildren), *this);
return revchildren;
}
};
Node reverse(Node const& tree) {
return reverser()(tree);
}
}
using Operations::reverse;
}
// For our demo, let's implement `operator <<` with a manipulator
#include <iostream>
namespace IO {
namespace detail {
using namespace boost;
// this pretty prints the tree as C++ initializer code
struct print_visitor : static_visitor<void> {
print_visitor(std::ostream& os, std::string const& indent = "\n") : _os(os), _indent(indent) {}
// concrete types
void operator()(std::string const& s) const { _os << '"' << s << '"'; }
void operator()(Tree::Data const& d) const { _os << "Data {" << d.x << "," << d.y << "," << d.z << '}'; }
// generics to cater for both direct and shared tree nodes
template <typename T> void operator()(shared_ptr<T> const& sp) const { (*this)(*sp); }
template <typename T> void operator()(shared_ptr<const T> const& sp) const { (*this)(*sp); }
template <typename... Ts> void operator()(variant<Ts...> const& tree) const { apply_visitor(*this, tree); }
template <typename Node>
void operator()(std::vector<Node> const& children) const {
_os << "Nodes {";
print_visitor subnode(_os, _indent + " ");
for(auto& n : children) {
_os << subnode._indent;
subnode(n);
_os << ",";
}
_os << _indent << '}';
}
private:
std::ostream& _os;
mutable std::string _indent;
};
template <typename NodeType>
struct print_manip {
print_manip(NodeType const& n) : _node(n) {}
friend std::ostream& operator<<(std::ostream& os, print_manip const& m) {
return print_visitor(os)(m._node), os << ";";
}
private:
NodeType const& _node;
};
}
template <typename NodeType>
detail::print_manip<NodeType> print(NodeType const& node) {
return node;
}
}
#include <boost/make_shared.hpp>
namespace Support {
namespace detail {
template <typename SNode, typename Nodes = std::vector<SNode> >
struct share_visitor : boost::static_visitor<SNode> {
SNode operator()(Tree::Node const& tree) const { return apply_visitor(*this, tree); }
SNode operator()(Tree::Nodes const& children) const {
Nodes shared;
std::transform(children.begin(), children.end(), back_inserter(shared), *this);
return boost::make_shared<typename SNode::element_type>(shared);
}
template <typename LeafNode>
SNode operator()(LeafNode const& v) const { return boost::make_shared<typename SNode::element_type>(v); }
};
}
}
#include <boost/bind.hpp>
namespace SharedTree {
using Tree::Data;
using SNode = boost::shared_ptr<
boost::make_recursive_variant<
std::string, Data, std::vector<boost::shared_ptr<boost::recursive_variant_>
> >::type>;
using Node = SNode::element_type;
using Nodes = std::vector<SNode>;
namespace Operations {
template <typename SNode, typename Nodes = std::vector<SNode> >
struct upper_caser : boost::static_visitor<SNode> {
SNode operator()(SNode const& tree) const {
return apply_visitor(boost::bind(*this, boost::ref(tree), _1), *tree);
}
// dispatch
SNode operator()(SNode const&, std::string const& value) const {
std::string xformed;
std::transform(value.begin(), value.end(), back_inserter(xformed), [](uint8_t c) { return std::toupper(c); });
return boost::make_shared<typename SNode::element_type>(xformed);
}
SNode operator()(SNode const& node, Nodes const& children) const {
Nodes xformed; // TODO optimize
std::transform(children.begin(), children.end(), back_inserter(xformed), *this);
return (equal(children, xformed))
? node
: boost::make_shared<typename SNode::element_type>(xformed);
}
template <typename V> SNode operator()(SNode const& node, V const&) const {
return node;
}
};
template <typename SNode>
SNode ucase(SNode const& tree) {
return upper_caser<SNode>()(tree);
}
}
using Operations::ucase;
SNode share(Tree::Node const& tree) {
return Support::detail::share_visitor<SNode>()(tree);
}
}
namespace CowTree {
using Tree::Data;
using SNode = boost::shared_ptr<
boost::make_recursive_variant<
std::string, Data, std::vector<boost::shared_ptr<boost::recursive_variant_ const>
> >::type const>;
using Node = SNode::element_type;
using Nodes = std::vector<SNode>;
SNode share(Tree::Node const& tree) {
return Support::detail::share_visitor<SNode>()(tree);
}
}
#include <boost/lexical_cast.hpp> // for the roundtrip test
namespace Support {
template <typename NodeType1, typename NodeType2>
bool tree_equal(NodeType1 const& a, NodeType2 const& b) {
using IO::print;
return boost::lexical_cast<std::string>(print(a)) ==
boost::lexical_cast<std::string>(print(b));
}
}
int main() {
using namespace Tree;
using IO::print;
using Support::tree_equal;
Node tree = Nodes {
"hello",
Data { 1,2,3 },
Nodes {
"more nested",
Nodes {
Data { 2,3,4 },
Data { 3,4,5 },
Data { 4,5,6 },
},
"nodes"
}
};
std::cout << "Before transformation: \n" << print(tree) << "\n";
std::cout << "After transformation: \n" << print(reverse(tree)) << "\n";
Node roundtrip = reverse(reverse(tree));
std::cout << "Roundtrip tree_equal: " << std::boolalpha << tree_equal(tree, roundtrip) << "\n";
std::cout << "//////////////////////////////////////////////////\n";
std::cout << "// manipulate SharedTree \"copies\"\n";
auto shared = SharedTree::share(tree);
std::cout << "Shared: " << print(shared) << "\n";
std::cout << "Equal to source: " << tree_equal(tree, shared) << "\n";
auto shared2 = shared;
using boost::get;
using boost::make_shared;
get<SharedTree::Nodes>(*shared).push_back(make_shared<SharedTree::Node>("added to a shared tree"));
std::cout << "Shared2 after changing shared: " << print(shared2) << "\n";
std::cout << "Shared trees equal: " << tree_equal(shared, shared2) << "\n";
std::cout << "Not equal to source: " << tree_equal(tree, shared) << "\n";
std::cout << "//////////////////////////////////////////////////\n";
std::cout << "// now let's see about CowTree\n";
auto cow = CowTree::share(tree);
std::cout << "Cow: " << print(cow) << "\n";
std::cout << "Equal to source: " << tree_equal(tree, cow) << "\n";
auto ucased = SharedTree::ucase(cow);
std::cout << "Ucased: " << print(ucased) << "\n";
std::cout << "Equal to cow source: " << tree_equal(ucased, cow) << "\n";
/*
* The
*
* Nodes {
* Data { 2,3,4 },
* Data { 3,4,5 },
* Data { 4,5,6 },
* },
*
* subtree should still be shared, because it wasn't touched:
*/
std::cout << "Subtree from ucased: " << print(get<CowTree::Nodes>(*get<CowTree::Nodes>(*ucased)[2])[1]) << "\n";
std::cout << "Subtree from cow: " << print(get<CowTree::Nodes>(*get<CowTree::Nodes>(*cow )[2])[1]) << "\n";
std::cout << "Subtrees match: " << tree_equal(
get<CowTree::Nodes>(*get<CowTree::Nodes>(*ucased)[2])[1],
get<CowTree::Nodes>(*get<CowTree::Nodes>(*cow) [2])[1])
<< "\n";
// unchanged nodes should be shared:
std::cout << "Subtrees shared: " << (
get<CowTree::Nodes>(*get<CowTree::Nodes>(*ucased)[2])[1].get() ==
get<CowTree::Nodes>(*get<CowTree::Nodes>(*cow) [2])[1].get())
<< "\n";
// changed nodes aren't shared:
std::cout << "Siblings unshared: " << (
get<CowTree::Nodes>(*get<CowTree::Nodes>(*ucased)[2])[2].get() !=
get<CowTree::Nodes>(*get<CowTree::Nodes>(*cow) [2])[2].get())
<< "\n";
std::cout << "Parents unshared: " << (
get<CowTree::Nodes>(*ucased)[2].get() !=
get<CowTree::Nodes>(*cow) [2].get())
<< "\n";
std::cout << "Roots unshared: " << ( ucased.get() != cow.get())
<< "\n";
}
Output:
Before transformation:
Nodes {
"hello",
Data {1,2,3},
Nodes {
"more nested",
Nodes {
Data {2,3,4},
Data {3,4,5},
Data {4,5,6},
},
"nodes",
},
};
After transformation:
Nodes {
Nodes {
"sedon",
Nodes {
Data {6,5,4},
Data {5,4,3},
Data {4,3,2},
},
"detsen erom",
},
Data {3,2,1},
"olleh",
};
Roundtrip tree_equal: true
//////////////////////////////////////////////////
// manipulate SharedTree "copies"
Shared: Nodes {
"hello",
Data {1,2,3},
Nodes {
"more nested",
Nodes {
Data {2,3,4},
Data {3,4,5},
Data {4,5,6},
},
"nodes",
},
};
Equal to source: true
Shared2 after changing shared: Nodes {
"hello",
Data {1,2,3},
Nodes {
"more nested",
Nodes {
Data {2,3,4},
Data {3,4,5},
Data {4,5,6},
},
"nodes",
},
"added to a shared tree",
};
Shared trees equal: true
Not equal to source: false
//////////////////////////////////////////////////
// now let's see about CowTree
Cow: Nodes {
"hello",
Data {1,2,3},
Nodes {
"more nested",
Nodes {
Data {2,3,4},
Data {3,4,5},
Data {4,5,6},
},
"nodes",
},
};
Equal to source: true
Ucased: Nodes {
"HELLO",
Data {1,2,3},
Nodes {
"MORE NESTED",
Nodes {
Data {2,3,4},
Data {3,4,5},
Data {4,5,6},
},
"NODES",
},
};
Equal to cow source: false
Subtree from ucased: Nodes {
Data {2,3,4},
Data {3,4,5},
Data {4,5,6},
};
Subtree from cow: Nodes {
Data {2,3,4},
Data {3,4,5},
Data {4,5,6},
};
Subtrees match: true
Subtrees shared: true
Siblings unshared: true
Parents unshared: true
Roots unshared: true

One way to solve the problem on having to dispatch on unique_ptr<T> is to go back to passing around pointers the way you do in your visitor, but changing the return type to Node*, like this:
// Top-level function stays the same
Node* genericVisit(Node* node) {
if (dynamic_cast<Subnode2>(node)) {
return visit(static_cast<Subnode2*>(node));
}
// etc
}
// Specialized overloads can return replacements as they go
virtual Node* visit(Subnode2* node) {
if (mustReplace()) {
Subnode1 *myReplacement = ...
return myReplacement;
}
if (node->getChild()) {
Node *replacement = genericVisit(node->getChild());
// nullptr means no replacement; non-null means we replace the child
if (replacement) {
node->setChild(replacement);
}
}
return nullptr;
}
This approach requires implementer to pay attention to what's returned (i.e. nullptr or not) before performing the replacement. On the other hand, it keeps the final decision on performing the replacement in the hands of the function that makes the visitor call, which ultimately translates to more control over the internals, i.e. better encapsulation.

Related

Detecting null pointers inside vector of boos::variant

Following the question in Heterogenous vectors of pointers. How to call functions.
I would like to know how to identify null points inside the vector of boost::variant.
Example code:
#include <boost/variant.hpp>
#include <vector>
template< typename T>
class A
{
public:
A(){}
~A(){}
void write();
private:
T data;
};
template< typename T>
void A<T>::write()
{
std::cout << data << std::endl;
}
class myVisitor
: public boost::static_visitor<>
{
public:
template< typename T>
void operator() (A<T>* a) const
{
a->write();
}
};
int main()
{
A<int> one;
A<double> two;
typedef boost::variant<A<int>*, A<double>* > registry;
std::vector<registry> v;
v.push_back(&one);
v.push_back(&two);
A<int>* tst = new A<int>;
for(auto x: v)
{
boost::apply_visitor(myVisitor(), x);
try {delete tst; tst = nullptr;}
catch (...){}
}
}
Since I am deleting the pointer I would hope that the last one will give me an error or something. How can I check if the entry in the entry is pointing to nullptr?
Note: this partly ignores the X/Y of this question, based on the tandom question (Heterogenous vectors of pointers. How to call functions)
What you seem to be after is polymorphic collections, but not with a virtual type hierarchy.
This is known as type erasure, and Boost Type Erasure is conveniently wrapped for exactly this use case with Boost PolyCollection.
The type erased variation would probably look like any_collection:
Live On Coliru
#include <boost/variant.hpp>
#include <cmath>
#include <iostream>
#include <vector>
#include <boost/poly_collection/any_collection.hpp>
#include <boost/type_erasure/member.hpp>
namespace pc = boost::poly_collection;
BOOST_TYPE_ERASURE_MEMBER(has_write, write)
using writable = has_write<void()>;
template <typename T> class A {
public:
A(T value = 0) : data(value) {}
// A() = default; // rule of zero
//~A() = default;
void write() const { std::cout << data << std::endl; }
private:
T data/* = 0*/;
};
int main()
{
pc::any_collection<writable> registry;
A<int> one(314);
A<double> two(M_PI);
registry.insert(one);
registry.insert(two);
for (auto& w : registry) {
w.write();
}
}
Prints
3.14159
314
Note that the insertion order is preserved, but iteration is done type-by-type. This is also what makes PolyCollection much more efficient than "regular" containers that do not optimize allocation sizes or use pointers.
BONUS: Natural printing operator<<
Using classical dynamic polymorphism, this would not work without adding virtual methods, but with Boost TypeErasure ostreamable is a ready-made concept:
Live On Coliru
#include <boost/variant.hpp>
#include <cmath>
#include <iostream>
#include <vector>
#include <boost/poly_collection/any_collection.hpp>
#include <boost/type_erasure/operators.hpp>
namespace pc = boost::poly_collection;
using writable = boost::type_erasure::ostreamable<>;
template <typename T> class A {
public:
A(T value = 0) : data(value) {}
// A() = default; // rule of zero
//~A() = default;
private:
friend std::ostream& operator<<(std::ostream& os, A const& a) {
return os << a.data;
}
T data/* = 0*/;
};
int main()
{
pc::any_collection<writable> registry;
A<int> one(314);
A<double> two(M_PI);
registry.insert(one);
registry.insert(two);
for (auto& w : registry) {
std::cout << w << "\n";
}
}
Printing the same as before.
UPDATE
To the comment:
I want to create n A<someType> variables (these are big objects). All of these variables have a write function to write something to a file.
My idea is to collect all the pointers of these variables and at the end loop through the vector to call each write function. Now, it might happen that I want to allocate memory and delete a A<someType> variable. If this happens it should not execute the write function.
This sounds like one of the rare occasions where shared_ptr makes sense, because it allows you to observe the object's lifetime using weak_ptr.
Object Graph Imagined...
Let's invent a node type that can participate in a pretty large object graph, such that you would keep an "index" of pointers to some of its nodes. For this demonstration, I'll make it a tree-structured graph, and we're going to keep References to the leaf nodes:
using Object = std::shared_ptr<struct INode>;
using Reference = std::weak_ptr<struct INode>;
Now, lets add identification to the Node base so we have an arbitrary way to identify nodes to delete (e.g. all nodes with odd ids). In addition, any node can have child nodes, so let's put that in the base node as well:
struct INode {
virtual void write(std::ostream& os) const = 0;
std::vector<Object> children;
size_t id() const { return _id; }
private:
size_t _id = s_idgen++;
};
Now we need some concrete derived node types:
template <typename> struct Node : INode {
void write(std::ostream& os) const override;
};
using Root = Node<struct root_tag>;
using Banana = Node<struct banana_tag>;
using Pear = Node<struct pear_tag>;
using Bicycle = Node<struct bicycle_tag>;
// etc
Yeah. Imagination is not my strong suit ¯\(ツ)/¯
Generate Random Data
// generating demo data
#include <random>
#include <functional>
#include <array>
static std::mt19937 s_prng{std::random_device{}()};
static std::uniform_int_distribution<size_t> s_num_children(0, 3);
Object generate_object_graph(Object node, unsigned max_depth = 10) {
std::array<std::function<Object()>, 3> factories = {
[] { return std::make_shared<Banana>(); },
[] { return std::make_shared<Pear>(); },
[] { return std::make_shared<Bicycle>(); },
};
for(auto n = s_num_children(s_prng); max_depth && n--;) {
auto pick = factories.at(s_prng() % factories.size());
node->children.push_back(generate_object_graph(pick(), max_depth - 1));
}
return node;
}
Nothing fancy. Just a randomly generated tree with a max_depth and random distribution of node types.
write to Pretty-Print
Let's add some logic to display any object graph with indentation:
// for demo output
#include <boost/core/demangle.hpp>
template <typename Tag> void Node<Tag>::write(std::ostream& os) const {
os << boost::core::demangle(typeid(Tag*).name()) << "(id:" << id() << ") {";
if (not children.empty()) {
for (auto& ch : children) {
ch->write(os << linebreak << "- " << indent);
os << unindent;
}
os << linebreak;
}
os << "}";
}
To keep track of the indentation level I'll define these indent/unindent
manipulators modifying some custom state inside the stream object:
static auto s_indent = std::ios::xalloc();
std::ostream& indent(std::ostream& os) { return os.iword(s_indent) += 3, os; }
std::ostream& unindent(std::ostream& os) { return os.iword(s_indent) -= 3, os; }
std::ostream& linebreak(std::ostream& os) {
return os << "\n" << std::setw(os.iword(s_indent)) << "";
}
That should do.
Getting Leaf Nodes
Leaf nodes are the nodes without any children.
This is a depth-first tree visitor taking any output iterator:
template <typename Out>
Out get_leaf_nodes(Object const& tree, Out out) {
if (tree) {
if (tree->children.empty()) {
*out++ = tree; // that's a leaf node!
} else {
for (auto& ch : tree->children) {
get_leaf_nodes(ch, out);
}
}
}
return out;
}
Removing some nodes:
Yet another depht-first visitor:
template <typename Pred>
size_t remove_nodes_if(Object tree, Pred predicate)
{
size_t n = 0;
if (!tree)
return n;
auto& c = tree->children;
// depth first
for (auto& child : c)
n += remove_nodes_if(child, predicate);
auto e = std::remove_if(begin(c), end(c), predicate);
n += std::distance(e, end(c));
c.erase(e, end(c));
return n;
}
DEMO TIME
Tieing it all together, we can print a randomly generated graph:
int main()
{
auto root = generate_object_graph(std::make_shared<Root>());
root->write(std::cout);
This puts all its leaf node References in a container:
std::list<Reference> leafs;
get_leaf_nodes(root, back_inserter(leafs));
Which we can print using their write() methods:
std::cout << "\nLeafs: " << leafs.size();
for (Reference& ref : leafs)
if (Object alive = ref.lock())
alive->write(std::cout << " ");
Of course all the leafs are still alive. But we can change that! We will remove one in 5 nodes by id:
auto _2mod5 = [](Object const& node) { return (2 == node->id() % 5); };
std::cout << "\nRemoved " << remove_nodes_if(root, _2mod5) << " 2mod5 nodes from graph\n";
std::cout << "\n(Stale?) Leafs: " << leafs.size();
The reported number of leafs nodes would still seem the same. That's... not
what you wanted. Here's where your question comes in: how do we detect the
nodes that were deleted?
leafs.remove_if(std::mem_fn(&Reference::expired));
std::cout << "\nLive leafs: " << leafs.size();
Now the count will accurately reflect the number of leaf nodes remaining.
Live On Coliru
#include <memory>
#include <vector>
#include <ostream>
using Object = std::shared_ptr<struct INode>;
using Reference = std::weak_ptr<struct INode>;
static size_t s_idgen = 0;
struct INode {
virtual void write(std::ostream& os) const = 0;
std::vector<Object> children;
size_t id() const { return _id; }
private:
size_t _id = s_idgen++;
};
template <typename> struct Node : INode {
void write(std::ostream& os) const override;
};
using Root = Node<struct root_tag>;
using Banana = Node<struct banana_tag>;
using Pear = Node<struct pear_tag>;
using Bicycle = Node<struct bicycle_tag>;
// etc
// for demo output
#include <boost/core/demangle.hpp>
#include <iostream>
#include <iomanip>
static auto s_indent = std::ios::xalloc();
std::ostream& indent(std::ostream& os) { return os.iword(s_indent) += 3, os; }
std::ostream& unindent(std::ostream& os) { return os.iword(s_indent) -= 3, os; }
std::ostream& linebreak(std::ostream& os) {
return os << "\n" << std::setw(os.iword(s_indent)) << "";
}
template <typename Tag> void Node<Tag>::write(std::ostream& os) const {
os << boost::core::demangle(typeid(Tag*).name()) << "(id:" << id() << ") {";
if (not children.empty()) {
for (auto& ch : children) {
ch->write(os << linebreak << "- " << indent);
os << unindent;
}
os << linebreak;
}
os << "}";
}
// generating demo data
#include <random>
#include <functional>
#include <array>
static std::mt19937 s_prng{std::random_device{}()};
static std::uniform_int_distribution<size_t> s_num_children(0, 3);
Object generate_object_graph(Object node, unsigned max_depth = 10) {
std::array<std::function<Object()>, 3> factories = {
[] { return std::make_shared<Banana>(); },
[] { return std::make_shared<Pear>(); },
[] { return std::make_shared<Bicycle>(); },
};
for(auto n = s_num_children(s_prng); max_depth && n--;) {
auto pick = factories.at(s_prng() % factories.size());
node->children.push_back(generate_object_graph(pick(), max_depth - 1));
}
return node;
}
template <typename Out>
Out get_leaf_nodes(Object const& tree, Out out) {
if (tree) {
if (tree->children.empty()) {
*out++ = tree;
} else {
for (auto& ch : tree->children) {
get_leaf_nodes(ch, out);
}
}
}
return out;
}
template <typename Pred>
size_t remove_nodes_if(Object tree, Pred predicate)
{
size_t n = 0;
if (!tree)
return n;
auto& c = tree->children;
// depth first
for (auto& child : c)
n += remove_nodes_if(child, predicate);
auto e = std::remove_if(begin(c), end(c), predicate);
n += std::distance(e, end(c));
c.erase(e, end(c));
return n;
}
#include <list>
int main()
{
auto root = generate_object_graph(std::make_shared<Root>());
root->write(std::cout);
std::list<Reference> leafs;
get_leaf_nodes(root, back_inserter(leafs));
std::cout << "\n------------"
<< "\nLeafs: " << leafs.size();
for (Reference& ref : leafs)
if (Object alive = ref.lock())
alive->write(std::cout << " ");
auto _2mod5 = [](Object const& node) { return (2 == node->id() % 5); };
std::cout << "\nRemoved " << remove_nodes_if(root, _2mod5) << " 2mod5 nodes from graph\n";
std::cout << "\n(Stale?) Leafs: " << leafs.size();
// some of them are not alive, see which are gone ("detecing the null pointers")
leafs.remove_if(std::mem_fn(&Reference::expired));
std::cout << "\nLive leafs: " << leafs.size();
}
Prints e.g.
root_tag*(id:0) {
- bicycle_tag*(id:1) {}
- bicycle_tag*(id:2) {
- pear_tag*(id:3) {}
}
- bicycle_tag*(id:4) {
- bicycle_tag*(id:5) {}
- bicycle_tag*(id:6) {}
}
}
------------
Leafs: 4 bicycle_tag*(id:1) {} pear_tag*(id:3) {} bicycle_tag*(id:5) {} bicycle_tag*(id:6) {}
Removed 1 2mod5 nodes from graph
(Stale?) Leafs: 4
Live leafs: 3
Or see the COLIRU link for a much larger sample.

C++: How to iterate through a double pointer pointing to an array of pointers

In the following piece of code, I am iterating through a pointer to an array of pointers to TreeNode objects. Below is my code where I iterate through the array:
TreeNode* childListPointer = *currNode->children;
for (TreeNode* currChild = childListPointer; currChild != NULL; currChild = ++childListPointer) {
std::cout << "Iteration" << endl;
}
And below is the code of my TreeNode struct:
typedef struct TreeNode {
int key;
int val;
bool flag;
int num_children;
TreeNode **children;
} TreeNode;
However, my code keeps getting stuck an infinite loop, even when the length of the array is a small number (e.x. 4 or 5).
Note: The autograder system does not allow me to modify the TreeNode struct.
Your array is a size and count.
int num_children;
TreeNode **children;
Based off that, you can make a simple range adapter for for(:) loops:
template<class It>
struct range {
It s, f;
It begin() const { return s; }
It end() const { return f; }
range(It b, It e):s(b),f(e) {}
range(It b, std::ptrdiff_t count):range(b, b+count) {}
};
now, just:
for(TreeNode* child : range{ currNode->children, currNode->num_children })
{
std::cout << "Iteration" << endl;
}
Prior to c++17 you need a make_range:
template<class It>
range<It> make_range( It b, It e ) { return {b,e}; }
template<class It>
range<It> make_range( It b, std::ptrdiff_t c ) { return {b,c}; }
for(TreeNode* child : make_range( currNode->children, currNode->num_children ))
{
std::cout << "Iteration" << endl;
}
because "deduction guides" where added in c++17.
And you are done. You can now iterate over the children without doing any pointer arithmetic and getting confused.
...
If you are stuck in c++03 you can do:
for (int i = 0; i < currNode->num_children; ++i)
{
TreeNode* child = currNode->children[i];
std::cout << "Iteration" << endl;
}
or
TreeNode** it = currNode->children;
TreeNode** end = it+currNode->num_children;
for (; it != end; ++it)
{
TreeNode* child = *it;
std::cout << "Iteration" << endl;
}
which is almost exactly what the range version compiles down to.

C++ how can I implement a hash set from a hash table using a template class child of a template class?

I have a school assignment to implement a hash_set and a hash_map, I am given a main.cpp that has code to test these classes (inserts, deletes, searches, prints, etc). I am also given a base hash_table class that is a templated class, and just told to "make the hash_set and hash_map work based on the testing code with new classes of their own".
Without more specific details, I assume I am supposed to implement the set and map based on the hash_table, which the teacher said the project could be completed in 40 lines of code (wat??) between the two new classes...but I can't even get the hash_set class to inherit the hash_table since it's a template. I can get it compiling ok without making the hash_set class a template, until I instantiate it, since the testing code is passing it a template type in the declaration. But if I try to make the class a template type itself to compensate for this, everything breaks and I can't track down the source of the error. I'm about 8 hours into this project which should supposedly be a 1 hour project, so I'm at wits end here.
Here is the original main.cpp with the testing code (hash_map is commented out because I haven't even started that yet :/ )
//
// main.cpp
// CS3100Project05_HashCollections
#include "WSUHashTable.h"
//#include "WSUHashMap.h" // Uncomment to test WSUHashMap
#include "WSUHashSet.h" // Uncomment to test WSUHashSet
#include <iostream>
int main(int argc, const char * argv[])
{
///////////////////////////////////////////////////////////////////
/// Part 1 :TEST HASH TABLE ///
///////////////////////////////////////////////////////////////////
{
WSUHashTable<int> example = {1, 2, 3, 4, 1};
std::cout << "Part 1a: Expected output is in any order " <<
"\"1 2 3 4\"\n";
for(int n : example)
{
std::cout << n << ' ';
}
std::cout << std::endl;
std::cout << std::endl;
std::cout << "Part 1b: Expected output is \"Found 2\"\n";
{
auto search = example.find(2);
if(search != example.end())
{
std::cout << "Found " << (*search) << '\n';
}
else
{
std::cout << "Not found\n";
}
}
std::cout << std::endl;
std::cout << "Part 1c: Expected output is \"Not found\"\n";
example.erase(2);
{
auto search = example.find(2);
if(search != example.end()) {
std::cout << "Found " << (*search) << '\n';
}
else {
std::cout << "Not found\n";
}
}
std::cout << std::endl;
}
///////////////////////////////////////////////////////////////////
/// Part 2: TEST HASH SET ///
///////////////////////////////////////////////////////////////////
/***** Uncomment to test WSUHashSet */
{
WSUHashSet<std::string> stringSet = {"1", "2", "3", "4"};
std::cout << "Part 2a: Expected output is \"Found \"2\"\"\n";
{ // Test find() that succeeds
auto search = stringSet.find("2");
if(search != stringSet.end()) {
std::cout << "Found \"" << (*search) << "\"\n";
}
else {
std::cout << "Not found\n";
}
}
std::cout << std::endl;
std::cout << "Part 2b: Expected output is \"Not found\"\n";
stringSet.erase("2");
{ // Test find() that fails
auto search = stringSet.find("2");
if(search != stringSet.end())
{
std::cout << "Found \"" << (*search) << "\"\n";
}
else
{
std::cout << "Not found\n";
}
}
std::cout << std::endl;
WSUHashSet<double> doubleSet = {
1.1, 2.2, 3.2, 4.4, 5.5, 6.1, 7.2, 8.4, 9.9
};
std::cout << "Part 2c: Expected output is in any order " <<
"\"5.5 7.2 8.4 9.9 1.1 2.2 3.2 4.4 6.1\"\n";
for(auto n : doubleSet )
{ // Test using implicit iterators
std::cout << n << ' ';
}
std::cout << std::endl;
std::cout << std::endl;
std::cout << "Part 2d: Expected output is in any order " <<
"\"5.5 7.2 8.4 9.9 4.4 6.1\"\n";
// Part 7: Using explicit iterators while mutating set
for(auto it = doubleSet.begin(); it != doubleSet.end(); )
{ // erase every number less than 4.0
if(*it < 4.0)
{
it = doubleSet.erase(it);
}
else
{
++it;
}
}
for(auto n : doubleSet)
{
std::cout << n << ' ';
}
std::cout << std::endl;
std::cout << std::endl;
}
///////////////////////////////////////////////////////////////////
/// Part 3: TEST HASH MAP ///
///////////////////////////////////////////////////////////////////
/***** Uncomment to test WSUHashMap *
{
WSUHashMap<int, std::string> dict = {{1, "one"}, {2, "two"}};
dict.insert({3, "three"});
dict.insert(std::make_pair(4, "four"));
dict.insert({{4, "another four"}, {5, "five"}});
std::cout << "Part 3a: Expected output is " <<
"\"inserting 1 -> \"another one\" failed\"\n";
bool ok = dict.insert({1, "another one"}).second;
std::cout << "inserting 1 -> \"another one\" "
<< (ok ? "succeeded" : "failed") << '\n';
std::cout << std::endl;
std::cout << "Part 3b: Expected output is " <<
"\"contents: " <<
"1 => one " <<
"2 => two " <<
"3 => three " <<
"4 => four " <<
"5 => five\"\n";
std::cout << "contents: ";
for(auto& p : dict)
{
std::cout << " " << p.first << " => " << p.second << ' ';
}
std::cout << std::endl;
}
*****/
return 0;
}
And this is the original hash_table class file:
//
// WSUHashTable.h
// CS3100Project05_HashCollections
#ifndef __CS3100Project05_HashCollections__WSUHashTable_H
#define __CS3100Project05_HashCollections__WSUHashTable_H
#include <vector>
#include <utility>
#include <algorithm>
#include <iterator>
#include <cassert>
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
template <
typename elementT,
typename Hash = std::hash<elementT>
>
class WSUHashTable
{
private:
///////////////////////////////////////////////////////////////////
typedef elementT bucket_t;
///////////////////////////////////////////////////////////////////
static const std::size_t initialCapacity = (1 << 2);
constexpr static float maxLoadFactor = 0.73f;
///////////////////////////////////////////////////////////////////
std::size_t mSize; // Number of elements in table
std::vector<bucket_t> mStorage; // Storage for elements
std::vector<bool> mIsValidFlags;
public:
///////////////////////////////////////////////////////////////////
class iterator : public std::iterator<
std::input_iterator_tag, elementT>
{
friend class WSUHashTable<elementT, Hash>;
const WSUHashTable<elementT, Hash> *mHashTablePtr;
std::size_t mIndex;
////////////////////////////////////////////////////////////////
iterator(
const WSUHashTable<elementT, Hash> &hashTable,
std::size_t index = 0
) :
mHashTablePtr(&hashTable),
mIndex(index)
{
}
////////////////////////////////////////////////////////////////
std::size_t getIndex() const
{
return mIndex;
}
public:
////////////////////////////////////////////////////////////////
iterator(const iterator &other) :
mHashTablePtr(other.mHashTablePtr),
mIndex(other.mIndex)
{
}
////////////////////////////////////////////////////////////////
iterator& operator++()
{
++mIndex;
while(mIndex < mHashTablePtr->mIsValidFlags.size() &&
!mHashTablePtr->mIsValidFlags[mIndex])
{ // Skip over empty buckets
++mIndex;
}
return *this;
}
////////////////////////////////////////////////////////////////
iterator operator++(int)
{
const_iterator tmp(*this);
operator++();
return tmp;
}
////////////////////////////////////////////////////////////////
bool operator==(const iterator& rhs)
{
return mIndex == rhs.mIndex;
}
////////////////////////////////////////////////////////////////
bool operator!=(const iterator& rhs)
{
return mIndex != rhs.mIndex;
}
////////////////////////////////////////////////////////////////
const elementT &operator*()
{
return mHashTablePtr->mStorage[mIndex];
}
};
///////////////////////////////////////////////////////////////////
typedef const iterator const_iterator;
private:
typedef std::pair<const_iterator, bool> _findResult;
///////////////////////////////////////////////////////////////////
std::size_t _calculatedIndex(const elementT &element) const
{
return (Hash()(element) % mStorage.size());
}
///////////////////////////////////////////////////////////////////
// Returns a pair containing iterator to bucket where element
// should be and flag indicating whether it is there.
_findResult _find( const elementT &element ) const
{
std::size_t index = _calculatedIndex(element);
while(mIsValidFlags[index] &&
((mStorage[index] < element) || (element < mStorage[index])))
{ // Loop until element is found or an empty bucket is found
++index;
index %= mStorage.size();
}
return _findResult(
const_iterator(*this, index), mIsValidFlags[index]);
}
///////////////////////////////////////////////////////////////////
void _doubleCapacityAndRehash()
{
const std::size_t oldSize = mIsValidFlags.size();
// Save off mStorage by moving contents instead of copying
std::vector<bucket_t> oldStorage = std::move(mStorage);
std::vector<bool> oldIsValidFlags = std::move(mIsValidFlags);
// Replace mStorage and mIsValidFlags with empty storage with
// twice the size/capacity.
mStorage = std::move(std::vector<bucket_t>(oldSize * 2));
mIsValidFlags = std::move(std::vector<bool>(oldSize * 2));
// We are going to re-insert everything, so strat with size 0
mSize = 0;
for(std::size_t i = 0; i < oldSize; ++i)
{ // Insert values from all valid buckets in old storage
if(oldIsValidFlags[i])
{
insert(oldStorage[i]);
}
}
}
public:
///////////////////////////////////////////////////////////////////
WSUHashTable() :
mSize(0),
mStorage(initialCapacity),
mIsValidFlags(initialCapacity)
{
}
///////////////////////////////////////////////////////////////////
WSUHashTable(const WSUHashTable &other) :
mSize(other.mSize),
mStorage(other.mStorage),
mIsValidFlags(other.mIsValidFlags)
{
}
///////////////////////////////////////////////////////////////////
template< class InputIt >
WSUHashTable(InputIt first, InputIt last) :
mSize(0),
mStorage(initialCapacity),
mIsValidFlags(initialCapacity)
{
while(first != last)
{
insert(*first);
++first;
}
}
///////////////////////////////////////////////////////////////////
WSUHashTable(std::initializer_list<elementT> init) :
mSize(0),
mStorage(initialCapacity),
mIsValidFlags(initialCapacity)
{
insert(init);
}
///////////////////////////////////////////////////////////////////
std::size_t size() const
{
return mSize;
}
///////////////////////////////////////////////////////////////////
/// Inserts element(s) into the container, if the container doesn't
/// already contain an an equivalent element.
/// Returns a pair consisting of an iterator to the inserted
/// element (or to the element that prevented the insertion) and a
/// bool denoting whether the insertion took place.
std::pair<const_iterator, bool> insert( const elementT &element )
{
if(mSize > (maxLoadFactor * mStorage.size()))
{ // resize to make room for insertion
_doubleCapacityAndRehash();
}
_findResult result = _find(element);
if(result.second)
{ // element is present
return std::pair<const_iterator, bool>(result.first, false);
}
const std::size_t index = result.first.getIndex();
mStorage[index] = element;
mIsValidFlags[index] = true;
++mSize;
return std::pair<const_iterator, bool>(result.first, true);
}
///////////////////////////////////////////////////////////////////
/// Inserts element(s) into the container, if the container doesn't
/// already contain an an equivalent element.
/// Returns a pair consisting of an iterator to the inserted
/// element (or to the element that prevented the insertion) and a
/// bool denoting whether the insertion took place.
///
/// An && argumnet signals an "emplace" operation in C++11. The
/// value will be moved via std::move() instead of copied.
std::pair<const_iterator, bool> insert( elementT &&element )
{
if(mSize > (maxLoadFactor * mStorage.size()))
{ // resize to make room for insertion
_doubleCapacityAndRehash();
}
_findResult result = _find(element);
if(result.second)
{ // element is present
return std::pair<const_iterator, bool>(
result.first, false);
}
const std::size_t index = result.first.getIndex();
mStorage[index] = std::move(element);
mIsValidFlags[index] = true;
++mSize;
return std::pair<const_iterator, bool>(result.first, true);
}
///////////////////////////////////////////////////////////////////
void insert( std::initializer_list<elementT> ilist )
{
for(const elementT &element : ilist)
{
insert(element);
}
}
///////////////////////////////////////////////////////////////////
/// Returns iterator following the last removed element.
const_iterator erase( const_iterator pos )
{
const std::size_t index = pos.getIndex();
if(mIsValidFlags[index])
{ // element is present
mIsValidFlags[index] = false;
--mSize;
}
return ++iterator(pos);
}
///////////////////////////////////////////////////////////////////
// Returns the number of elements erased (it will be zero or one).
std::size_t erase(const elementT &element)
{
_findResult result = _find(element);
if(result.second)
{ // element is present
mIsValidFlags[result.first.getIndex()] = false;
--mSize;
return 1;
}
return 0;
}
///////////////////////////////////////////////////////////////////
const_iterator find( const elementT &element ) const
{
_findResult result = _find(element);
if(result.second)
{ // element was found
return result.first;
}
return end();
}
///////////////////////////////////////////////////////////////////
const_iterator begin() const
{
std::size_t index = 0;
while(index < mIsValidFlags.size() && !mIsValidFlags[index])
{ // Skip over empty buckets
++index;
}
return const_iterator(*this, index);
}
///////////////////////////////////////////////////////////////////
const_iterator end() const
{
return const_iterator(*this, mStorage.size());
}
};
#endif /* defined(__CS3100Project05_HashCollections__WSUHashTable_H) */
I know the hash_table file is long, and I have a decent idea on how to proceed, since a hash_set really only needs to implement a search function within the insert to make sure the list is unique, but doesn't really mess with hash order or anything (I'm not really sure how to implement the hash itself, but I assume the inheritance will sort that out nicely), and the hash_map will allow a null key and any null values....but first something has to compile.
This is what I have currently as my hash_set class, just trying to get a simple constructor that uses the parent class's constructor with the correct type (std::string):
#ifndef __WSUHashSet__
#define __WSUHashSet__
#include <vector>
#include <utility>
#include <algorithm>
#include <iterator>
#include "WSUHashTable.h"
/*template<
typename elementT,
typename Hash = std::hash<elementT>
>*/
class WSUHashSet : public WSUHashTable<std::string> {
private:
public:
WSUHashSet(std::initializer_list<std::string> init) : WSUHashTable(init)
{
insert(init);
}
};
#endif //__WSUHashSet__
Currently I get the same error: with this exact code it tells me that "WSUHashSet is not a template", which is good because my class is fine, but bad because I can't just edit the main.cpp to not treat it as a template.
When I try to make it a template, like this:
#ifndef __WSUHashSet__
#define __WSUHashSet__
#include <vector>
#include <utility>
#include <algorithm>
#include <iterator>
#include "WSUHashTable.h"
template<
typename elementT,
typename Hash = std::hash<elementT>
>
class WSUHashSet<elementT> : public WSUHashTable<std::string> {
private:
public:
WSUHashSet<elementT>::WSUHashSet(std::initializer_list<std::string> init) : WSUHashTable(init)
{
insert(init);
}
};
#endif //__WSUHashSet__
I really need some direction here. I can't afford to spend a lot more time on this as it's not my only class, and I feel like this should be fairly simple. The theory makes sense, but the implementation makes me feel like I'm blindly wandering in circles.
Thanks
EDIT: the actual compliler error is
WSUHashSet.h line 19: error: WSUHashSet is not a template
Apparently the pasting into the code block here caused confusion. Here's the actual line (19 in codeblocks) that is breaking the compilation:
WSUHashSet(std::initializer_list<std::string> init) : WSUHashTable(init)
{
insert(init);
}
If you look at the test code, you can see the the types you create must be templates, as they're instantiated for specific element types:
WSUHashTable<int> example = {1, 2, 3, 4, 1};
WSUHashSet<double> doubleSet = { ...
You show your attempt to make a template:
template<
typename elementT,
typename Hash = std::hash<elementT>
>
class WSUHashSet<elementT> : public WSUHashTable<std::string> {
public:
WSUHashSet<elementT>::WSUHashSet(std::initializer_list<std::string> init) : WSUHashTable(init)
{
insert(init);
}
...
That's not so far off... try:
template <typename elementT>
class WSUHashSet<elementT> : public WSUHashTable<elementT>
{
public:
WSUHashSet(std::initializer_list<std::string> init)
: WSUHashTable<elementT>(init)
{ }

How to clone a hook with Boost Intrusive?

I'm learning Boost Intrusive library. I have a problem when I try to copy a STL container. I use a std::vector. It contains elements of class list_base_hook in the mode auto_unlink but the information about the node (is_linked()) is lost when you call the copy constructor.
I have the following code:
class helper_class
{
public:
helper_class(void) { /* ... */ }
helper_class(const helper_class& hc) { /* ... */ }
helper_class(helper_class&& hc) { /* ... */ }
helper_class & operator=(const helper_class& hc) { /* ... */ }
helper_class & operator=(helper_class&& hc) { /* ... */ }
virtual ~helper_class(void) { /* ... */ }
// ...
};
typedef list_base_hook<link_mode<auto_unlink> > auto_hook;
class my_class : public auto_hook
{
public:
friend bool operator==(const my_class &a, const my_class &b)
{
return (a.int_ == b.int_) &&
(a.helper_class_ == b.helper_class_);
}
int int_;
helper_class* helper_class_;
// ...
};
typedef list<my_class, constant_time_size<false> > my_class_list;
struct new_cloner
{
my_class *operator()(const my_class &clone_this)
{ return new my_class(clone_this); }
};
struct delete_disposer
{
void operator()(my_class *delete_this)
{ delete delete_this; }
};
int main()
{
// ...
helper_class the_helper_class;
const int MaxElem = 100;
std::vector<my_class> nodes(MaxElem);
std::vector<my_class> copy_nodes(MaxElem);
my_class_list list;
for(int i = 0; i < MaxElem; ++i) {
nodes[i].int_ = i;
nodes[i].helper_class_ = &the_helper_class;
}
list.insert(list.end(), nodes.begin(), nodes.end());
my_class_list cloned_list;
cloned_list.clone_from(list, new_cloner(), delete_disposer());
copy_nodes = nodes;
std::cout << "nodes[0].is_linked() : "
<< ((nodes[0].is_linked()) ? "LINKED":"NO-LINKED")
<< std::endl;
std::cout << "copy_nodes[0].is_linked() : "
<< ((copy_nodes[0].is_linked()) ? "LINKED":"NO-LINKED")
<< std::endl;
std::cout << "list[0].is_linked() : "
<< (((*list.begin()).is_linked()) ? "LINKED":"NO-LINKED")
<< std::endl;
std::cout << "cloned_list[0].is_linked() : "
<< (((*cloned_list.begin()).is_linked()) ? "LINKED":"NO-LINKED")
<< std::endl;
cloned_list.clear_and_dispose(delete_disposer());
// ...
return 0;
};
Standard output:
nodes[0].is_linked() : LINKED
copy_nodes[0].is_linked() : NO-LINKED
list[0].is_linked() : LINKED
cloned_list[0].is_linked() : LINKED
Why the vector copy_nodes isn't linked?
Thanks you.
Why would you expect a copied node to be in a collection?
If you print a book twice, do you expect it to be magically end up in the same library as the other book that was printed months ago?
It's just a different object. Also known as a copy.
If your copy would "magically" clone the hook as well, that would either break container invariants, or raise the question /where/ the copy should be inserted in the container.
After some serious debating, I figured you might want to know how to clone the list along with the values in a vector:
my_class_list cloned_list;
std::vector<my_class> cloned_nodes;
cloned_nodes.reserve(MaxElem);
cloned_list.clone_from(
list,
[&cloned_nodes](my_class const&v) { cloned_nodes.push_back(v); return &cloned_nodes.back(); },
[](my_class*){}
);
There's no delete here (because you can just destroy the vector anyway). Here's a full demo of this
Live On Coliru
#include <boost/intrusive/list.hpp>
using namespace boost::intrusive;
struct my_class : list_base_hook<link_mode<auto_unlink> > { };
typedef list<my_class, constant_time_size<false> > my_class_list;
#include <iostream>
int main()
{
const int MaxElem = 100;
std::vector<my_class> nodes(MaxElem);
//////////////////////////////////////////////
// He's making a list
my_class_list list;
list.insert(list.end(), nodes.begin(), nodes.end());
//////////////////////////////////////////////
// He's checking it twice
my_class_list cloned_list;
std::vector<my_class> cloned_nodes;
cloned_nodes.reserve(MaxElem);
cloned_list.clone_from(
list,
[&cloned_nodes](my_class const&v) { cloned_nodes.push_back(v); return &cloned_nodes.back(); },
[](my_class*){}
);
std::cout << std::boolalpha;
std::cout << "nodes[0].is_linked() : " << nodes[0].is_linked() << std::endl;
std::cout << "cloned_nodes[0].is_linked(): " << cloned_nodes[0].is_linked() << std::endl;
std::cout << "list[0].is_linked() : " << list.begin()->is_linked() << std::endl;
std::cout << "cloned_list[0].is_linked() : " << cloned_list.begin()->is_linked() << std::endl;
//////////////////////////////////////////////
// Gonna find out who's naughty or nice:
auto nit = cloned_nodes.begin();
auto lit = cloned_list.begin();
while (nit != cloned_nodes.end() && lit != cloned_list.end()) {
assert(&(*nit++) == &(*lit++)); // this would fail if you didn't `reserve()` the vector up front
}
//////////////////////////////////////////////
// now, if you really want you can do
cloned_list.clear();
// after which the simplest thing to do would be `cloned_nodes.clear()`, but let's be very precise:
cloned_nodes.erase(std::remove_if(
cloned_nodes.begin(), cloned_nodes.end(),
[](my_class const& v) { return !v.is_linked(); }),
cloned_nodes.end());
}
In fact, here's a version that puts the cloned nodes right there in the same vector as the source nodes, for fun: Live On Coliru too.

implementing stack class using link list to validate xml file

Implement your Stack class using link list. Use your stack class to validate XML file.
Write C++ program to read XML file from command line and validate XML matching case insensitive beginning and ending tags.
Give error messages to all missing corresponding begin or end tags and continue validating all XML file.
Example to correct XML document.
<Students>
<Student>
<Name>Tanver</Name>
<RollNo>312</RollNo>
<student>
<students>
Example to incorrect XML document, “head” and “html” end tag missing. “name” begin tag missing.
<html>
<head>
<title>My first page</title>
</name>
<body>
My first page
</body>
I'm also training C++ and thought this was a nice exercise. Here is what I came up with:
#include <iostream>
#include <vector>
#include <string>
#include <regex>
using namespace std;
template <typename T>
class Item {
public:
T data;
Item *prev;
Item(T& data, Item *prev) : data(data), prev(prev) {}
};
template <typename T>
class StackIterator {
public:
Item<T> *curr;
StackIterator(Item<T> *curr) : curr(curr) {}
T& operator*() {
return curr->data;
}
StackIterator<T>& operator++() {
curr = curr->prev;
return *this;
}
friend bool operator!=(const StackIterator<T>& l, const StackIterator<T>& r) {
return l.curr != r.curr;
}
};
template <typename T>
class Stack {
typedef StackIterator<T> iterator;
public:
Item<T> *last=NULL;
T& back(void) {
return last->data;
}
void push_back(T d) {
last = new Item<T>(d, last);
}
void pop_back(void) {
Item<T> *curr = last;
last = last->prev;
delete curr;
}
~Stack() {
while (last) pop_back();
}
iterator begin() {
return iterator(last);
}
iterator end() {
return iterator(NULL);
}
};
int main() {
string xml[] = {"<Students>", "<Student>", "<Name>", "Tanver", "</Name>",
"<RollNo>", "312", "</RollNo>", "</Student>", "</Students>"};
//string xml[] = {"<html>", "<head>", "<title>", "My first page", "</title>",
// "</name>", "<body>", "My first page", "</body>"};
regex opentag("^<(.*)>$");
regex closetag("^</(.*)>$");
smatch m;
Stack<string> v;
//vector<string> v;
for (auto& it : xml) {
if (regex_search(it, m, closetag)) {
for (auto& x : m)
if (x == v.back()) {
v.pop_back();
cout << "closing " << x << "\n";
} else {
cout << "Tag " << x << " was not opened\n";
}
} else if (regex_search(it, m, opentag)) {
for (auto &x : m) {
cout << "opening " << x << "\n";
v.push_back(x);
}
} else {
cout << "data: " << it << "\n";
}
}
for (auto& x : v) {
cout << "Tag " << x << " was not closed\n";
}
}
I didn't bother about the reading from the command line part. Of course, one can also skip the whole StackIterator thing, but that would be like writing the whole thing in plain C which I normally do.
Hope this helps!