In the previous two posts, I talked about std::variant<> and std::optional<>. Today, I want to take up std::any for discussion.
The type any (implemented by the class any) allows a variable to hold a single value of any type. More interestingly, the type of the value held by a variable of type any can even change dynamically. In this sense, it is different from variant<> and optional<>.
We have to include the header <any> in order to use this type.
Let us start with a simple example and explore the various features of this type. Consider the following code snippet.
Here we create two variables of any type, one initialized with integer value and the other with a string. To retrieve the contained value of the variable, we need to use the templated any_cast<>() function. This function can also be used to assign a new value to the variable, but of the same type as the existing value.
It is OK to define a variable of any type without initializing it; the value could be set later. We can use the member function has_value() to check if the variable has a value. See the following example.
Notice how we can directly assign a value to the variable. We do not have to use any_cast<T>(v) as we did in the earlier example.
The next example given below shows how we can assign different value types to the same any type variable.
It is important to remember that when we use any_cast<T>(v), we have to use the current value type.
What happens if we use any_cast<T>(v) to access a variable, but the type we specify is different from the current value type? This causes an exception of type bad_any_cast to be thrown. See this code fragment:
It is possible to destroy the value contained in an any type variable using the reset() member function. Once reset, the variable holds no value. This is illustrated below.
So far, we have shown the any type variable being used with primitive value types such as integer. Can the type hold values of user-defined types? Yes, of course. Here is how:
There is one requirement when we use user-defined types with any. And that is, the user-defined type must be copy constructible. In other words, it must have a copy constructor. In the example below, struct Y has a deleted copy constructor, and hence it cannot be used with any type.
Here is another cool thing. Normally collections such as arrays and vectors require that the contained elements belong to the same type. Of course, we can use inheritance and pointers to store objects belonging to the same hierarchy. But when you create an array or vector of any type, you can actually store objects of unrelated types! This is illustrated in the following example.
Notice how we can use the type() member function of any to get the type description of the contained object.
As the last point to discuss, let us see if there is any space overhead in using any type. The following code fragment uses the sizeof() operator to print the number of bytes allocated to many objects of any type, each holding a different value type.
What is surprising is that each of the variables, irrespective of the respective value type, seems to have been allocated 40 bytes! Even if the variable is uninitialized, this is indeed the case.
Given the flexibility that any gives us in storing any value type, and even changing it dynamically, we are prepared for additional overhead, but why this much (constant value)? I am certain this is implementation dependent.
That is std::any for you. Hope you found the discussion interesting. The example source is available here. I tested this on VS 2017.
Recent Comments