std::variant
std::vector<std::string>
)Things you can do:
#include <variant>
std::variant<int, float, string> intFloatString;
struct PrintVisitor
{
void operator() (int i) { cout << "int: " << i << '\n'; }
void operator() (float f) { cout << "float: " << f << '\n'; }
void operator() (const string& s) { cout << "str: " << s << '\n'; }
};
std::visit(PrintVisitor{}, intFloatString);
OUTPUT:
int: 0
intFloatString = “hello super world”; cout « “index = “ « intFloatString.index() « endl;
OUTPUT index = 1 index = 2
* Use `getIf<Type>` to get a pointer to an object of Type. Returns nullptr if the variant doesn’t currently store an object of this type
if (const auto intPtr = get_if
OUTPUT:
* Use `get<Type>` to retrieve a reference to an object of `Type`. This will throw a `bad_variant_access` exception if it doesn’t contain one of those types:
try
{
auto f = std::get
* Use` holds_alternative<int> ()`
if (holds_alternative
OUTPUT: the variant holds a string
* Other things to note:
* No extra heap allocation occurs
* Constructors/Destructors of non-trivial classes are called when switching
## Creation
* Default construction - as before
std::variant<int, float> intFloat;
std::cout « intFloat.index() « ”, val: “ « std::get
Output: 0, val: 0
* Monostate for default initialisation
class NotSimple { public: NotSimple(int, float) {} };
std::variant<NotSimple, int> cannotInit; // error std::variant<std::monostate, NotSimple, int> okInit; std::cout « okInit.index() « ‘\n’;
Output: 0
* Passing a value:
std::variant<int, float, std::string> intFloatString { 10.5f };
std::cout « intFloatString.index() « ”, value “ « std::get
Output: 1
* Watch out for ambiguity:
std::variant<int, float, std::string> intFloatString { 10.5 }; //Compiler error
* In place
* Can be used to resolve ambiguity
* And for efficient creation of complex types (similar to std::optional)
* `std::in_place_type` and `std::in_place_index`
std::variant<long, float, std::string> longFloatString { std::in_place_index<1>, 7.6 // double! };
// could also be:
variant<long, float, std::string> longFloatString
{
std::in_place_type
std::cout « longFloatString.index() « ”, value “
« std::get
// in_place for complex types
std::variant<std::vector
std::cout « vecStr.index() « ”, vector size “
« std::get<std::vector
Output: 1, value 7.6 0, vector size 4
* Copy initialisation
std::variant<int, float> intFloatSecond { intFloat };
std::cout « intFloatSecond.index() « ”, value “
« std::get
Output: 0, value 0
* Unwanted Type Narrowing and Conversion
* First implentation used regular c++ expressions which produced some odd results. Fixed in GCC 10.0
* What does the following lines code produce?
std::variant<bool, string> v = “Hello”;
std::variant<float, optional
|Expression| Before | After |
|--|--|--|
|`std::variant<bool, string> v = "Hello"` | `bool` | `string` |
|`std::variant<float, optional<double>> x = 10.05` | `float` | `optional` |
|`std::variant<float, char> v = 0` | ill-formed | ill-formed |
|` std::variant<float, long> v = 0` | ill-formed | `long` |
Changing the Values
* Four types (well 5 types really):
* assignment operator
std::variant<int, float, std::string> intFloatString { “Hello” }; intFloatString = 10; // we’re now an int
* emplace (use index as template parameter)
intFloatString.emplace<2> (std::string (“Hello”)); // we’re now string again
* using the reference returned from get
std::get
* Using getIf to change a pointer to the object
intFloatString = 10.1f;
if (auto pFloat = std::get_if
* Using a visitor (can’t change type but can change the value of the current type)
## Object Lifetime
* `std::variant` manages object lifetime (unlinke c-style union)
* calls the appropriate constructors and destructors when changing types:
{ std::variant<std::string, int> v { “Hello A Quite Long String” }; // v allocates some memory for the string v = 10; // we call destructor for the string! } // no memory leak
* Or using custom classes
class MyType { public: MyType() { std::cout « “MyType::MyType\n”; } ~MyType() { std::cout « “MyType::~MyType\n”; } };
class OtherType { public: OtherType() { std::cout « “OtherType::OtherType\n”; } OtherType (const OtherType&) { std::cout « “OtherType::OtherType copy ctor\n”; } ~OtherType() { std::cout « “OtherType::~OtherType\n”; } };
int main() { std::variant<MyType, OtherType> v; v = OtherType(); return 0; }
OUTPUT MyType::MyType OtherType::OtherType MyType::~MyType OtherType::OtherType copy ctor OtherType::~OtherType OtherType::~OtherType
## Access
* Cannot do something like this:
std::variant<int, float, std::string> intFloatString { “Hello” }; std::string s = intFloatString;
* You can use `get<Type>` with either `Type` or the index of the type.
* Will throw an exception if it is the wrong type.
std::variant<int, float, std::string> intFloatString;
try
{
auto f = std::get
// alternative
auto f = std::get<1> (intFloatString); } catch (std::bad_variant_access&) {
std::cout << "our variant doesn't hold float at this moment...\n"; } ``` * Alternatively use `get_if<>`, again with either `Type` or an index.
* Also non-member but won’t throw.
* Returns a pointer to `Type` or `nullptr`
* Takes pointer to object (whereas get takes a reference) ``` if (const auto intPtr = std::get_if<0> (&intFloatString)
std::cout << "int!" << *intPtr << '\n'; ```
std::visit
function, nb multiple vars:
template <class Visitor, class... Variants>
constexpr visit(Visitor&& vis, Variants&&... vars);
std::variant<int, float, std::string> intFloatString { “Hello” }; std::visit (PrintVisitor, intFloatString);
OUTPUT Hello
* Can also modify variant:
auto PrintVisitor = { std::cout « t « ‘\n’; }; auto TwiceMoreVisitor = { t*= 2; };
std::variant<int, float> intFloat { 20.4f }; std::visit (PrintVisitor, intFloat); std::visit (TwiceMoreVisitor, intFloat); std::visit(PrintVisitor, intFloat);
OUTPUT 20.4 40.8
* Generic lambdas are simple and useful, but we can also define a structure with overloaded `operator()`
* when we don’t want to treat all types the same,
* or we want to use some kind of state in our visitor
struct MultiplyVisitor { float mFactor;
MultiplyVisitor (float factor) : mFactor (factor) { }
void operator() (int& i) const
{
i *= static_cast<int> (mFactor);
}
void operator()(float& f) const
{
f *= mFactor;
}
void operator()(std::string& ) const
{
// nothing to do here...
} };
std::visit (MultiplyVisitor (0.5f), intFloat); std::visit (PrintVisitor, intFloat);
OUTPUT 10.2
* Useful feature, but not currently in standard library (may be in C++20), is the overload structure:
template<class… Ts> struct overload : Ts… { using Ts::operator()…; }; template<class… Ts> overload(Ts…) -> overload<Ts…>;
* Creates a struct that inherits from the lambdas
std::variant<int, float, std::string> myVariant; std::visit( std::overload { { std::cout « “int: “ « i; }, { std::cout « “string: “ « s; }, { std::cout « “float: “ « f; } }, myVariant );
* Visiting multiple variants
* Visitor must provide overloads for all combinations of variants.
* So to visit 2 variants, each with 3 types, the visitor must provide overloads for all 9 combinations.
* Compiler error if not
std::variant<int, float, char> v1 { ‘s’ }; std::variant<int, float, char> v2 { 10 };
std::visit( overload { { }, { }, { }, { }, { }, { }, { }, { }, { } }, v1, v2);
* Athough generic lambdas can be used alongside/instead:
struct Pizza { }; struct Chocolate { }; struct Salami { }; struct IceCream { };
int main() { std::variant<Pizza, Chocolate, Salami, IceCream> firstIngredient{IceCream()}; std::variant<Pizza, Chocolate, Salami, IceCream> secondIngredient{Chocolate()};
std::visit(overload{ { std::cout « “here you have, Pizza with Salami!\n”; }, { std::cout « “here you have, Pizza with Salami!\n”; }, { std::cout « “Chocolate with IceCream!\n”; }, { std::cout « “IceCream with a bit of Chocolate!\n”; }, { std::cout « “invalid composition…\n”; }, }, firstIngredient, secondIngredient);
return 0; }
OUTPUT: IceCream with a bit of Chocolate!
* Operations
* Comparisons: If objects are of the same type then it calls corresponding operator, if not then the ‘earlier’ type (lower Type index) is less than.
* `std::move`: Can be used
* `std::hash` is availble if all types are hasable
* Exception Safety Guarantees
class ThrowingClass { public: explicit ThrowingClass(int i) { if (i == 0) throw int (10); } operator int () { throw int(10); } };
int main(int argc, char** argv) { std::variant<int, ThrowingClass> v;
// change the value:
try {
v = ThrowingClass(0);
}
catch (...) {
std::cout << "catch(...)\n";
// we keep the old state!
std::cout << v.valueless_by_exception() << '\n'; std::cout << std::get<int>(v) << '\n';
}
// inside emplace
try {
v.emplace<0>(ThrowingClass(10)); // calls the operator int
}
catch (...) {
std::cout << "catch(...)\n";
// the old state was destroyed, so we're not in invalid state!
std::cout << v.valueless_by_exception() << '\n';
}
return 0; } ``` * If an exception is thrown during destruction:
* Assignment: Previous type will still be valid
* Emplace(): State will be invalid (and can be checked using valueless_by_exception() member function).
* Calling index() on an invalid state will return variant::npos, and std::get and std::visit will throw a variant_bad_access * Performance & Memory Considerations
* Will need to be large enough to store largest type
* Plus a discriminator
* Usual alignment rules apply
std::variant<Object, ErrorCode>
so you can return an Object
if successful, or an error code if not.