Let's implement a program that knows the types, cat and dog, and that stores a mixed list of cats and dogs without using any runtime polymorphy:
- First, we include all the needed headers and define that we use the std namespace:
#include <iostream>
#include <variant>
#include <list>
#include <string>
#include <algorithm>
using namespace std;
- Next, we implement two classes that have similar functionality but are not related to each other in any other way, in contrast to classes that, for example, inherit from the same interface or a similar interface. The first class is cat. A cat object has a name and can say meow:
class cat {
string name;
public:
cat(string n) : name{n} {}
void meow() const {
cout << name << " says Meow!n";
}
};
- The other class is dog. A dog object does not say meow but woof, of course:
class dog {
string name;
public:
dog(string n) : name{n} {}
void woof() const {
cout << name << " says Woof!n";
}
};
- Now we can define an animal type, which is just a type alias to std::variant<dog, cat>. This is basically the same as an old-school union but has all the extra features that variant provides:
using animal = variant<dog, cat>;
- Before we write the main program, we implement two helpers first. One helper is an animal predicate. By calling is_type<cat>(...) or is_type<dog>(...), we can find out if an animal variant instance holds a cat or a dog. The implementation just calls holds_alternative, which is a generic predicate function for variant types:
template <typename T>
bool is_type(const animal &a) {
return holds_alternative<T>(a);
}
- The second helper is a structure that acts as a function object. It is a twofold function object because it implements operator() twice. One implementation is an overload that accepts dogs and the other accepts cats. For these types, it just calls the woof or the meow function:
struct animal_voice
{
void operator()(const dog &d) const { d.woof(); }
void operator()(const cat &c) const { c.meow(); }
};
- Let's put these types and helpers to use. First, we define a list of animal variant instances and fill it with cats and dogs:
int main()
{
list<animal> l {cat{"Tuba"}, dog{"Balou"}, cat{"Bobby"}};
- Now, we print the contents of the list three times, and each time in a different way. One way is using variant::index(). Because animal is an alias of variant<dog, cat>, a return value of 0 means that the variant holds a dog instance. Index 1 means it is a cat. The order of the types in the variant specialization is the key here. In the switch case block, we access the variant with get<T> in order to get the actual cat or dog instance inside:
for (const animal &a : l) {
switch (a.index()) {
case 0:
get<dog>(a).woof();
break;
case 1:
get<cat>(a).meow();
break;
}
}
cout << "-----n";
- Instead of using the numeric index of the type, we can also explicitly ask for every type. The get_if<dog> returns a dog-typed pointer to the internal dog instance. If there is no dog instance inside, then the pointer is null. This way, we can try to get at different types until we finally succeed:
for (const animal &a : l) {
if (const auto d (get_if<dog>(&a)); d) {
d->woof();
} else if (const auto c (get_if<cat>(&a)); c) {
c->meow();
}
}
cout << "-----n";
- The last and most elegant way is variant::visit. This function accepts a function object and a variant instance. The function object must implement different overloads for all the possible types the variant can hold. We implemented a structure with the right operator() overloads before, so we can use it here:
for (const animal &a : l) {
visit(animal_voice{}, a);
}
cout << "-----n";
- At last, we will count the number of cats and dogs in the variant list. The is_type<T> predicate can be specialized on cat and dog and can then be used in combination with std::count_if to return us the number of instances of this type:
cout << "There are "
<< count_if(begin(l), end(l), is_type<cat>)
<< " cats and "
<< count_if(begin(l), end(l), is_type<dog>)
<< " dogs in the list.n";
}
- Compiling and running the program first yields the same list printed three times. After that, we see that the is_type predicates combined with count_if work just fine:
$ ./variant
Tuba says Meow!
Balou says Woof!
Bobby says Meow!
-----
Tuba says Meow!
Balou says Woof!
Bobby says Meow!
-----
Tuba says Meow!
Balou says Woof!
Bobby says Meow!
-----
There are 2 cats and 1 dogs in the list.