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

Is it possible to use an STL container containing a boost::variant and use the same container type in the variant type itself?

本文关键字:类型 boost 包含 可以使 STL 容器 是否      更新时间:2023-10-16

这似乎是不可能的,但我想我无论如何都会问。

我定义了一个boost::variant,如下所示:

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

在我的代码后面,我定义了一个std::map,如下所示:

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

现在我希望能够在my_map中具有std::map<std::string, ConfigVariant>值。例如,我想这样做:

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

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

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

既然不可能做出这样的类型定义,有什么办法可以解决这个问题吗?

官方文档中有一节介绍递归变量类型。它解释了两种方法:使用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;

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

这个问题实际上与boost::variant无关;您只是要求使用标准容器制作一个n元树。

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

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

另请参阅是否可以用不完整的类型实例化标准容器模板;这也包含一个变通方法。为了将该解决方法应用于variant,它本身需要一个完整的类型,我建议将不完整的类型封装在std::unique_ptr中。

听起来你想要:

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";

使用boost::any将使其适用于您。我半小时前也写了这段代码,它也可以代替boost::variant在您的情况下工作。它基本上只是一个荣耀的void*指针,但带有类型检查断言。我想boost::any也只是引擎盖下的一个空白,但我不确定我忘了我的代码并没有(故意)拥有数据的所有权——如果你想使用它,你必须修改它。这可能会很困难。boost:任何人都拥有所有权,所以这可能是更好的选择。

然后你的代码是:

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上编译的。不过,它最好封装在一个用户友好的类中。

相关文章: