C++: std::monostate

std::monostate was released with std::variant in C++17.

Essentially, std::monostate is a type where all its instances have exactly the same (and empty) state, hence its name.

Think of its implementation as being similar to something like this:

class empty { };

And what is it useful for?

  1. An instance of this class can be used to represent the initial value of a std::variant when declared as its first type. A std::variant MUST always contain a valid value, so std::monostate is quite useful when its initial value is not known a priori or when the other data types do not have default constructors.
std::variant<std::monostate, int, std::string> value; // here value is instantiated using std::monostate.
  1. It can be used when representing a “no-value” as a valid std::variant value is needed.
std::variant<int, std::string, std::monostate> value{2}; // stores an int.
value = std::monostate{}; // now stores a new instance of std::monostate.
  1. Independently from std::variant, it can be used when a class with no data is required. A concrete example?

Currently, I am working on a UI layout system. The layout_container can store UI widgets and, depending on the layout policies used, they need to specify a “constraint.” The constraint could be a relative XY coordinate, or a position inside a border_layout (north, south, etc.) or NOTHING (for example, in a flow-based layout, the UI controls are positioned one next to the other, flowing like text, and thus, they do not need any constraint).

So I could define my layout_container like this:

template <typename Constraint, typename LayoutPolicy>
class layout_container;

and have the specific types like:

using border_layout_container = layout_container<border_layout_constraint, border_layout_policy>;
using xy_layout_container = layout_container<xy_layout_constraint, xy_layout_policy>;
using flow_layout_container = layout_container<std::monostate, flow_layout_policy>;

I do not need to change anything in my layout_container to support (or not) constraints thanks to std::monostate.

  1. Additionally, std::monostate implements all comparison operators (operator==, operator!=, etc.). All instances are equal, by the way. Finally, std::monostate can also be used as a key on std::unordered_map instances because it provides a specialization for std::hash.

Want to read more about std::variant? Check this out!

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.

C++: Perfect forwarding

The following function template invoke (as its name says) invokes the function/functor/lambda expression passed as an argument, passing it the two extra arguments given:

#include <iostream>
#include <string>
  
void sum(int a, int b)
{
    std::cout << a + b << std::endl;
}
 
void concat(const std::string& a, const std::string& b)
{
    std::cout << a + b << std::endl;
}
 
template <typename PROC, typename A, typename B>
void invoke(PROC p, const A& a, const B& b)
{
    p(a, b);
}
 
int main()
{
    invoke(sum, 10, 20);
    invoke(concat, "Hello ", "world");
    return 0;
}

Nice, it works as expected, and the result is:

30
Hello world

The problem with that implementation is that it only works with arguments passed as constant references, so if the programmers try to invoke the following function:

void successor(int a, int& b)
{
    b = a + 1;
}

with this call:

int s = 0;
invoke(successor, 10, s);
std::cout << s << std::endl;

the compiler returns this error in the invoke implementation:

Binding of reference to type 'int' to a value of type 'const int' drops qualifiers

This error occurs because the second argument of the successor function is not a const-reference.

Before C++11, the only way to deal with this problem was creating a set of overloads containing all the possible combinations of const, non-const references in the methods, something like this:

#include <iostream>
#include <string>
 
void sum(int a, int b)
{
    std::cout << a + b << std::endl;
}
 
void concat(const std::string& a, const std::string& b)
{
    std::cout << a + b << std::endl;
}
 
void successor(int a, int& b)
{
    b = ++a;
}
 
template <typename PROC, typename A, typename B>
void invoke(PROC p, const A& a, const B& b)
{
    p(a, b);
}
 
template <typename PROC, typename A, typename B>
void invoke(PROC p, const A& a, B& b)
{
    p(a, b);
}
 
template <typename PROC, typename A, typename B>
void invoke(PROC p, A& a, const B& b)
{
    p(a, b);
}
 
template <typename PROC, typename A, typename B>
void invoke(PROC p, A& a, B& b)
{
    p(a, b);
}
 
int main()
{
    invoke(sum, 10, 20);
    invoke(concat, "Hello", "world");
    int s = 0;
    invoke(successor, 10, s);
    std::cout << s << std::endl;
    return 0;
}

Notice that there are four overloads for the invoke function template example above because two parameters need to be forwarded to the function passed in P.

If the function had more parameters, this would be unmaintainable (for N arguments, programmers would need to have 2^N overloads because of all the const/non-const possible combinations).

Starting from C++11, C++ lets programmers perform perfect forwarding, which means that they can forward the parameters passed to a function template to another function call inside it without losing their own qualifiers (const-ref, ref, value, rvalue, etc.).

To use this technique, the invoke function template in the example must have arguments passed as rvalue references, and the std::forward function template, which is in charge of performing the type deduction and forwarding the arguments to the invoked function with its own reference, const-reference, or rvalue-reference qualifiers, must be used. Using this technique, the code becomes something like this:

#include <iostream>
#include <string>
#include <utility>
 
void sum(int a, int b)
{
    std::cout << a + b << std::endl;
}
 
void concat(const std::string& a, const std::string& b)
{
    std::cout << a + b << std::endl;
}
 
void successor(int a, int& b)
{
    b = ++a;
}
 
template <typename PROC, typename A, typename B>
void invoke(PROC p, A&& a, B&& b)
{
    p(std::forward<A>(a), std::forward<B>(b));
}
 
int main()
{
    invoke(sum, 10, 20);
    invoke(concat, "Hello", "world");
    int s = 0;
    invoke(successor, 10, s);
    std::cout << s << std::endl;
    return 0;
}

What is this technique used for?

For example, consider this class:

struct A { };
struct B { int a; int b; };
struct C { int a; int b; string c; };
struct D
{
public:
    D(int a) {}
};

Programmers want to have a factory function template that will let them run this code:

int main()
{
    auto a = factory<A>();
    auto b = factory<B>(10, 20);
    auto c = factory<C>(30, 40, "Hello");
    auto d = factory<D>(10);
     
    std::cout << c->c << std::endl;
}

As can be seen, the factory function template must be a variadic-template-based function, so a solution would be this one:

template <typename T, typename ... ARGS>
std::unique_ptr<T> factory(const ARGS&... args)
{
    return std::unique_ptr<T>{new T { args... }};
}

Notice that the std::make_unique<T> helper template method works like this example and serves the same purpose: create a new std::unique_ptr of type T, forwarding the parameters passed to its constructor.

Nice, and it works as expected. But the problem of losing qualifiers arises again. What happens with the following code?


If programmers try to use this struct in this way:

int main()
{
    int x = 2;
    auto e = factory<E>(x);
    std::cout << x << std::endl;  
}

They will get an error because x is an int&, not a const int&.

Fortunately, perfect forwarding is the way to go:

#include <iostream>
#include <string>
#include <utility>
 
struct A { };
struct B { int a; int b; };
struct C { int a; int b; string c; };
struct D
{
public:
    D(int a) {}
};
 
struct E
{
    int& e;
    E(int& e) : e{e} { e++; }
};
 
template <typename T, typename ... ARGS>
std::unique_ptr<T> factory(ARGS&&... args)
{
    return std::unique_ptr<T>{new T { std::forward<ARGS>(args)... }};
}
 
int main()
{
    auto a = factory<A>();
    auto b = factory<B>(10, 20);
    auto c = factory<C>(30, 40, "Hello");
    auto d = factory<D>(10);
     
    int x = 2;
    auto e = factory<E>(x);
    std::cout << x << std::endl;
}

Perfect forwarding also helps avoid writing several overloads of functions to support move semantics and copy semantics.

For example:

#include <iostream>
 
struct X
{
    X() { std::cout << "ctor" << std::endl; }
    X(const X&) { std::cout << "copy ctor" << std::endl; }
    X(X&&) { std::cout << "move ctor" << std::endl; }
};
 
struct Wrapper
{
    X w;
     
    Wrapper(const X& w) : w{w} { }
};
 
int main()
{
    Wrapper w1{X {}};
    std::cout << "***" << std::endl;
    X y;
    Wrapper w2{y};
}

This produces the output:

ctor
copy ctor
***
ctor
copy ctor

The little problem here is that when w1 is being constructed, the instance of X is a temporary instance that will be destroyed immediately after invoking the Wrapper constructor. In this case, invoking the move constructor should be better (performance-wise).

So, the programmers can create a second constructor and modify the Wrapper class to be like this:

struct Wrapper
{
    X w;
     
    Wrapper(const X& w) : w{w} { }   
    Wrapper(X&& w): w{std::move(w)} { } // Adding a overloaded constructor
};

Cool! It works as expected:

ctor
move ctor
***
ctor
copy ctor

When constructing w1, the Wrapper constructor with an rvalue reference parameter is used. In the example with a constructor that takes one parameter, this works nicely and is easy to maintain, but with constructors or functions that take many parameters, the same maintainability issues mentioned above arise again. So, programmers can rely on perfect forwarding to let the compiler decide which constructor to use and generate the required (and desired) code:

struct Wrapper
{
    X w;
     
    template <typename Q>
    Wrapper(Q&& w) : w{std::forward<Q>(w)} { }
};

Nice, isn’t it?

std::forward<T> is declared in the header file <utility>