Storing any value in a container/variable
If you have been programming in Java, C#, or Delphi, you will definitely miss the ability of creating containers with the Object value type in C++. The Object class in those languages is a basic class for almost all types, so you are able to assign almost any value to it at any time. Just imagine how great it would be to have such a feature in C++:
typedef std::unique_ptr<Object> object_ptr;
std::vector<object_ptr> some_values;
some_values.push_back(new Object(10));
some_values.push_back(new Object("Hello there"));
some_values.push_back(new Object(std::string("Wow!")));
std::string* p = dynamic_cast<std::string*>(some_values.back().get());
assert(p);
(*p) += " That is great!\n";
std::cout << *p; Getting ready
We'll be working with the header-only library. The basic knowledge of C++ is all you need for this recipe.
How to do it...
Boost offers a solution, the Boost.Any library, that has an even better syntax:
#include <boost/any.hpp>
#include <iostream>
#include <vector>
#include <string>
int main() {
std::vector<boost::any> some_values;
some_values.push_back(10);
some_values.push_back("Hello there!");
some_values.push_back(std::string("Wow!"));
std::string& s = boost::any_cast<std::string&>(some_values.back());
s += " That is great!";
std::cout << s;
} Great, isn't it? By the way, it has an empty state, which could be checked using the empty() member function (just like in standard library containers).
You can get the value from boost::any using two approaches:
void example() {
boost::any variable(std::string("Hello world!"));
// Following method may throw a boost::bad_any_cast exception
// if actual value in variable is not a std::string.
std::string s1 = boost::any_cast<std::string>(variable);
// Never throws. If actual value in variable is not a std::string
// will return an NULL pointer.
std::string* s2 = boost::any_cast<std::string>(&variable);
}How it works...
The boost::any class just stores any value in it. To achieve this, it uses the type erasure technique (close to what Java or C# does with all types). To use this library you do not really need to know its internal implementation in detail, but here's a quick glance at the type erasure technique for the curious.
On the assignment of some variable of type T, Boost.Any instantiates a holder<T> type that may store a value of the specified type T and is derived from some base-type placeholder:
template<typename ValueType>
struct holder : public placeholder {
virtual const std::type_info& type() const {
return typeid(ValueType);
}
ValueType held;
};A placeholder type has virtual functions for getting std::type_info of a stored type T and for cloning a stored type:
struct placeholder {
virtual ~placeholder() {}
virtual const std::type_info& type() const = 0;
};boost::any stores ptr-- a pointer to placeholder. When any_cast<T>() is used, boost::any checks that calling ptr->type() gives std::type_info equal to typeid(T) and returns static_cast<holder<T>*>(ptr)->held.
There's more...
Such flexibility never comes without any cost. Copy constructing, value constructing, copy assigning, and assigning values to instances of boost::any do dynamic memory allocation; all the type casts do RunTime Type Information (RTTI) checks; boost::any uses virtual functions a lot. If you are keen on performance, the next recipe will give you an idea of how to achieve almost the same results without dynamic allocations and RTTI usage.
boost::any makes use of rvalue references but can not be used in constexpr.
The Boost.Any library was accepted into C++17. If your compiler is C++17 compatible and you wish to avoid using Boost for any, just replace the boost namespace with namespace std and include <any> instead of <boost/any.hpp>. Your standard library implementation may work slightly faster if you are storing tiny objects in std::any.
Note
std::any has the reset() function instead of clear() and has_value() instead of empty(). Almost all exceptions in Boost derived from the std::exception class or from its derivatives, for example, boost::bad_any_cast is derived from std::bad_cast. It means that you can catch almost all Boost exceptions using catch (const std::exception& e).
See also
- Boost's official documentation may give you some more examples; it can be found at http://boost.org/libs/any
- The Using a safer way to work with a container that stores multiple chosen types recipe for more info on the topic