Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Everything You Need to Know About std::any From C++17, Part 2

DZone's Guide to

Everything You Need to Know About std::any From C++17, Part 2

We finish up this quick two-part series by looking at how web and software developers can use std::any in their applications.

· Web Dev Zone ·
Free Resource

Bugsnag monitors application stability, so you can make data-driven decisions on whether you should be building new features, or fixing bugs. Learn more.

Welcome back! If you missed Part 1, you can check it out here.

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 behaviorstd::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 below: 

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 and 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's 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:

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 in order 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 a much smaller type (GCC8.1 reports 8 bytes), but, as a 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 a variable type can make an application a bit simpler.

Parsing Files

In the examples about std::variant (see 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 the 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, which 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.
  • For message passing, it's the same idea, you'd like to have the flexibility for the client.
  • For parsing files, a really "variable" type could be useful for supporting custom types.

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?

Monitor application stability with Bugsnag to decide if your engineering team should be building new features on your roadmap or fixing bugs to stabilize your application.Try it free.

Topics:
web dev ,c++ ,c++ 17 ,tutorial ,wrapper types

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}