Since we can't know what option.person
is at compile-time, we need to find a way to work around that at runtime.
One option for doing so is std::variant
, which can store any number of different types; but does so at the cost of always having the same size as the largest templated type.
As an example, if I did this:
std::variant<char, int> myVariant = '!';
Even though myVariant
holds a char
(1 byte), it uses 4 bytes of RAM because an int
is 4 bytes.
Using Variants
Rather than inheriting from different objects that do not share a common base at compile-time, we can maintain the 'base' type as a variable within Student
instead.
#include <iostream>
#include <variant>
#include <concepts>
class Person {
public:
void f()
{
std::cout << "I'm a person!\n";
}
};
class University {
public:
void f()
{
std::cout << "I'm a university!\n";
}
};
class Student {
public:
using variant_t = std::variant<Person, University>;
variant_t base;
// Here we accept an rvalue of any type, then we move it to the 'base' variable.
// if the type is not a Person or University, a compiler error is thrown.
Student(auto&& owner) : base{ std::move(owner) } {}
void g()
{
// METHOD 1: Using std::holds_alternative & std::get
// This has the advantage of being the simplest & easiest to understand.
if (std::holds_alternative<Person>(base))
std::get<Person>(base).f();
else if (std::holds_alternative<University>(base))
std::get<University>(base).f();
// METHOD 2: Using std::get_if
// This has the advantage of being the shortest.
if (auto* person = std::get_if<Person>(&base))
person->f();
else if (auto* university = std::get_if<University>(&base))
university->f();
// METHOD 3: Using std::visit
// This has the advantage of throwing a meaningful compiler error if-
// -we modify `variant_t` and end up passing an unhandled type.
std::visit([](auto&& owner) {
using T = std::decay_t<decltype(owner)>;
if constexpr (std::same_as<T, Person>)
owner.f(); //< this calls `Person::f()`
else if constexpr (std::same_as<T, University>)
owner.f(); //< this calls `University::f()`
else static_assert(false, "Not all potential variant types are handled!");
}, base);
}
};
In this example, I showed 3 different methods of accessing the underlying value of base
.
As a result, the output is:

Further reading: