This is a part 2 of my question, originally posted here. Thanks to @sehe for clarifications and help. I ended up with the code that follows, but I can't figure out how can I reduce this thing to a generic solution with variant and visitor. Help/advise is greatly appreciated. Thanks.
#include "stdafx.h"
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <boost/format.hpp>
#include <boost/variant.hpp>
template <typename T> class A
{
public:
typename T L;
typename std::shared_ptr<T> Lptr;
using tlist = std::vector<std::shared_ptr<T>>;
A(std::string n = "") : _n(n){}
A(const A& another) : _n(another._n){};
A(A&& a) : _n(a._n){ _lst = std::move(another._lst); }
tlist &lst() { return _lst; }
void emplace_back(std::shared_ptr<T> wp) {
_lst.emplace_back(wp);
}
std::string n() const { return _n; }
private:
tlist _lst;
std::string _n;
};
/*
suppose I have following tree structure
Store
Shelves
Products on the shelve
*/
using lA = A<boost::blank>; // product
using lB = A<lA>; // shelf
using lC = A<lB>; // store
using lAp = std::shared_ptr<lA>;
using lBp = std::shared_ptr<lB>;
using lCp = std::shared_ptr<lC>;
void printIt(lAp p, int indent){
for (int i = 0; i < indent; ++i)
std::cout << '\t';
std::cout << p->n() << std::endl;
}
void printIt(lBp p, int indent){
for (int i = 0; i < indent; ++i)
std::cout << '\t';
std::cout << p->n() << std::endl;;
std::for_each(begin(p->lst()), end(p->lst()), [&](lAp i){
printIt(i, indent + 1); }
);
}
void printIt(lCp p, int indent){
for (int i = 0; i < indent; ++i)
std::cout << '\t';
std::cout << p->n() << std::endl;
std::for_each ( begin(p->lst()), end(p->lst()), [&](lBp i)
{
printIt(i, indent + 1);
});
}
int main() {
using storage = boost::variant<lAp, lBp, lCp>;
std::vector<lCp> stores;
for (int s = 0; s < 5; ++s) {
lCp store(new lC((boost::format("store %1%") % s).str()));
stores.emplace_back(store);
for (int i = 0; i < 3; ++i) {// ten shelves in the store
lBp shelf(new lB((boost::format("shelf %1%") % i).str()));
store->emplace_back(shelf);
for (int j = 0; j < 2; ++j) // twenty producs per shelf
shelf->emplace_back(std::shared_ptr<lA>(new lA((boost::format("product %1%") % j).str())));
}
}
std::for_each(begin(stores), end(stores), [](lCp p){printIt(p,0); });
int i;
std::cin >> i;
}
I'm not sure what the goal is with all the polymorphism, both static and dynamic. I'd say if your type structure is fixed like that, just use:
Live On Coliru
#include <iostream>
#include <algorithm>
#include <string>
#include <vector>
namespace SimpleDomain {
struct Product {
std::string name;
};
struct Shelf {
std::string name;
std::vector<Product> _products;
};
struct Store {
std::string name;
std::vector<Shelf> _shelves;
};
std::ostream& operator<<(std::ostream& os, Product const& p) {
return os << "\t\t" << p.name << "\n";
}
std::ostream& operator<<(std::ostream& os, Shelf const& s) {
os << "\t" << s.name << "\n";
for (auto& p : s._products) os << p;
return os;
}
std::ostream& operator<<(std::ostream& os, Store const& s) {
os << s.name << "\n";
for (auto& sh : s._shelves) os << sh;
return os;
}
}
int main() {
std::vector<SimpleDomain::Store> stores = {
{ "store 1", {
{ "shelf 1", { { "product 1" }, { "product 2" }, { "product 3" }, } },
{ "shelf 2", { { "product 4" }, { "product 5" }, { "product 6" }, } },
},
},
{ "store 2", {
{ "shelf 1", { { "product 7" }, { "product 8" }, { "product 9" }, } },
{ "shelf 2", { { "product 10" }, { "product 11" }, { "product 12" }, } },
},
}
};
std::for_each(begin(stores), end(stores),
[](SimpleDomain::Store const& p){std::cout << p;});
}
Prints
store 1
shelf 1
product 1
product 2
product 3
shelf 2
product 4
product 5
product 6
store 2
shelf 1
product 7
product 8
product 9
shelf 2
product 10
product 11
product 12
Here you could use a recursive variant to be more generic:
Live On Coliru
#include <iostream>
#include <algorithm>
#include <string>
#include <vector>
#include <boost/variant.hpp>
namespace GenericDomain {
namespace Tag {
struct Store{};
struct Shelf{};
struct Product{};
}
template <typename Kind> struct Node;
using Store = Node<Tag::Store>;
using Shelf = Node<Tag::Shelf>;
using Product = Node<Tag::Product>;
using Tree = boost::variant<
boost::recursive_wrapper<Product>,
boost::recursive_wrapper<Store>,
boost::recursive_wrapper<Shelf>
>;
template <typename Kind> struct Node {
std::string name;
std::vector<Tree> children;
};
template <> struct Node<Tag::Product> {
std::string name;
};
std::ostream& operator<<(std::ostream& os, Tag::Store) { return os << "Store"; }
std::ostream& operator<<(std::ostream& os, Tag::Shelf) { return os << "\tShelf"; }
std::ostream& operator<<(std::ostream& os, Tag::Product) { return os << "\t\tProduct"; }
template <typename Kind> std::ostream& operator<<(std::ostream& os, Node<Kind> const& n) {
os << Kind{} << ": " << n.name << "\n";
for (auto& child : n.children) os << child;
return os;
}
std::ostream& operator<<(std::ostream& os, Product const& p) {
return os << Tag::Product{} << ": " << p.name << "\n";
}
}
int main() {
using namespace GenericDomain;
std::vector<Store> stores = {
Store { "store 1", {
Shelf { "shelf 1", { Product { "product 1" }, Product { "product 2" }, Product { "product 3" }, } },
Shelf { "shelf 2", { Product { "product 4" }, Product { "product 5" }, Product { "product 6" }, } },
},
},
Store { "store 2", {
Shelf { "shelf 1", { Product { "product 7" }, Product { "product 8" }, Product { "product 9" }, } },
Shelf { "shelf 2", { Product { "product 10" }, Product { "product 11" }, Product { "product 12" }, } },
},
}
};
std::for_each(begin(stores), end(stores),
[](GenericDomain::Store const& p){std::cout << p;});
}
Prints
Store: store 1
Shelf: shelf 1
Product: product 1
Product: product 2
Product: product 3
Shelf: shelf 2
Product: product 4
Product: product 5
Product: product 6
Store: store 2
Shelf: shelf 1
Product: product 7
Product: product 8
Product: product 9
Shelf: shelf 2
Product: product 10
Product: product 11
Product: product 12
You can see that we can detect the type of node. Of course, nothing prevents us from making bizarre hierarchies:
std::vector<Store> stores = {
Store { "store 1", {
Shelf { "shelf 1", {
Product { "product 1" },
Store { "store 2", {
Shelf { "shelf 1", { Product { "product 7" }, Product { "product 8" }, Product { "product 9" }, } },
Shelf { "shelf 2", { Product { "product 10" }, Product { "product 11" }, Product { "product 12" }, } },
}, },
Product { "product 3" },
} },
Shelf { "shelf 2", { Product { "product 4" }, Product { "product 5" }, Product { "product 6" }, } },
}, },
};
To generically handle the indentation, make a stateful visitor:
std::ostream& operator<<(std::ostream& os, Tag::Store) { return os << "Store"; }
std::ostream& operator<<(std::ostream& os, Tag::Shelf) { return os << "Shelf"; }
std::ostream& operator<<(std::ostream& os, Tag::Product) { return os << "Product"; }
struct print_vis {
size_t indent = 0;
std::ostream& _os;
using result_type = void;
template <typename Kind> void operator()(Node<Kind> const& n) const {
_os << std::string(indent, ' ') << Kind{} << ": " << n.name << "\n";
print_vis sub { indent+4, _os };
for (auto& child : n.children) sub(child);
}
void operator()(Product const& p) const {
_os << std::string(indent, ' ') << Tag::Product{} << ": " << p.name << "\n";
}
void operator()(Tree const& tree) const {
boost::apply_visitor(*this, tree);
}
Prints: Live On Coliru
Store: store 1
Shelf: shelf 1
Product: product 1
Store: store 2
Shelf: shelf 1
Product: product 7
Product: product 8
Product: product 9
Shelf: shelf 2
Product: product 10
Product: product 11
Product: product 12
Product: product 3
Shelf: shelf 2
Product: product 4
Product: product 5
Product: product 6
With the same "weird" tree as just above with the GenericDomain tree:
Live On Coliru
#include <iostream>
#include <algorithm>
#include <string>
#include <vector>
#include <memory>
namespace DynamicDomain {
struct Node;
using Tree = std::shared_ptr<Node>;
struct Node {
virtual std::string type() const = 0;
std::string name;
std::vector<Tree> children;
template <typename... Child>
Node(std::string name, Child&&... children) :
name(std::move(name)), children { std::forward<Child>(children)... }
{ }
};
struct Product : Node { using Node::Node; virtual std::string type() const { return "Product"; } };
struct Shelf : Node { using Node::Node; virtual std::string type() const { return "Shelf"; } };
struct Store : Node { using Node::Node; virtual std::string type() const { return "Store"; } };
struct print_vis {
size_t indent;
std::ostream* _os;
using result_type = void;
void operator()(Tree const& tree) const {
if (tree) (*this) (*tree); else *_os << "[null]";
}
void operator()(Node const& node) const {
*_os << std::string(indent, ' ') << node.type() << ": " << node.name << "\n";
print_vis sub { indent+4, _os };
for (auto const& child : node.children) sub(child);
}
};
std::ostream& operator<<(std::ostream& os, Tree const& n) {
print_vis{0, &os} (n);
return os;
}
}
int main() {
using namespace DynamicDomain;
std::vector<Tree> stores = {
std::make_shared<Store> ("store 1",
std::make_shared<Shelf> ("shelf 1",
std::make_shared<Product> ("product 1"),
std::make_shared<Store> ("store 2",
std::make_shared<Shelf> ("shelf 1", std::make_shared<Product> ("product 7"), std::make_shared<Product> ("product 8"), std::make_shared<Product> ("product 9") ),
std::make_shared<Shelf> ("shelf 2", std::make_shared<Product> ("product 10"), std::make_shared<Product> ("product 11"), std::make_shared<Product> ("product 12") )
),
std::make_shared<Product> ("product 3")
),
std::make_shared<Shelf> ("shelf 2", std::make_shared<Product> ("product 4"), std::make_shared<Product> ("product 5"), std::make_shared<Product> ("product 6") )
),
};
std::for_each(begin(stores), end(stores),
[](DynamicDomain::Tree const& p){ std::cout << p; });
}
Not my idea of "neat" and potentially much less efficient - although it does allow for nullable nodes and sharing of subtrees.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With