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;
We'll be working with the header-only library. The basic knowledge of C++ is all you need for this recipe.
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); }
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
.
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)
.
- 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