There is a Student class inherited from Person. And there is Student class inherited from University. I want to change the parent class Person, University based on the option without rewriting Student such as Student1 and Student2 (because student class is very complicated). Here is the example code.
class Person {
void f() {printf("I'm person")}
};
class University {
void f() {printf("I'm university")}
};
class Student1 : public Person {
void g() {f()}
};
class Student2 : public University {
void g() {f()} // I don't wan't to rewrite this!
};
if (option.person) {
Student1 student;
}
else {
Student2 student;
}
CodePudding user response:
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: