5

This seems like it may be impossible, but I thought I would ask anyway.

I have defined a boost::variant like this:

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

Later in my code I define a std::map like this:

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

Now I would like to be able to have std::map<std::string, ConfigVariant> values inside my_map. For example, I would like to do this:

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

The reason I think this is impossible is because it seems like the corresponding variant definition would look like this:

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

Since making such a type definition would be impossible, is there any way around this?

Max
  • 15,157
  • 17
  • 82
  • 127

4 Answers4

7

The official documentation has a section on recursive variant types. It explains two approaches: using boost::recursive_wrapper and boost::make_recursive_variant. I'm not sure that it is possible to define this kind of recursion with recursive_wrapper (I have never been able to personally, but I'm far from an expert). In contrast with make_recursive_variant it is really easy: you just need to replace your recursive variant type with boost::recursive_variant_ and then use ::type to evaluate the metafunction and get the type you want.

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;
}
llonesmiz
  • 155
  • 2
  • 11
  • 20
4

The question doesn't really have anything to do with boost::variant; you are simply asking to make an n-ary tree using a standard container.

The answer is no, because the standard containers require complete types to be used as their template arguments. A container cannot contain itself because, as you observed, the definition would be recursive. Its constructor would presuppose that its constructor already existed. The result would be an incomplete type error.

As a special case, in fact std::vector implementations often do allow this to be done. The constructor (and anything else requiring a complete element type) is not actually instantiated until the class definition of vector is complete. And all the standard containers could be implemented to make it work in the same way. But it's not required by the Standard.

See also Can standard container templates be instantiated with incomplete types? ; this also contains a workaround. To make the workaround apply to variant, which requires a complete type by itself, I'd suggest wrapping the incomplete type in std::unique_ptr.

Community
  • 1
  • 1
Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
0

It sounds like you want:

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

This would enable access of the form:

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

But not of the form:

my_map["key 1"] = "hello world";
Bill Lynch
  • 80,138
  • 16
  • 128
  • 173
0

Using boost::any would make that work for you. I also just a half-hour ago wrote this code, which would also work in your situation in place of boost::variant. It's basically just a glorified void* pointer, but with type-checking asserts. I'd guess boost::any is also just a void* under the hood, but I'm not sure. I forgot my code doesn't take ownership of the data (by intention) - you'll have to modify so it does if you wanted to use it. That might prove difficult. boost::any takes ownership, so that might be the better option.

Your code would then be:

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

Or using smart pointers:

struct Data;

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

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

Think of it like a folder structure. Folders contain files, and they can also contain folders.

Here's it compiling at Ideone.com. It'd be nicer wrapped in a user-friendly class though.

Jamin Grey
  • 10,151
  • 6
  • 39
  • 52