C++17: std::any

When trying to implement something that will store a data value of an unknown type (in order to be as generic as possible, for example), we had these possibilities before C++17:

  • Having a void* pointer to something that will be assigned in runtime. The problem with this approach is that you leave the complete responsability of handling the lifetime of the thing poìnted by this void pointer to the user of our code. Very error prone.
  • Having a union with a limited set of types available.
  • Having a base class (e.g. Object) and store pointers to instances derived from such class (à la Java).
  • Having an instance of template typename T (for example). Good approach, but in order to be useful and generic, we need to propagate the typename T in all the generic code that will use ours. Probably verbose.

So, let’s welcome to std::any.

std::any, as you already guess it, is a template class shipped in C++17 and implemented in <any> that can store a value of any type, so, these lines are completely valid:

std::any a = 123;
std::any b = "Hello";
std::any c = std::vector<int>{10, 20, 30};

Obviously, this is C++ and you, as user, need to know the type of the thing you stored in an instance of std::any, so, to retrieve the value stored, you need to perform an any_cast as in this code:

#include <any>
#include <iostream>

int main()
{
    std::any number = 150;
    std::cout << std::any_cast<int>(number) << "\n";
}   

If you try to cast the value stored in an instance of std::any, a std::bad_any_cast exception is thrown. For example, if you try to cast that number to a string, you will get this runtime error:

terminate called after throwing an instance of 'std::bad_any_cast'
  what():  bad any_cast

If the value stored in an instance of std::any is an instance of a class or struct, the compiler will make sure the destructor of such value will be invoked when the instance of std::any will go out of scope.

Other really nice thing about std::any is that you can replace the existing value stored in an instance of it, for other value of any other type, for example:

std::any content = 125;
std::cout << std::any_cast<int>(content) << "\n";

content = std::string{"Hello world"};
std::cout << std::any_cast<std::string>(content) << "\n";

About lifetimes

Let’s consider this class:

struct A
{
  int n;
  A(int n) : n{n} { std::cout << "Constructor\n"; }
  ~A() { std::cout << "Destructor\n"; }
  A(A&& a) : n{a.n} { std::cout << "Move constructor\n"; }
  A(const A& a) : n{a.n} { std::cout << "Copy constructor\n"; }
  void print() const { std::cout << n << "\n"; }
};

This class stores an int, and print it out with “print”. I wrote constructor, copy constructor, move constructor and destructor with messages that will tell me when the object will be created, copied, moved or destroyed.

So, let’s create a std::any instance with an instance of this class:

std::any some = A{4516};

This will be the output of such code:

Constructor
Move constructor
Destructor
Destructor

Why two constructors and two destructors are invoked if I only created one instance?

Because the instance of std::any will store a copy (ok, in this case a “moved version”) of the original object I created and, though in my example can be trivial, in a complex object it cannot be.

How to avoid this problem?

Using std::make_any.

std::make_any is very similar to std::make_shared in the way that it will be in charge to create the object instead of copying/moving ours. The parameters passed to std::make_any are the ones you would pass to the object’s constructor.

So, I can modify my code to this:

auto some = std::make_any<A>(4517);

And the output will be:

Constructor
Destructor

Now, I want to invoke to the method “print”:

auto some = std::make_any<A>(4517);
std::any_cast<A>(some).print();

And when I do that, the output is:

Constructor
Copy constructor
4517
Destructor
Destructor

Why such extra copy was created?

Because std::any_cast<A> returns a copy of the given object. If I want to avoid a copy and use a reference, I need to explicit a reference in std::any_cast, something like:

<code>auto some = std::make_any<A>(4517);
std::any_cast<A&>(some).print();</code>

And the output will be:

Constructor
4517
Destructor

It is also possible to use std::any_cast<T> passing a pointer to an instance of std::any instead of a reference.

In such case, if the cast is possible, will return a valid pointer to a T* object, otherwise it will return a nullptr. For example:

auto some = std::make_any(4517);
std::any_cast<A>(&some)->print();
std::cout << std::any_cast<int>(&some) << "\n";

In this case, notice I am passing a pointer to “some” instead of a reference. When this occurs, the implementation returns a pointer to the target type if the object stored is of the same type (as in the second line) or a null pointer if noy (as in the third line, where I am trying to cast my object of type A to int). Using these overloaded version with pointers avoids to raise an exception and lets you check if the returned pointer is null instead.

std::any is a very nice tool to store things that we, as implementors of something reusable, do not know a priori; it could be used to store for example, extra parameters passed to threads, objects of any type stored as extra information in UI widgets (similar to the .Tag property in Windows.Forms.Control in .NET, for example), etc. Performance wise, std::any needs to store things in the heap and also needs to perform some extra checking to return the values only if the cast is valid, so, it is not as fast as having a generic object known at compile time.

C++17: std::variant

Let’s suppose I have a system that handles students, teachers and crew of a school.

To model that in an object oriented style, I would have a class hierarchy similar to this one:

class person
{
std::string name;

public:
template <typename String>
person(String&& name) : name { forward<String>(name) }
{
}

virtual ~person() { }
const string& get_name() const { return name; }
virtual void do_something() = 0;
};

class student : public person
{
public:
using person::person;

void do_homework()
{
cout << "Need access to Stack Overflow\n";
}

void do_something() override
{
cout << "I am doing something the students do\n";
}
};

class teacher : public person
{
public:
using person::person;

void teach()
{
cout << "This is the unique truth\n";
}

void do_something() override
{
cout << "I am doing something the teachers do\n";
}
};

class crew : public person
{
public:
using person::person;

void help_team()
{
cout << "I am helping teachers and students\n";
}

void do_something() override
{
cout << "I am doing something crew do\n";
}
};

And my collection would be defined like this:

map<size_t, person*> people;

where the size_t ID would be the key of the map.

Since I do not want to deal with raw pointers, this would be a better definition:

map<size_t, unique_ptr<person>> people;

Now, I will insert some elements to my collection:

people.insert(make_pair(14, make_unique<student>("Phil Collins")));
people.insert(make_pair(25, make_unique<teacher>("Peter Gabriel")));
people.insert(make_pair(32, make_unique<crew>("Justin Bieber")));

To get the name of person 14, I should do something like:

people.find(14)->second->get_name(); //being 100% sure that person with ID 14 exists

And to do something specific implemented in a derived class, I need to downcast:

static_cast<crew&>(*people.find(32)->second).help_team();

Since C++11, the language has been evolving to a more generic and more template metaprogramming-like paradigm and has been getting away from the classical OOP design where inheritance and polymorphism are amongst the most important tools.

So, how could I implement something similar to the thing shown above without inheritance and polymorphism?

Let me introduce std::variant ! :)

C++17 introduced variant, that is basically a template class where you specify the possible types of the values that the variant instance can store, so, for my example, I could define something like:

using person = std::variant<student, teacher, crew>;

In this line, I am defining an alias person that represents a variant value that can store a student, a teacher or a crew (think on variant to be something like a typesafe union).

So, my map would be defined in this way:

map<size_t, person> people;

And my classes student, teacher, and crew could be defined as follows:

class student
{
std::string name;
public:
template <typename String>
student(String&& name) : name { forward<String>(name) }
{
}

const string& get_name() const { return name; }

void do_homework()
{
cout << "Need access to Stack Overflow\n";
}

void do_something()
{
cout << "I am doing something the students do\n";
}
};

class teacher
{
std::string name;
public:
template <typename String>
teacher(String&& name) : name { forward<String>(name) }
{
}

const string& get_name() const { return name; }

void teach()
{
cout << "This is the unique truth\n";
}

void do_something()
{
cout << "I am doing something the teachers do\n";
}
};

class crew
{
std::string name;

public:
template <typename String>
crew(String&& name) : name { forward<String>(name) }
{
}

const string& get_name() const { return name; }

void help_team()
{
cout << "I am helping teachers and students\n";
}

void do_something()
{
cout << "I am doing something crew do\n";
}
};

To make my example clean and to demonstrate that I do not need inheritance and polymorphism, notice I am not defining a base class nor I am defining virtual methods at all. Anyway. in real production code the coder could create a base class with no virtual methods and inherit from such class to avoid code duplication.

Notice also I am not using any pointer (raw or smart), so the map will contain actual values, removing one level of indirection and letting the compiler optimize based on that knowledge.

So, let me add some objects to the map:

people.insert(make_pair(14, student { "Phil Collins" }));
people.insert(make_pair(25, teacher { "Peter Gabriel" }));
people.insert(make_pair(32, crew { "Justin Bieber" }));

To get the person with id 14:

auto& the_variant = people.find(14)->second;

To get the “student” inside that variant object, I need to use the function get:

auto& the_student = get(the_variant);
cout << the_student.get_name() <<  "\n";

If I try to get an object that is not of the type stored in the variant, the system will throw a std::bad_variant_access exception, for example if I try to do this with the variant from the example above:

auto& the_student = get<teacher>(the_variant);

To execute a specific method of a given class, I do not need to do any downcasting because I already have the object of the given type, so, instead of:

static_cast<crew&>(*people.find(32)->second).help_team();

I would do:

get<crew>(people.find(32)->second).help_team();

that is by far straight and cleaner.

Now, given I have a method called “do_something” in all my classes, I would want to be able to invoke it no matter the type of the object stored in the variant.

So, I need to do something like this in the polymorphic world:

for (auto& p : people)
{
p.second->do_something();
}

To do this, there is a function called: std::visit.

What visit does is accessing the variant object and invoke the method passed as argument with the object stored in the variant. So, given my example, I could do something like:

auto& the_variant = people.find(14)->second;
visit([](auto& s)
{
s.do_something();
}, the_variant);

The magic is in the “auto” part here. When you “visit” a variant, the compiler generates one method for each type specified in the variant declaration, in my case 3 (one for student, one for crew and one for teacher), and executes the specific method depending on the type of the value stored in the variant. So, to execute do_something() for all objects in the variant, I need to do something like:

for (auto& p : people)
{
visit([](auto& s)
{
s.do_something();
}, p.second);
}

It is beautiful, isn’t it? Polymorphic-like behavior with no overhead that polymorphism brings.