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
With the help of std::optionalyou can store one kind of type. With the help of std::variantyou can store several types in one object. And C ++ 17 provides us with another such wrapper type - std::anywhich 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 MyAnywe 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::anyfrom 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 variablesstd::anyand 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::anycompletely 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 itstd::variantmay 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::anyare two ways to change the value that is currently stored in : method emplaceor 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::anyis the lack of leakage of resources. To achieve this, it will std::anydestroy 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::anyinitialized 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_caston 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_castfor returning pointers is overloaded and marked as noexcept.Performance and memory usage
std::anyIt 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::optionaldoes 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::anythis 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::anyIt 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::anydoes 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::anyis flexibility. In the examples below, you can see some ideas (or specific implementations) where using std::anyit 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::anyfor 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 wParamor lParamin 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::anynot a template class
- std::anyuses 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 used
- std::anycan be called "heavy", but it offers safety and greater flexibility
- Access to the data std::anycan be obtained with the help ofany_castwhich 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