Understanding the Use of std::any in C++ with an Example

C++ std::any is a type-safe container for single values of any type. It is useful to put multiple types into a single container such as std::vector which requires all elements stored have the same “type”. It is a part of the C++17 standard library. This blog post will take a close look at a certain piece of code that uses std::any and its related functions to handle various types in a vector.

Let’s break down the code piece by piece:

#include <any>
#include <iostream>
#include <vector>
#include <type_traits>

These are the standard library headers which are included:

  • <any>: This header includes the std::any class, which is a type-safe container for single values of any type.
  • <vector>: This header includes the std::vector class template that represents a dynamic array. We will put multiple std::any objects into a vector.
  • <type_traits>: This header includes templates that can be used to make compile-time conditional decisions based on types.
class A {
public:
    ~A () {
        std::cout << "A dstor\n";
    }
    A() {
    }
    A(const A&) = default;
};

This defines a simple class A with a default constructor, a copy constructor, and a destructor that outputs “A dstor” when called.

int main()
{
    std::cout << std::boolalpha;
    std::vector<std::any> vec;

    std::cout << std::is_standard_layout<std::vector<std::any>>() << std::endl;
  • std::boolalpha: This manipulator is used to print boolean values as “true” or “false” instead of “1” or “0”.
  • std::vector<std::any> vec;: This line declares a vector that can hold elements of any type.
  • std::is_standard_layout<std::vector<std::any>>(): This line checks if std::vector<std::any> is of standard layout, meaning all non-static data members have the same access control, the class has no virtual functions and no virtual base classes.
    std::any a = *(new int);
    vec.push_back(a);
    a = 9;
    vec.push_back(a);
  • std::any a = *(new int);: This line creates a new integer on the heap, dereferences it, and stores it in a variable of type std::any. However, this line leads to memory leak as there is no delete statement to deallocate the memory. A better approach would be std::any a = int();. We use the new here to illustrate std::any can work well with new.
  • vec.push_back(a);: This line adds the std::any variable a to the vector vec.
  • a = 9; vec.push_back(a);: These lines assign an int value to a, and then add a to vec again.
    a = A();
    vec.push_back(a);

The std::any variable a is assigned an instance of class A. It is also added to the vector vec.

OK, till now, we have added multiple elements of different types into the any vector. We can read them out and perform operations according to their types.

    std::cout << "size: " << vec.size() << std::endl;
    for (auto x : vec) {
        std::cout << x.type().name() << " ";
        std::any int_type = 8;
        if (x.type() == int_type.type()) {
            std::cout << std::any_cast<int>(x) << std::endl;
        } else {
            std::cout << "non int type" << std::endl;
        }
    }

vec.size() prints the size of the vector vec. Then the for loop iterates over the vector vec.

For each element x:

  • x.type().name(): This prints the name of the type of x.
  • Then do operation according to the type. We use int as an example.
    • std::any int_type = 8; declares a variable of type std::any and assigns it an int value. So we can use its type for type comparison.
    • If x is of type int, std::any_cast<int>(x) is used to retrieve the int value from x and print it. If x is not of type int, it prints “non int type”.

In summary, std::any can be a super abstract class that can contain any types. This code demonstrates how std::any can be used to store and manipulate values of any type in a type-safe way, and how to retrieve the value from an std::any object with std::any_cast. In the example code, we put elements of different types in a single vector, and then check the type during runtime and process the elements depending on their types.

Here is the C++ program putting all pieces together

#include <any>
#include <iostream>
#include <vector>
#include <type_traits>

class A {
public:
    ~A () {
        std::cout << "A dstor\n";
    }
    A() {
    }
    A(const A&) = default;
};

int main()
{
    std::cout << std::boolalpha;
    std::vector<std::any> vec;

    std::cout << std::is_standard_layout<std::vector<std::any>>() << std::endl;

    std::any a = *(new int);
    vec.push_back(a);
    a = 9;
    vec.push_back(a);

    a = A();
    vec.push_back(a);

    std::cout << "size: " << vec.size() << std::endl;
    for (auto x : vec) {
        std::cout << x.type().name() << " ";
        std::any int_type = 8;
        if (x.type() == int_type.type()) {
            std::cout << std::any_cast<int>(x) << std::endl;
        } else {
            std::cout << "non int type" << std::endl;
        }
    }
}

Here is one execution example

$ g++ -std=c++17 ./any.cpp -o any

$ ./any 
true
A dstor
A dstor
size: 3
i 0
i 9
1A non int type
A dstor
A dstor
A dstor
Leave a Reply

Your email address will not be published. Required fields are marked *