Hello, Habr! We present to your attention a translation of the article “Everything You Need to Know About std :: any from C ++ 17” by Bartlomiej Filipek .
With the help of std::optional
you can store one kind of type. With the help of std::variant
you can store several types in one object. And C ++ 17 provides us with another such wrapper type - std::any
which can store anything while remaining type-safe.The basics
Prior to this, the C ++ standard did not provide many solutions to the problem of storing several types in one variable. Of course you can use void*
, but it is not at all safe.Theoretically, void*
you can wrap it in a class where you can somehow store the type:class MyAny
{
void* _value;
TypeInfo _typeInfo;
};
As you can see, we got a certain basic form std::any
, but to ensure type safety MyAny
we need additional checks. That is why it is better to use an option from the standard library than to make your own decision.And this is what it is std::any
from C ++ 17. It allows you to store anything in the object and reports an error (throws an exception) when you try to access by specifying the wrong type.Little demo:std::any a(12);
a = std::string("Hello!");
a = 16;
std::cout << std::any_cast<int>(a) << '\n';
try
{
std::cout << std::any_cast<std::string>(a) << '\n';
}
catch(const std::bad_any_cast& e)
{
std::cout << e.what() << '\n';
}
a.reset();
if (!a.has_value())
{
std::cout << "a is empty!" << "\n";
}
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";
}
This code will output:16
bad any_cast
a is empty!
float: 1
int: 10
string: Hello World
The example above shows some important things:std::any
— std::optional
std::variant
- ,
.has_value()
.reset()
std::decay
- ,
std::any_cast
, bad_any_cast
, «T».type()
, std::type_info
The example above looks impressive - a real type variable in C ++! If you really like JavaScript, you can even make all your type variables std::any
and use C ++ as JavaScript :)But maybe there are some normal usage examples?When to use?
While it is void*
perceived by me as a very unsafe thing with a very limited range of possible uses, it is std::any
completely type-safe, so it has some good ways to use it.For instance:- In libraries - when your library needs to store or transfer some data, and you do not know what type this data can be
- When parsing files - if you really can’t determine what types are supported
- Messaging
- Scripting Language Interaction
- Creating an interpreter for a scripting language
- User Interface - Fields Can Store Anything
It seems to me that in many of these examples we can highlight a limited list of supported types, so it std::variant
may be a better choice. But of course it is difficult to create libraries without knowing the final products in which it will be used. You just don’t know what types will be stored there.The demonstration showed some basic things, but in the following sections you will learn more about std::any
, so keep reading.Create std :: any
There are several ways to create an object of type std::any
:- standard initialization - object is empty
- direct initialization with value / object
- directly indicating the type of object -
std::in_place_type
- via
std::make_any
For instance:
std::any a;
assert(!a.has_value());
std::any a2(10);
std::any a3(MyType(10, 11));
std::any a4(std::in_place_type<MyType>, 10, 11);
std::any a5{std::in_place_type<std::string>, "Hello World"};
std::any a6 = std::make_any<std::string>("Hello World");
Change value
There std::any
are two ways to change the value that is currently stored in : method emplace
or 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);
Object Life Cycle
The key to security std::any
is the lack of leakage of resources. To achieve this, it will std::any
destroy any active object before assigning a new value.std::any var = std::make_any<MyType>();
var = 100.0f;
std::cout << std::any_cast<float>(var) << "\n";
This code will output the following:MyType::MyType
MyType::~MyType
100
The object is std::any
initialized with an object of type MyType, but before assigning a new value (100.0f), the destructor is called MyType
.Gaining access to a value
In most cases, you have only one way to access the value in std::any
- std::any_cast
, it returns the values of the specified type if it is stored in the object.This feature is very useful, as it has many ways to use it:- return a copy of the value and quit
std::bad_any_cast
on error - return a link to the value and quit
std::bad_any_cast
on error - return a pointer to a value (constant or not) or nullptr in case of an error
See an example: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;
std::any_cast<MyType&>(var).Print();
std::any_cast<int>(var);
}
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 can see, we have two ways to track errors: through exceptions ( std::bad_any_cast
) or returning a pointer (or nullptr
). The function std::any_cast
for returning pointers is overloaded and marked as noexcept
.Performance and memory usage
std::any
It looks like a powerful tool, and you will most likely use it to store data of different types, but what is the price of it?The main problem is the extra memory allocation.std::variant
and std::optional
does not require any additional memory allocations, but this is because the types of data stored in the object are known in advance. std :: any does not have such information, so it can use additional memory.Will this happen always or sometimes? Which rules? Will this even happen with simple types like int?Let's see what the standard says:Implementations should avoid the use of dynamically allocated memory for a small contained value. Example: where the object constructed is holding only an int. Such small-object optimization shall only be applied to types T for which is_nothrow_move_constructible_v is true
An implementation should avoid using dynamic memory for small-sized stored data. For example, when an object is created storing only int. Such optimization for small objects should only be applied to types T for which is_nothrow_move_constructible_v is true.
As a result, they propose to use Small Buffer Optimization / SBO for implementations. But this also has a price. This makes the type larger - to cover the buffer.Let's look at the size std::any
, here are the results from several compilers:In general, as you can see, std::any
this is not a simple type, and it brings additional costs. It usually takes up a lot of memory, due to SBO, from 16 to 32 bytes (in GCC or clang ... or even 64 bytes in MSVC!).Migrating from boost :: any
boost::any
It was introduced somewhere in 2001 (version 1.23.0). In addition, the author boost::any
(Kevlin Henney) is also the author of the proposal std::any
. Therefore, these two types are closely related, the version from STL is strongly based on its predecessor.Here are the main changes:The main difference is that it boost::any
does not use SBO, so it takes up significantly less memory (in GCC8.1 its size is 8 bytes), but because of this, it dynamically allocates memory even for such small types as int.Examples of using std :: any
The main plus std::any
is flexibility. In the examples below, you can see some ideas (or specific implementations) where using std::any
it makes the application a little easier.File parsing
In the examples to std::variant
( you can see them here [eng] ) you could see how you can parse configuration files and store the result in a type variable std::variant
. Now you are writing a very general solution, maybe it is part of some library, then you may not be aware of all the possible types of types.Storing data using std::any
for parameters is likely to be quite good in terms of performance, and at the same time give you the flexibility of the solution.Messaging
In Windows Api, which is mainly written in C, there is a messaging system that uses message id with two optional parameters that store message data. Based on this mechanism, you can implement WndProc, which processes the message sent to your window.LRESULT CALLBACK WindowProc(
_In_ HWND hwnd,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
);
The fact is that the data is stored in wParam
or lParam
in different forms. Sometimes you only need to use a couple of bytes wParam
.What if we change this system so that the message can pass anything to the processing method?For instance:class Message
{
public:
enum class Type
{
Init,
Closing,
ShowWindow,
DrawWindow
};
public:
explicit Message(Type type, std::any param) :
mType(type),
mParam(param)
{ }
explicit Message(Type type) :
mType(type)
{ }
Type mType;
std::any mParam;
};
class Window
{
public:
virtual void HandleMessage(const Message& msg) = 0;
};
For example, you can send a message to the window:Message m(Message::Type::ShowWindow, std::make_pair(10, 11));
yourWindow.HandleMessage(m);
A window can reply to a message like this:switch (msg.mType) {
case Message::Type::ShowWindow:
{
auto pos = std::any_cast<std::pair<int, int>>(msg.mParam);
std::cout << "ShowWidow: "
<< pos.first << ", "
<< pos.second << "\n";
break;
}
}
Of course, you have to determine how the data type is stored in messages, but now you can use real types instead of different tricks with numbers.Properties
The original document that any represents for C ++ (N1939) shows an example of a property object:struct property
{
property();
property(const std::string &, const std::any &);
std::string name;
std::any value;
};
typedef std::vector<property> properties;
This object looks very useful because it can store many different types. The first one that comes to my mind is an example of using it in a user interface manager or in a game editor.We pass through the borders
In r / cpp there was a stream about std :: any. And there was at least one great comment that summarizes when a type should be used.From this comment :The bottom line is that std :: any allows you to transfer rights to arbitrary data across borders that do not know about its type.
Everything I talked about before is close to this idea:- in the library for the interface: you do not know what types the client wants to use there
- messaging: the same idea - give the customer flexibility
- file parsing: to support any type
Total
In this article, we learned a lot about std::any
!Here are some things to keep in mind:std::any
not a template classstd::any
uses optimization of small objects, so it will not dynamically allocate memory for simple types such as int or double, and for larger types, additional memory will be usedstd::any
can be called "heavy", but it offers safety and greater flexibility- Access to the data
std::any
can be obtained with the help of any_cast
which offers several "modes". For example, in case of an error, it may throw an exception or just return nullptr - use it when you don’t know exactly what data types are possible, otherwise consider using
std::variant