【问题标题】:Recursive using declaration with boost variant带有 boost 变体的递归 using 声明
【发布时间】:2018-01-05 23:24:45
【问题描述】:

我试图表示一个树状递归数据结构,其中每个节点可能是两种不同数据类型之一。我使用 boost 变体来“容纳”每个节点上可能存在的两种类型。

但是,我遇到了一个问题。我严格使用 'using' 指令声明所有这些类型,所以当我了解节点的递归性质时,它会失败,因为 typedef/using 可能不使用递归。

如何做到这一点?

using LeafData = int; // just for illustration
using LeafNode = std::unordered_map<std::string, LeafData>;
using InnerNode = std::unordered_map<std::string, boost_variant<InnerNode, LeafNode>>; // this is problematic since I'm using InnerNode recursively

我已经使用 boost::make_recursive_variant 进行了探索,但它创建的类型 (A) 并不是我所需要的,因为当我想要一个由 InnerNode 组成的 (B) 单个变体时,它会在变体中产生一个变体或 LeafNode。

(A) boost_variant<boost_variant<InnerNode, LeafNode>, LeafNode>
(B) boost_variant<InnerNode, LeafNode>

【问题讨论】:

    标签: c++ c++11 boost boost-variant


    【解决方案1】:

    直接回答(见下文提示)

    你可以使用make_recursive_variant做你想做的事:

    Live On Coliru

    #include <boost/variant.hpp>
    #include <unordered_map>
    
    struct LeafData {
        int _i;
        LeafData(int i) : _i(i) {}
    }; // just for illustration
    
    using LeafNode = std::unordered_map<std::string, LeafData>;
    
    using Node = boost::make_recursive_variant<
            LeafNode,
            std::unordered_map<std::string, boost::recursive_variant_>
        >::type;
    
    using Inner = std::unordered_map<std::string, Node>;
    
    int main() {
        Node tree = Inner {
            { "a", LeafNode { { "one", 1 }, { "two", 2 }, { "three",3 } } },
            { "b", Inner {
                    { "b1", LeafNode { { "four", 4 }, { "five", 5 }, { "six", 6 } } },
                    { "b2", LeafNode { { "seven", 7 }, { "eight", 8 }, { "nine", 9 } } },
                }
            },
            { "c", LeafNode {} },
        };
    }
    

    提示

    为什么要区分内部/叶子节点?在我看来叶节点只是具有值的节点而不是子节点:

    Live On Coliru

    #include <boost/variant.hpp>
    #include <unordered_map>
    
    struct Data {
        int _i;
        Data(int i) : _i(i) {}
    }; // just for illustration
    
    using Tree = boost::make_recursive_variant<
            Data,
            std::unordered_map<std::string, boost::recursive_variant_>
        >::type;
    
    using Node = std::unordered_map<std::string, Tree>;
    
    int main() {
        Tree tree = Node {
            { "a", Node { { "one", 1 }, { "two", 2 }, { "three",3 } } },
            { "b", Node {
                    { "b1", Node { { "four", 4 }, { "five", 5 }, { "six", 6 } } },
                    { "b2", Node { { "seven", 7 }, { "eight", 8 }, { "nine", 9 } } },
                }
            },
            { "c", Node {} },
        };
    }
    

    没有 Make-Recursive-Variant

    您可以通过判断良好的前锋声明:

    Live On Coliru

    #include <boost/variant.hpp>
    #include <unordered_map>
    
    struct Data {
        int _i;
        Data(int i) : _i(i) {}
    }; // just for illustration
    
    struct Node;
    
    using Tree = boost::variant<Data, boost::recursive_wrapper<Node> >;
    
    struct Node : std::unordered_map<std::string, Tree> {
        using base = std::unordered_map<std::string, Tree>;
        using base::base; // inherit constructor
    };
    
    int main() {
        Tree tree = Node {
            { "a", Node { { "one", 1 }, { "two", 2 }, { "three",3 } } },
            { "b", Node {
                    { "b1", Node { { "four", 4 }, { "five", 5 }, { "six", 6 } } },
                    { "b2", Node { { "seven", 7 }, { "eight", 8 }, { "nine", 9 } } },
                }
            },
            { "c", Node {} },
        };
    }
    

    更优雅+更高效

    如果您使用可以在映射类型仍然不完整时实例化的unordered_map,则根本不需要recursive_wrapper 的性能影响。

    在这个过程中,我们可以让构造函数更加智能,树的构造更加简洁:

    Live On Coliru

    #include <boost/variant.hpp>
    #include <boost/unordered_map.hpp>
    
    struct Data {
        int _i;
        Data(int i = 0) : _i(i) {}
    }; // just for illustration
    
    struct Node : boost::variant<Data, boost::unordered_map<std::string, Node> > {
        using Map = boost::unordered_map<std::string, Node>;
        using Base = boost::variant<Data, Map>;
    
        using Base::variant;
        using Base::operator=;
    
        Node(std::initializer_list<Map::value_type> init) : Base(Map(init)) {}
    };
    
    int main() {
        auto tree = Node {
            { "a", { { "one", 1 }, { "two", 2 }, { "three", 3 } } },
            { "b", {
                    { "b1", { { "four", 4 }, { "five", 5 }, { "six", 6 } } },
                    { "b2", { { "seven", 7 }, { "eight", 8 }, { "nine", 9 } } },
                }
            },
            { "c", {} },
        };
    }
    

    ¹(我认为 c++17 将其添加到标准库规范中)

    【讨论】:

    • 感谢您的详细回复。针对您的提示,至于为什么将 LeafNodes 与 InnerNodes 区分开来,是因为我希望如何构造和遍历树。树的结构是不知道的,而是动态增长的。叶子实际上包含一个原子计数器的映射,我需要为每个叶子创建相同的 cntr 集。那些 cntr 存在于 InnerNodes 是没有意义的,尽管我考虑在 InnerNodes 有不同类型的 cntr,但是必须提前知道计数器的集合(映射),以便在不存在的情况下创建叶子
    • 如果我还希望将浮点数作为叶数据类型,最后一个示例的外观如何?
    • @Reza II 假设您的问题在于 int/float 之间的隐式转换?感谢 C 遗产霸主的那一点好运(咳)。也许您可以使用 UDL 减轻痛苦? coliru.stacked-crooked.com/a/e13ba6511fce0686
    • @sehe 我尝试add a std::vector type,但初始化列表失败。使用[] 设置值也很好。
    【解决方案2】:

    你需要一个指针,变体要么是叶子数据,要么是指向另一个叶子的指针。

    【讨论】:

    • 您有正确的想法,但没有正确的经验来提供有用的答案。 Boost 促进了您所描述的内容,因此您不必 必须处理指针(以及与之相关的正确性/异常安全问题)。此外,在我的回答中,您首先看到了避免间接的方法[因为所选容器是基于节点的,因此用作间接点]。
    【解决方案3】:

    我能够使用 boost::make_recursive_variant 方法,但它需要一个额外的处理程序来处理变体访问者。

    using LeafData = int; // just for illustration
    using LeafNode = std::unordered_map<std::string, LeafData>;
    using InnerNode = std::unordered_map<std::string, boost::make_recursive_variant<boost::recursive_variant_, LeafNode>::type>;
    

    变体访问者需要处理三种类型,LeafNode、InnerNode 和 AND,InnerNode::mapped_type(这是递归变体),然后编译器似乎很高兴。不确定这是否会“工作”,因此需要运行并查看行为方式。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-04-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-22
      • 1970-01-01
      • 2021-08-02
      • 1970-01-01
      相关资源
      最近更新 更多