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.
Join the DZone community and get the full member experience.
Join For FreeWelcome 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:
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:
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 extranew
.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 returnnullptr
. - 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
orboost::any
? - Can you mention what the uses cases were?
- Where do you see
std::any
might be useful?
Published at DZone with permission of Bartłomiej Filipek, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments