We will implement a function that tries to be able to print everything. It uses std::any as its argument type:
- First, we include some necessary headers and declare that we use the std namespace:
#include <iostream>
#include <iomanip>
#include <list>
#include <any>
#include <iterator>
using namespace std;
- In order to reduce the number of angle bracket syntax in the following program, we define an alias for list<int>, which we will use later:
using int_list = list<int>;
- Let's implement a function that claims to be able to print anything. The promise is that it prints anything provided as an argument in the form of an std::any variable:
void print_anything(const std::any &a)
{
- The first thing we need to check is if the argument contains anything or if it is just an empty any instance. If it is empty, then there is no sense in trying to figure out how to print it:
if (!a.has_value()) {
cout << "Nothing.n";
- If it is not empty, we can try to compare it with different types until we see a match. The first type to try is string. If it is a string, we can cast a to a string typed reference using std::any_cast and just print it. We put the string in quotes for cosmetic reasons:
} else if (a.type() == typeid(string)) {
cout << "It's a string: "
<< quoted(any_cast<const string&>(a)) << 'n';
- If it is not a string, it might be an int. In case this type matches, we can use any_cast<int> to obtain the actual int value:
} else if (a.type() == typeid(int)) {
cout << "It's an integer: "
<< any_cast<int>(a) << 'n';
- std::any does not only work with such simple types as string and int. We can also put a whole map or list or whatever composed complex data structure into an any variable. Let's see if the input is a list of integers, and if it is, we can just print it like we would print a list:
} else if (a.type() == typeid(int_list)) {
const auto &l (any_cast<const int_list&>(a));
cout << "It's a list: ";
copy(begin(l), end(l),
ostream_iterator<int>{cout, ", "});
cout << 'n';
- If none of these types match, we run out of type guesses. Let's give up in that case and tell the user that we have no idea how to print this:
} else {
cout << "Can't handle this item.n";
}
}
- In the main function, we can now call this function with arbitrary types. We can call it with an empty any variable using {} or feed it with a string "abc" or an integer. Because std::any can be constructed from such types implicitly, there is no syntax overhead. We can even construct a whole list and throw it into this function:
int main()
{
print_anything({});
print_anything("abc"s);
print_anything(123);
print_anything(int_list{1, 2, 3});
- If we are going to put objects that are really expensive to copy into an any variable, we can also perform an in-place construction. Let's try this with our list type. The in_place_type_t<int_list>{} expression is an empty object that gives the constructor of any enough information to know what we are going to construct. The second parameter, {1, 2, 3}, is just an initializer list that will be fed to the int_list embedded in the any variable for construction. This way, we avoid unnecessary copies or moves:
print_anything(any(in_place_type_t<int_list>{}, {1, 2, 3}));
}
- Compiling and running the program yields the following output, which is just what we expected:
$ ./any
Nothing.
It's a string: "abc"
It's an integer: 123
It's a list: 1, 2, 3,
It's a list: 1, 2, 3,