清除具有未知递归级别的嵌套关联容器的初始化

时间:2017-02-25 01:46:27

标签: c++ c++17

基本上我想要一些像std::unordered_map<std::string, std::variant<unsigned, /*self_type*/>>类似的容器。在此容器中,无符号值是终端节点,而self_type表示子树,应进一步搜索直到终端节点。

然而,这可以通过一个额外的包装类来实现。

struct node {
    std::unordered_map<std::string, 
                       std::variant<unsigned, std::unique_ptr<node>>> children;
};

足够公平,但我想用嵌套的intializer列表将其初始化为普通std::unordered_map。例如:

{
    {
        "str1",
        {
            {"strstr1", 1},
            {"strstr2", 2}
        }
    },
    {"str2", 3}
}

欢迎提供更合适的数据结构建议。

1 个答案:

答案 0 :(得分:3)

解决方案1 ​​ - 使用包装类:

struct node {
    using myvar = boost::variant< unsigned, boost::recursive_wrapper< node > >;
    using childmap = std::unordered_map< std::string, myvar >;

    node() {}

    node( std::initializer_list< childmap::value_type > il ) :
        children( il ) {}

    childmap children;
};

我在这里使用boost :: variant,因为我没有std::variant可用。 boost::recursive_wrapper是必需的,因为boost::variant通常需要完整的类型,但此时node仍然不完整。

boost::recursive_wrapper毫无魔力。它只是一个指针的包装器!我们知道,可以为不完整类型声明指针而不会出现问题。这个包装类只是隐藏了一个事实,即通过处理分配,释放和提供值语义来使用指针。它具有boost::variant的特殊支持,使得包装器完全透明,因此可以使用变体,就好像根本没有包装类一样。

用法:

node n {
    { "foo", 1 },
    { "bar", 2 },
    { "baz", node {
        { "bim", 3 },
        { "bam", 4 }}
    }
};

n.children[ "fum" ] = 5;
n.children[ "fup" ] = node{{ "fap", 6 }};

初始化列表中的显式“节点”是必需的,因为变体构造函数不能从嵌套的初始化列表中推断出类型。

演示:http://coliru.stacked-crooked.com/a/123c59a3523c39ed

解决方案2 - 从unordered_map派生:

这消除了对“儿童”成员的需要。

struct nodemap :
    std::unordered_map< 
        std::string, 
        boost::variant< unsigned, boost::recursive_wrapper< nodemap > > >
{
    using base = std::unordered_map< 
        std::string, 
        boost::variant< unsigned, boost::recursive_wrapper< nodemap > > >;

    // Forward all constructors of the base class.
    using base::base;
};

用法:

nodemap n{
    { "foo", 1 },
    { "bar", 2 },
    { "baz", nodemap{
        { "bim", 3 },
        { "bam", 4 }}
    }};

n[ "fum" ] = 5;
n[ "fup" ] = nodemap{{ "fap", 6 }};

更多用法示例:

// Add something to a child nodemap. 
boost::get<nodemap>( n[ "baz" ] )[ "fap" ] = 7;

// This will throw a boost::bad_get exception because n[ "foo" ] is not a nodemap.
//boost::get<nodemap>( n[ "foo" ] )[ "fap" ] = 8;

// To avoid this problem, we can check if the child actually is a nodemap:
if( nodemap* pn = boost::get<nodemap>( &n[ "foo" ] ) )
{
    (*pn)[ "fap" ] = 8; 
}

演示:http://coliru.stacked-crooked.com/a/69914ec5646129f2