Modern C++ Utility Types

std::any

Sometimes, you don't know what type a value is until program runtime. That's where std::any comes in handy. A std::any object can store a single value of pretty much any type. Later on, you can recover that value by telling it explicitly what type you're expecting.

A std::any object is empty when you default construct it. You place a value inside it via assignment or construction:

#include <iostream>
#include <any>

int main()
{
    std::any a{ 42 };
    std::cout << std::any_cast<int>(a) << '\n';
}

When a is first constructed with the integer value 42, then recovered by calling std::any_cast<int>(a), the value 42 is printed. The type given to any_cast must be the same as the type initially stored or the expression will throw an exception of type std::bad_any_cast.

It is possible to change the stored type later:

#include <iostream>
#include <any>

int main()
{
    std::any a{ 42 };
    a = 3.14;
    std::cout << std::any_cast<double>(a) << '\n';
}

You can check if it has a value before calling std::any_cast:

#include <iostream>
#include <any>

int main()
{
    std::any a;

    // Check this condition before entering the block
    if (!a.has_value())
        std::cout << "empty\n";
}

If a type cast to a pointer like type does not succeed, then instead of an exception, a null pointer is returned:

#include <iostream>
#include <any>

int main()
{
    std::any a{ 42 };

    // Store the object's address in the pointer
    if (int* p{ std::any_cast<int>(&a) })
        std::cout << *p << '\n';
}

std::any effectively stores its value using type erasure. The actual type of the object held within std::any is hidden from the compiler, and must be explicitly provided by the programmer during extraction. If the incorrect type is specified during extraction, a null pointer, when casting to a pointer type, or an exception, std::bad_any_cast, is thrown.

The dynamic storage of arbitrary type with std::any is more efficient than using something like dynamic allocation from a pool of objects, but it has performance limitations: access speed is lower than with standard, correctly typed variables, and due to the lack of compile time type checking, errors can be introduced that a std::variant, which requires a predefined set of possible types, would catch earlier.

If the list of types your program is expected to hold is known and finite, the std::variant type should be preferred due to increased speed and earlier detection of type errors. Where type erasure is absolutely required during runtime, std::any will serve your purpose.

0 comments

No comments yet. Be the first.

Add a comment

About CppLearn

CppLearn is an interactive, free reference for modern C++. In each lesson, you'll find out how the language works, rather than just lines of code that you can copy and paste. We cover concepts starting from beginner lessons on fundamentals and proceed to templates, move semantics, smart pointers, and areas that usually trap or scare C++ learners.