Last Update:
Everything You Need to Know About std::any from C++17
Table of Contents
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 likestd::optional
orstd::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 throwbad_any_cast
if the active type is notT
. - 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:
- Refactoring with
std::optional
- Using
std::optional
- Error handling and
std::optional
- About
std::variant
- Using
std::any
(this post) - In place construction for
std::optional
,std::variant
andstd::any
- Using
std::string_view
- C++17 string searchers & conversion utilities
- Working with
std::filesystem
- Something more?
Resources about C++17 STL:
- C++17 In Detail by Bartek!
- C++17 - The Complete Guide by Nicolai Josuttis
- C++ Fundamentals Including C++ 17 by Kate Gregory
- Practical C++14 and C++17 Features - by Giovanni Dicanio
- C++17 STL Cookbook by Jacek Galowicz
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 whichis_nothrow_move_constructible_v<T>
istrue
.
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:
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:
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:
Download a free copy of C++17 Language Ref Card!
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 classstd::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?
I've prepared a valuable bonus for you!
Learn all major features of recent C++ Standards on my Reference Cards!
Check it out here: