Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

stl

boost

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?

like image 247
Max Avatar asked Jul 29 '13 02:07

Max


People also ask

What is boost :: variant?

Boost. Variant, part of collection of the Boost C++ Libraries. It is a safe, generic, stack-based discriminated union container, offering a simple solution for manipulating an object from a heterogeneous set of types in a uniform manner.

What is the point of std :: variant?

The class template std::variant represents a type-safe union. An instance of std::variant at any given time either holds a value of one of its alternative types, or in the case of error - no value (this state is hard to achieve, see valueless_by_exception).

Which of the following are STL containers types?

The three types of containers found in the STL are sequential, associative and unordered.

What is container explain different container supported by STL?

A container is an object that stores a collection of objects of a specific type. For example, if we need to store a list of names, we can use a vector . C++ STL provides different types of containers based on our requirements.


2 Answers

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;
}
like image 126
Santi Gil Avatar answered Oct 15 '22 12:10

Santi Gil


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.

like image 36
Potatoswatter Avatar answered Oct 15 '22 13:10

Potatoswatter