是否可以使用包含boost :: variant的STL容器并在变体类型本身中使用相同的容器类型?

时间:2013-07-29 02:52:22

标签: c++ boost stl

这似乎不太可能,但我想我还是会问。

我已经定义了boost::variant这样:

typedef boost::variant<double, int, std::string> ConfigVariant;

稍后在我的代码中,我定义了std::map,如下所示:

std::map<std::string, ConfigVariant> my_map;

现在我希望std::map<std::string, ConfigVariant>内有my_map个值。例如,我想这样做:

my_map[key1][key2] = "hello world";

我认为这是不可能的原因是因为看起来相应的变体定义看起来像这样:

typedef boost::variant<double, int, std::string, std::map<std::string, ConfigVariant> ConfigVariant;

由于这样的类型定义是不可能的,有什么方法可以解决这个问题吗?

4 个答案:

答案 0 :(得分:7)

官方文档有recursive variant types部分。它解释了两种方法:使用boost::recursive_wrapperboost::make_recursive_variant。我不确定是否可以用recursive_wrapper定义这种递归(我从来没有亲自去过,但我远非专家)。与make_recursive_variant相比,它非常简单:您只需要使用boost::recursive_variant_替换递归变体类型,然后使用::type来评估元函数并获得所需的类型。

typedef boost::make_recursive_variant<
                    double,
                    int, 
                    std::string,
                    //std::map<std::string,ConfigVariant>
                    std::map<std::string,boost::recursive_variant_>
        >::type ConfigVariant;

Running on coliru

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

#include <boost/variant.hpp>

typedef boost::make_recursive_variant<double, int, std::string, std::map<std::string, boost::recursive_variant_> >::type ConfigVariant;

struct printer : boost::static_visitor<>
{
    void operator()(int val) const
    {
        std::cout << val;
    }
    void operator()(double val) const
    {
        std::cout << val;
    }
    void operator()(const std::string& val) const
    {
        std::cout << val;
    }
    void operator()(const std::map<std::string,ConfigVariant>& val) const
    {
        std::cout << "map_of{ ";
        for(std::map<std::string,ConfigVariant>::const_iterator it=val.begin(),end=val.end(); it!=end; ++it)
        {
            boost::apply_visitor(*this,it->second);
            std::cout << " ";
        }
        std::cout << "}";   
    }
};


int main()
{
    ConfigVariant intconf=1;
    ConfigVariant doubleconf=1.2;
    ConfigVariant stringconf="conf";
    std::map<std::string, ConfigVariant> mapconf, mapconf2;
    mapconf["int"]=intconf;
    mapconf["string"]=stringconf;
    mapconf2["map"]=mapconf;
    mapconf2["double2"]=doubleconf;
    ConfigVariant visitable=mapconf2;

    boost::apply_visitor(printer(), visitable);
    std::cout << std::endl;
}

答案 1 :(得分:4)

这个问题与boost::variant没有任何关系;你只是想用标准容器制作一个n-ary树。

答案是否定的,因为标准容器需要将完整类型用作模板参数。容器不能包含自身,因为正如您所观察到的,定义将是递归的。它的构造函数会预先假定它的构造函数已经存在。结果将是不完整的类型错误。

作为一种特殊情况,实际上std::vector实现通常允许这样做。在vector的类定义完成之前,构造函数(以及需要完整元素类型的任何其他内容)实际上不会被实例化。并且可以实现所有标准容器以使其以相同的方式工作。但标准并不要求它。

另见Can standard container templates be instantiated with incomplete types?;这也包含一个解决方法。要使变通方法适用于variant,这需要一个完整的类型,我建议将不完整的类型包装在std::unique_ptr中。

答案 2 :(得分:0)

这听起来像你想要的:

typedef boost::variant<double, int, std::string> ConfigVariant;
std::map<std::string, std::map<std::string, ConfigVariant> > my_map;

这样可以访问表单:

my_map["key 1"]["key 2"] = "hello world";

但不是形式:

my_map["key 1"] = "hello world";

答案 3 :(得分:0)

使用boost::any可以帮助您。 我也在半个小时前写了this code,这也适用于你的情况,而不是boost::variant。 它基本上只是一个美化的void*指针,但带有类型检查断言。我猜boost::any也只是一个空洞*,但我不确定。 我忘记了我的代码没有取得数据的所有权(按意图) - 你必须修改,如果你想使用它。这可能很难。 boost :: any取得所有权,因此这可能是更好的选择。

您的代码将是:

typedef std::map<std::string, boost::any> ConfigMap;

或使用智能指针:

struct Data;

typedef std::map<std::string, std::unique_ptr<Data> > ConfigMap;

struct Data
{
    boost::variant<blah> value;
    ConfigMap map;
};

将其视为文件夹结构。文件夹包含文件,它们也可以包含文件夹。

这是在Ideone.com编译的。不过,它会更好地包含在用户友好的课程中。