The C++ Boost Libraries Part 6 - boost::any

Andrew Stephens, Sunday the 6th of December, 2009

In C++ if you have a variable that you say is of type "Person" (for instance), you can be fairly certain (more or less) that it always actually contains a Person (or perhaps a subclass of Person This post was automatically imported from my old sandfly.net.nz blog. It may look a little weird since it was not originally written for this format.. If you have a container of Persons, then you know (more or less) that every member is also a Person (or a subclass).

This is all very good, prevents a lot of runtime errors, and generally makes C++ a great language if you care about correctness. But sometimes, very rarely, you actually want to store a whole bunch of messy, unrelated types in a container without trying to ram them into some sort of class hierarchy. Parsers are a good example of this. It is often convenient just to chuck tokens of various types into a data structure for later processing without worrying too much about the specific type (string, int, float, etc).

boost::any is a small class that can hold values from almost any type, designed for just such messy applications. Using boost::any is very simple:

boost::any a1 = std::string("Moose");
boost::any a2 = 6;

Of course, getting the values back again is a little harder.

try
{
    std::string v1 = boost::any_cast< std::string >(a1); // this works, a1 is a string
    std::string v2 = boost::any_cast< std::string >(a2); // nope, will throw an exception at runtime
}
catch ( const boost::bad_any_cast& e )
{
    // tried to any_cast into something that wouldn't go
}

Of course, you can query a boost::any for the typeid of the stored object. Just don't do it when Scott Meyers is in the vicinity.

std::string v;
if ( a1.type() == typeid(std::string) )
{
    v = boost::any_cast< std::string >( a1 ); // this should never throw, since we checked first
}

A single boost::any is perhaps not that useful, but a container of them can store almost anything we want:

typedef std::vector< boost::any > AnyVector;
AnyVector values;

values.push_back( 5 );
values.push_back( std::string("Hello") );
values.push_back( 5.3 );

try 
{
    for ( AnyVector::const_iterator p = values.begin();
            p != values.end();
            ++p )
    {
        if ( p->type() == typeid(int) )
            cout << "Int = " << any_cast<int>(*p) << endl;
        else if ( p->type() == typeid(std::string) )
            cout << "String = " << any_cast<string>(*p) << endl;
        else 
        {
            cout << "Unhandled type: " << p->type().name() << endl;
        }
    }
} 
catch ( const boost::bad_any_cast &e )
{
    cout << "Bad any_cast<>" << e.what() << endl;
}

Any type that you put into a boost::any must be copy constructable (the any holds a copy, not a reference). You also have to make sure that its destructor doesn't throw (but of course you do that anyway!)

Although I wouldn't recommend boost::any for everyday use, it does come into its own when the only alternative is a huge class structure or (even worse) void *s.