【问题标题】:How to generalize a tree structure with variant/visitor如何用变体/访问者概括树结构
【发布时间】:2016-05-12 21:10:40
【问题描述】:

这是我的问题的第 2 部分,最初发布在 here。感谢@sehe 的澄清和帮助。我最终得到了下面的代码,但我不知道如何将这个东西简化为具有变体和访问者的通用解决方案。非常感谢帮助/建议。谢谢。

#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;
}

【问题讨论】:

    标签: c++ boost boost-variant


    【解决方案1】:
    1. KISS第一

      我不确定所有多态性的目标是什么,无论是静态的还是动态的。我会说如果您的类型结构是这样固定的,只需使用:

      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;});
      }
      

      打印

      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
      
    2. 完全通用,没有动态多态性:

      在这里您可以使用递归变体来更通用:

      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;});
      }
      

      打印

      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::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" }, } },
          }, },
      };
      

      为了一般处理缩进,创建一个有状态的访问者:

      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);
          }
      

      打印: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
      
    3. 没有变体,只有动态多态

      使用与上面的 GenericDomain 树相同的“奇怪”树:

      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; });
      }
      

      不是我对“整洁”的看法,而且效率可能低得多 - 尽管它确实允许可空节点和共享子树。

    【讨论】:

    • 添加了两个基于变体的示例。
    • 添加了运行时多态方法(即使用您的 shared_ptr 和虚拟方法而不是变体和参数类型重载)。希望你能看到你正在混合的各种方法。我坚信您想要"KISS" 选项
    • 我采用了您的 GenericDomain 方法。问题:如何使用标签命名空间中定义的结构来解析访问者中特定于标签的值?我最终修改了模板 struct Node 以包含“kind”,如下所示: template struct Node { .. Kind kind; };所以我可以使用 typeid struct vstType 的“kind”进行迭代: public boost::static_visitor { template int operator()(T& p) const { if ((typeid(p.kind) == typeid (Tag::Store))) 返回 1;返回 2; } };有办法吗?
    • 这是我在 SO 上见过的最好的答案之一。
    猜你喜欢
    • 2022-01-09
    • 1970-01-01
    • 1970-01-01
    • 2018-09-01
    • 1970-01-01
    • 2011-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多