C++: 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.

C++: “auto” return type deduction

Before C++14, when implementing a function template you did not know the return type of your functions, you had to do something like this:

template <typename A, typename B>
auto do_something(const A& a, const B& b) -> decltype(a.do_something(b))
{
  return a.do_something(b);
}

You had to use “decltype” in order to say the compiler: “The return type of this method is the return type of method do_something of object a”. The “auto” keyword used to say the compiler: “The return type of this function is declared at the end”.

Since C++14, you can do something by far simpler:

template <typename A, typename B>
auto do_something(const A& a, const B& b)
{
  return a.do_something(b);
}

In C++14, the compiler deduces the return type of the methods that have “auto” as return type.

Restrictions:

All returned values must be of the same type. My example below does not compile because I am returning an “int” or a “double”.

auto f(int n)
{
	if (n == 1)
		return 1;

	return 2.0;
}

For recursive functions, a return value must be returned before the recursive call in order to let the compiler to know what will be the type of the value to return, as in this example:

auto accumulator(int n)
{
	if (n == 0)
		return 0;

	return n + accumulator(n - 1);
}

Music: “Stigmata” published

Hello,

I have just published my second album: “Stigmata“. It is already available through several streaming services such as Spotify, iTunes, CD Baby, Amazon, etc.

Some info:

Hardware used:

  • MacBook Pro 13″ mid-2012
  • Yamaha PSR-213e
  • Casio CDP-220R
  • Audio Technica ATH-M50x

Software used:

Cover artwork:

  • Zdenka Kristel Johnson-Kirigin Orías

Dedicated to:

  • Zdenka, my beautiful wife; Ariana, my little daughter; and Salomé, my mother.

In memoriam:

  • Stephen Johnson (1927-2017), a wise owl. I am pretty sure he would have liked this work.

C++11: std::tuple

A tuple is a C++11 construction and it is built heavily on variadic templates.

A tuple is a variadic class template that stores an unlimited set of values of different types, defined when instantiating the tuple; for example:

tuple<int, int> x;

will store 2 integers.

tuple<string, int, bool> y;

will store one string, one integer and one boolean and so on.

Continue reading “C++11: std::tuple”

C++: Smart pointers, part 5: weak_ptr

This is the last of several posts I wrote related to smart pointers:

  1. Smart pointers
  2. unique_ptr
  3. More on unique_ptr
  4. shared_ptr
  5. weak_ptr

In modern C++ applications (C++11 and later), you can replace almost all your naked pointers to shared_ptr and unique_ptr in order to have automatic resource administration in a deterministic way so you will not need (almost, again) to release the memory manually.

The “almost” means that there is one scenario where the smart pointers, specifically, the shared_ptr instances, will not work: When you have circular references. In this scenario, since every shared_ptr is pointing to the other one, the memory will never be released.

Continue reading “C++: Smart pointers, part 5: weak_ptr”