【问题标题】:C++ Boost recursive variant overloading apply_visitorC++ Boost递归变体重载apply_visitor
【发布时间】:2013-04-05 05:42:37
【问题描述】:

摘要:我希望在包含递归 boost::variant 对象的类中重载 apply_visitor() 方法。

在下面包含的代码中有方法:

template <typename T>
ostream& apply_visitor(const T& fn) const

我想为不同的访问者重载这个方法。像这样的:

ostream& apply_visitor(const PrintData& fn) const

但问题是 PrintData 类还没有完成(参见下面代码中的 cmets)。它是在 Node 类之后定义的。所以我有两个问题(除其他外——我欢迎对这段代码进行一般性批评,该代码正在建模我想投入生产的东西)。

1) 有没有办法让 apply_visitor(PrintData&) 工作?

2) 我可以重新排列(递归)变体,以便所有访问者方法都在 PrintData 中,并且我不必将 apply_visitor 添加到 Node 类吗?

/**
 * compiled with gcc (tested with 4.7.2 on linux)
 * requires the boost development headers to be discoverable
 * by the compiler.
 *
 * g++ -otest-recursive-visit -std=c++11 test-recursive-visit.cpp
 * ./test-recursive-visit
 **/

#include <iostream>
#include <map>
#include <string>
#include <vector>

#include "boost/variant.hpp"
#include "boost/variant/recursive_wrapper.hpp"
#include "boost/variant/static_visitor.hpp"

using namespace std;

/// type name demangler (as implemented in g++). For other compilers,
/// we could add more #elif statements, but for now, just return
/// the mangled name.
#ifdef __GNUG__ /// compiler is g++
#include <cxxabi.h>
string type_demangle(const string& name)
{
    int status;
    char* res = abi::__cxa_demangle(
        name.c_str(), NULL, NULL, &status);
    string demangled_name((status==0) ? res : name);
    free(res);
    return demangled_name;
}
#else /// compiler is not g++
string type_demangle(const string& name) { return name; }
#endif

/// forward declaration of the Node class
/// (needed for recursive variant type)
class Node;

/// the actual recursive variant type
/// (typically hidden from the users)
typedef boost::variant<
    boost::recursive_wrapper<Node>,
    vector<int>,
    vector<float>
> data_t;

// forward declaration for PrintData. See note below concerning
// Node::apply_visitor()
class PrintData;

/// this is the object users will see
/// for prototyping, the tree object is public
class Node
{
  public:
    /// sub-nodes are identified by unique strings
    /// which point to one of the objects that data_t
    /// can hold
    map<string,data_t> tree;

    /// constructor for a std::map object, passed to tree
    Node(const initializer_list<pair<const string,data_t>>& l)
    : tree(l)
    {}

    //
    // INTERESTING PART OF THIS EXAMPLE IS HERE
    //
    // I tried to replace T& with PrintData& adding
    // a forward declaration at the top but I get the
    // errors:
    //
    //  line 86:
    //      invalid use of incomplete type ‘const class PrintData’
    //  line 53:
    //      forward declaration of ‘const class PrintData’
    //

    /// This is called by boost::apply_visitor(Visitor(),Node)
    //ostream& apply_visitor(const PrintData& fn) const
    template <typename T>
    ostream& apply_visitor(const T& fn) const
    {
        for (auto i : tree)
        {
            *fn.os << fn.prefix << i.first;
            i.second.apply_visitor(fn);
        }
        return *fn.os;
    }
};

/// the printing visitor to ostream object
class PrintData : public boost::static_visitor<ostream&>
{
  public:
    ostream* os;
    string prefix;

    /// keep a pointer to the ostream and keep
    /// a prefix string which will hold and "indent"
    /// which is something like "  " for every level
    /// of recursion
    PrintData(ostream& out_stream, const string& prefix_str="")
    : os(&out_stream)
    , prefix(prefix_str)
    {}

    /// recurse into the tree, adding indent characters to prefix
    ostream& operator()(Node& n) const
    {
        *os << endl;
        n.apply_visitor(PrintData(*os, prefix+"  "));
        *os;
    }

    /// actual data types that we want to print out
    template <typename T>
    ostream& operator()(const vector<T>& d) const
    {
        *os << " (vector<" << type_demangle(typeid(T).name()) << ">):";
        for (T i : d)
        {
            *os << " " << i;
        }
        *os << endl;
        return *os;
    }
};

/// convenience operator to allow: cout << node;
ostream& operator<<(ostream& os, const Node& n)
{
    return boost::apply_visitor(PrintData(os), n);
}


int main()
{
    /// hooray for initialization lists!!!
    Node n {
        {"X", Node{
            {"a", vector<int>{1,2,3}},
            {"b", vector<float>{2,3,4}}
        }},
        {"Y", vector<int>{3,4,5}},
        {"Z", Node{
            {"A", Node{
                {"c", vector<float>{4,5,6}}
            }}
        }}
    };

    /**
    applying PrintData to n prints out the following:

    X
      a (vector<int>): 1 2 3
      b (vector<float>): 2 3 4
    Y (vector<int>): 3 4 5
    Z
      A
        c (vector<float>): 4 5 6

    **/
    cout << n;
}

【问题讨论】:

    标签: c++ recursion g++ boost-variant


    【解决方案1】:

    我遇到了类似的问题,我使用了make_recursive_variant,然后我使用map&lt;Key,Value&gt; 作为变体而不是 Node 类。最后,我的节点类变成了一个实用函数,包装了 std::map 引用。如:

    typedef boost::make_recursive_variant<
        std::map<std::string, boost::recursive_variant_>,
        vector<int>,
        vector<float>
    >::type data_t;
    
    // the printing visitor to ostream object
    class PrintData : public boost::static_visitor<ostream&>
    {
      public:
        ostream* os;
        string prefix;
    
        /// keep a pointer to the ostream and keep
        /// a prefix string which will hold and "indent"
        /// which is something like "  " for every level
        /// of recursion
        PrintData(ostream& out_stream, const string& prefix_str="")
        : os(&out_stream)
        , prefix(prefix_str)
        {}
    
        /// recurse into the tree, adding indent characters to prefix
        ostream& operator()(std::map<std::string, data_t>& tree) const
        {
            *os << endl;
            for (auto& i : tree)
            {
                *os << prefix << i.first;
                //you may want to change prefix and use new PrintData instead of *this
                boost::apply_visitor(*this, i.second);
            }
            return *os;
        }
    
        /// actual data types that we want to print out
        template <typename T>
        ostream& operator()(const vector<T>& d) const
        {
            *os << " (vector<" << type_demangle(typeid(T).name()) << ">):";
            for (T i : d)
            {
                *os << " " << i;
            }
            *os << endl;
            return *os;
        }
    };
    
    
    class Node
    {
    public:
        //implicit ctor for data_t conversion
        Node(data_t& data) : data(data) { }
        //...
    private:
        data_t& data;
    };
    

    编辑:我忘了提到您不再需要在 Node 类中使用 apply_visitor,但我不确定 initializer_list ctor。它可能不适用于data_t 类型。

    【讨论】:

    • +1 这是一个有趣的解决方案。我假设您使用变体来保存的不仅仅是几个向量。你能评论一下你在这个设计中遇到的任何问题吗?例如,我发现我必须使用双端队列来保存大对象,这样在重新分配容器时对特定元素的引用不会失效。
    • 我在我的应用程序初始化时构建我的结构,之后的行为就像一个查找树。因此,就参考位置而言,我从向量中受益更多。此外,我切换到 boost v1.53 及其利用 r 值引用的优势,这使我能够在不复制向量的情况下移动我的变体,反之亦然(您可以考虑返回变体而不需要复制成本的方法)。
    【解决方案2】:

    声明函数,但不要在类中定义它。相反,请等到定义 PrintData 后再定义它,此时它将是一个完整的类型。

    class PrintData;
    
    class Node
    {
      public:
      ...
        ostream& apply_visitor(const PrintData& fn) const;
      ...
    };
    
    class PrintData : public boost::static_visitor<ostream&>
    { ... };
    
    inline ostream& Node::apply_visitor(const PrintData& fn) const
    {
      ...
    }
    

    【讨论】:

    • 这正是问题所在,谢谢!有时我看不到森林的树木!还是以林还林? ;) 警示故事:在处理复杂性时不要忘记基础知识。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多