Partners: KDAB Whole Tomato Software CppDepend

18 June 2018

Everything You Need to Know About std::any from C++17

Using std::any in C++17

With std::optional you can represent some Type or nothing. With std::variant you can wrap several variants into one entity. And C++17 gives us one more wrapper type: std::any that can hold anything in a type-safe way.

The Basics

So far in the Standard C++, you had not many options when it comes to holding variable types in a variable. Of course, you could use void*, yet this wasn’t super safe.

Potentially, void* could be wrapped in a class with some type discriminator.

class MyAny
{
    void* _value;
    TypeInfo _typeInfo;
};

As you see, we have some basic form of the type, but it’s a bit of coding required to make sure MyAny is type-safe. That’s why it’s best to use the Standard Library rather than rolling a custom implementation.

And this is what std::any from C++17 is in its basic form. It gives you a chance to store anything in an object, and it reports errors (or throw exceptions) when you’d like to access a type that is not active.

A little demo:

std::any a(12);

// set any value:
a = std::string("Hello!");
a = 16;
// reading a value:

// we can read it as int
std::cout << std::any_cast<int>(a) << '\n'; 

// but not as string:
try 
{
    std::cout << std::any_cast<std::string>(a) << '\n';
}
catch(const std::bad_any_cast& e) 
{
    std::cout << e.what() << '\n';
}

// reset and check if it contains any value:
a.reset();
if (!a.has_value())
{
    std::cout << "a is empty!" << "\n";
}

// you can use it in a container:
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";
}

The code will output:

16
bad any_cast
a is empty!
float: 1
int: 10
string: Hello World

Play with the code @Coliru

We have several things showed in the example above:

  • std::any is not a template class like std::optional or std::variant.
  • by default it contains no value, and you can check it via .has_value().
  • you can reset an any object via .reset().
  • it works on “decayed” types - so before assignment, initialization, emplacement the type is transformed by std::decay.
  • when a different type is assigned, then the active type is destroyed.
  • you can access the value by using std::any_cast<T>, it will throw bad_any_cast if the active type is not T.
  • you can discover the active type by using .type() that returns std:: type_info of the type.

The above example looks impressive - a true variable type in C++!. If you like JavaScript then you can even make all of your variables std::any and use C++ like JavaScript :)

But maybe there are some legitimate use cases?

When to Use

While I perceive void* as an extremely unsafe pattern with some limited use cases, std::any adds type-safety, and that’s why it has some real use cases.

Some possibilities:

  • In Libraries - when a library type has to hold or pass anything without knowing the set of available types.
  • Parsing files - if you really cannot specify what are the supported types.
  • Message passing.
  • Bindings with a scripting language.
  • Implementing an interpreter for a scripting language
  • User Interface - controls might hold anything
  • Entities in an editor

I believe in a lot of cases we can limit the set of supported types, and that’s why std::variant might be a better choice. Of course, it gets tricky when you implement a library without knowing the final applications - so you don’t know the possible types that will be stored in an object.

The demo showed some basics, but in the following sections, you’ll discover more details about std::any so read on.

The Series

This article is part of my series about C++17 Library Utilities. Here’s the list of the other topics that I’ll cover:

Resources about C++17 STL:

std::any Creation

There are several ways you can create std::any object:

  • a default initialization - then the object is empty
  • a direct initialization with a value/object
  • in place std::in_place_type
  • via std::make_any

You can see it in the following example:

// default initialization:
std::any a;
assert(!a.has_value());

// initialization with an object:
std::any a2(10); // int
std::any a3(MyType(10, 11));

// in_place:
std::any a4(std::in_place_type<MyType>, 10, 11);
std::any a5{std::in_place_type<std::string>, "Hello World"};

// make_any
std::any a6 = std::make_any<std::string>("Hello World");

Play with the code @Coliru

Changing the Value

When you want to change the currently stored value in std::any then you have two options: use emplace or the 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);

Play with the code @Coliru

Object Lifetime

The crucial part of being safe for std::any is not to leak any resources. To achieve this behaviour std::any will 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";

Play with the code @Coliru

This will produce the following output:

MyType::MyType
MyType::~MyType
100

The any object is initialized with MyType, but before it gets a new value (of 100.0f) it calls the destructor of MyType.

Accessing The Stored Value

In order to read the currently active value in std::any you have mostly one option: std::any_cast. This function returns the value of the requested type if it’s in the object.

However, this function template is quite powerful, as it has many ways of using:

  • to return a copy of the value, and throw std::bad_any_cast when it fails
  • to return a reference (also writable), and throw std::bad_any_cast when it fails
  • to return a pointer to the value (const or not) or nullptr on failure

See the 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; // read/write
        std::any_cast<MyType&>(var).Print();
        std::any_cast<int>(var); // throw!
    }
    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();
    }
}

Play with the code @Coliru

As you see you have two options regarding error handling: via exceptions (std::bad_any_cast) or by returning a pointer (or nullptr). The function overloads for std::_any_cast pointer access is also marked with noexcept.

Performance & Memory Considerations

std::any looks quite powerful and you might use it to hold variables of variable types… but you might ask what’s the price of such flexibility?

The main issue: extra dynamic memory allocations.

std::variant and std::optional don’t require any extra memory allocations but this is because they know which type (or types) will be stored in the object. std::any has no knowledge and that why it might use some heap memory.

Will it happen always, or sometimes? What’re the rules? Will it happen even for a simple type like int?

Let’s see what the standard says:

From The Standard:

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<T> is true.

To sum up: Implementations are encouraged to use SBO - Small Buffer Optimization. But that also comes at some cost: it will make the type larger - to fit the buffer.

Let’s check what’s the size of std::any:

Here are the results from the three compilers:

Compiler sizeof(any)
GCC 8.1 (Coliru) 16
Clang 7.0.0 (Wandbox) 32
MSVC 2017 15.7.0 32-bit 40
MSVC 2017 15.7.0 64-bit 64

Play with code @Coliru

In general, as you see, std::any is not a “simple” type and it brings a lot of overhead. It’s usually not small - due to SBO - it takes 16 or 32 bytes (GCC or Clang… or even 64 bytes in MSVC!)

Migration from boost::any

Boost Any was introduced around the year 2001 (version Version 1.23.0). What’s more the author of the boost library - Kevlin Henney - is also the author of the proposal for std::any. So the two types are strongly connected, and the STL version is heavily based on the predecessor.

Here are the main changes:

Feature Boost.Any (1.67.0) std::any
Extra memory allocation Yes Yes
Small buffer optimization No Yes
emplace No Yes
in_place_type_t in constructor No Yes

The main difference is that boost.any doesn’t use SBO, so it’s much smaller type (GCC8.1 reports 8 bytes), but as the consequence, it will allocate a memory even for simple types, like int.

Examples of std::any

The core of std::any is flexibility. So In the below examples, you can see some ideas (or concrete implementations) where holding variable type can make an application a bit simpler.

Parsing files

In the examples about std::variant (see it here) you could see how it’s possible to parse config files and store the result as an alternative of several types. Yet, if you write a really generic solution - maybe as a part of some library, then you might not know all the possible types.

Storing std::any as a value for a property might be good enough from the performance point of view and will give you flexibility.

Message Passing

In Windows Api, which is C mostly, there’s a message passing system that uses message ids with two optional parameters that store the value of the message. Based on that mechanism you can implement WndProc that handles the messages passed to your window/control:

LRESULT CALLBACK WindowProc(
  _In_ HWND   hwnd,
  _In_ UINT   uMsg,
  _In_ WPARAM wParam,
  _In_ LPARAM lParam
);

The trick here is that the values are stored in wParam or lParam in various forms. Sometimes you have to use only a few bytes of wParam

What if we changed this system into std::any, so that a message could pass anything to the handling method?

For example:

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 a window:

Message m(Message::Type::ShowWindow, std::make_pair(10, 11));
yourWindow.HandleMessage(m);

Then the window can respond to the message like:

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;
    }
}

Play with the code @Coliru

Of course, you have to define how are the values specified (what are the types of a value of a message), but now you can use real types rather that doing various tricks with integers.

Properties

The original paper that introduces any to C++, N1939 shows an example of a property class.

struct property
{
    property();
    property(const std::string &, const std::any &);

    std::string name;
    std::any value;
};

typedef std::vector<property> properties;

The properties object looks very powerful as it can hold many different types. As a first use case a generic UI manager comes to my mind, or a game editor.

Passing across boundaries

Some time ago there was a thread on [r/cpp](
https://www.reddit.com/r/cpp/comments/7l3i19/why_was_stdany_added_to_c17/
) about std::any. And there was at least one great comment that summarises when the type should be used:

From the comment:

The general gist is that std::any allows passing ownership of arbitrary values across boundaries that don’t know about those types.

Everything that I mentioned before is close to this idea:

  • in a UI library: you don’t know what the final types that a client might use are
  • message passing: same idea, you’d like to have the flexibility for the client
  • parsing files: to support custom types a really “variable” type could be useful

Sorry for a little interruption in the flow :)
I've prepared a little bonus if you're interested in C++17, check it out here:

Wrap Up

In this article, we covered a lot about std::any!

Here are the things to remember about std::any:

  • std::any is not a template class
  • std::any uses Small Buffer Optimization, so it will not dynamically allocate memory for simple types like ints, doubles… but for larger types it will use extra new.
  • std::any might be considered ‘heavy’, but offers a lot of flexibility and type-safety.
  • you can access the currently stored value by using any_cast that offers a few “modes”: for example it might throw an exception or just return nullptr.
  • use it when you don’t know the possible types, in other cases consider std::variant.

Now a few questions to you:

  • Have you used std::any or boost::any?
  • Can you mention what the uses cases were?
  • Where do you see std::any might be useful?

Get my free ebook about C++17!

More than 50 pages about the new Language Standard.

C++17 in detail, by Bartlomiej Filipek

© 2017, Bartlomiej Filipek, Blogger platform
Any opinions expressed herein are in no way representative of those of my employers.
This site contains ads or referral links, which provide me with a commission. Thank you for your understanding.