Using std::any to store values of any type
With std::optional you can represent some Type or nothing. With std::variant you can wrap several variants into one entity. And C++17 gives us one more wrapper type: std::any that can hold anything (any type) in an object, and reports errors (or throws exceptions) when you'd like to access a type that is not active.
A little demo:
std::any a(12);
// set any value:
a = std::string("Hello!");
a = 16;
// reading a value:
// we can read it as int
std::cout << std::any_cast<int>(a) << '\n';
// but not as string:
try
{
std::cout << std::any_cast<std::string>(a) << '\n';
}
catch(const std::bad_any_cast& e)
{
std::cout << e.what() << '\n';
}
// reset and check if it contains any value:
a.reset();
if (!a.has_value())
{
std::cout << "a is empty!" << "\n";
}
// you can use it in a container:
std::map<std::string, std::any> m;
m["integer"] = 10;
m["string"] = std::string("Hello World");
m["float"] = 1.0f;
for (auto &[key, val] : m)
{
if (val.type() == typeid(int))
std::cout << "int: " << std::any_cast<int>(val) << "\n";
else if (val.type() == typeid(std::string))
std::cout << "string: " << std::any_cast<std::string>(val) << "\n";
else if (val.type() == typeid(float))
std::cout << "float: " << std::any_cast<float>(val) << "\n";
}
A true variable type in C++? If you like JavaScript then you can even make all of your variables std::any and use C++ like JavaScript :)
Main Features
std::anyis not a template class likestd::optionalorstd::variant.- by default it contains no value, and you can check it via
.has_value(). - you can reset an
std::anyobject via.reset(). - it works on decayed types - so before assignment, initialization, emplacement the type is transformed by
std::decay. - when a different type is assigned, then the active type is destroyed.
- you can access the value by using
std::any_cast<T>, it will throwbad_any_castif the active type is notT. - you can discover the active type by using
.type()that returnsstd::type_infoof the type.
Some of its Uses
While void* seems an extremely unsafe pattern with some limited use cases, std::any adds type-safety and thus has some real use cases. Such as
- In Libraries when a library type has to hold or pass anything without knowing the set of available types.
- Parsing files if you really cannot specify what are the supported types.
- Message passing
- Bindings with a scripting language
- Implementing an interpreter for a scripting language
- User Interface controls might hold anything
- Entities in an editor
Still, in a lot of cases where we can limit the set of supported types std::variant would be a better choice. (Of course, it gets tricky when you implement a library without knowing the final applications, so you don’t know the possible types that will be stored in an object.)
Creation of std::any
There are several ways you can create std::any object:
- a default initialization: then the object is empty
- a direct initialization with a value/object
- in place
std::in_place_type - via
std::make_any
You can see in the following example:
// default initialization:
std::any a;
assert(!a.has_value());
// initialization with an object:
std::any a2(10); // int
std::any a3(MyType(10, 11));
// in_place:
std::any a4(std::in_place_type<MyType>, 10, 11);
std::any a5{std::in_place_type<std::string>, "Hello World"};
// make_any
std::any a6 = std::make_any<std::string>("Hello World");
Changing the Value Held
When you want to change the currently stored value in std::any then you have two options: use member emplace or the assignment:
std::any a;
a = MyType(10, 11);
a = std::string("Hello");
a.emplace<float>(100.5f);
a.emplace<std::vector<int>>({10, 11, 12, 13});
a.emplace<MyType>(10, 11);
Accessing The Stored Value
In order to read the currently active value in std::any you have essentially one option: std::any_cast. This function returns the value of the requested type if it's in the object.
This function template though is quite powerful, as it has many ways of using:
- to return a copy of the value, and throw
std::bad_any_castwhen it fails - to return a reference (also writable), and throw
std::bad_any_castwhen it fails - to return a pointer to the value (const or not) or
nullptron failure
struct MyType
{
int a, b;
MyType(int x, int y) : a(x), b(y) { }
void Print() { std::cout << a << ", " << b << "\n"; }
};
int main()
{
std::any var = std::make_any<MyType>(10, 10);
try
{
std::any_cast<MyType&>(var).Print();
std::any_cast<MyType&>(var).a = 11; // read/write
std::any_cast<MyType&>(var).Print();
std::any_cast<int>(var); // throw!
}
catch(const std::bad_any_cast& e)
{
std::cout << e.what() << '\n';
}
int* p = std::any_cast<int>(&var);
std::cout << (p ? "contains int... \n" : "doesn't contain an int...\n");
MyType* pt = std::any_cast<MyType>(&var);
if (pt)
{
pt->a = 12;
std::any_cast<MyType&>(var).Print();
}
}
As you see you have two options regarding error handling: via exceptions (std::bad_any_cast) or by returning a pointer (or nullptr). The function overloads for std::_any_cast pointer access is also marked with noexcept.