[ad_1]
Across the time C++17 was being standardized I noticed magical phrases like “discriminated union”, “type-safe union” or “sum sort” floating round. Later it appeared to imply the identical sort: “variant”.
Let’s see how this model new std::variant
from C++17 works and the place it may be helpful.
Replace in early 2023 with notes about C++20, C++23 and different smaller refinements/wording.
One thing higher than union
However lest first begin with one thing outdated… unions
…
Unions are not often used within the consumer code, and more often than not, they need to be averted.
For instance, there’s a “frequent” trick with floating-point operations:
union SuperFloat {
float f;
int i;
};
int RawMantissa(SuperFloat f) {
return f.i & ((1 << 23) - 1);
}
int RawExponent(SuperFloat f) {
return (f.i >> 23) & 0xFF;
}
Nevertheless, whereas the above code would possibly work in C99, as a consequence of stricter aliasing guidelines it’s undefined behaviour in C++!
There’s an present Core Guideline Rule on that C.183:
C.183: Don’t use a
union
for sort punning:It’s undefined behaviour to learn a
union
member with a distinct sort from the one with which it was written. Such punning is invisible, or at the least more durable to identify than utilizing a named solid. Kind punning utilizing aunion
is a supply of errors.
There’s additionally further concern with unions: they’re quite simple and crude. You don’t have a option to know what’s the presently used sort and what’s extra they received’t name destructors of the underlying sorts. Right here’s an instance from cppreference/union that clearly illustrate how exhausting it may be:
#embody <iostream>
#embody <string>
#embody <vector>
union S
{
std::string str;
std::vector<int> vec;
~S() { } // what to delete right here?
};
int principal()
{
S s = {"Good day, world"};
// at this level, studying from s.vec is undefined conduct
std::cout << "s.str = " << s.str << 'n';
// it's important to name destructor of the contained objects!
s.str.~basic_string<char>();
// and a constructor!
new (&s.vec) std::vector<int>;
// now, s.vec is the lively member of the union
s.vec.push_back(10);
std::cout << s.vec.dimension() << 'n';
// one other destructor
s.vec.~vector<int>();
}
Play with the code @Coliru
As you see, the S
union wants numerous upkeep out of your facet. It’s important to know which kind is lively and adequately name destructors/constructors earlier than switching to a brand new variant.
That’s the explanation you in all probability received’t see numerous unions that use “superior” sorts reminiscent of vectors, strings, containers, and so forth, and so forth. Union is usually for primary sorts.
What might make unions higher?
- the power to make use of complicated sorts
- and the complete assist of their lifetime: should you swap the kind then a correct destructor is named. That means we don’t leak.
- a option to know what’s the lively sort
Earlier than C++17 you could possibly use some third-party library…. or use enhance::variant
. However now you might have std::variant
.
Primary demo of std::variant
Right here’s a primary demo of what you are able to do with this new sort:
#embody <string>
#embody <iostream>
#embody <variant>
struct SampleVisitor
{
void operator()(int i) const {
std::cout << "int: " << i << "n";
}
void operator()(float f) const {
std::cout << "float: " << f << "n";
}
void operator()(const std::string& s) const {
std::cout << "string: " << s << "n";
}
};
int principal()
{
std::variant<int, float, std::string> intFloatString;
static_assert(std::variant_size_v<decltype(intFloatString)> == 3);
// default initialized to the primary various, ought to be 0
std::go to(SampleVisitor{}, intFloatString);
// index will present the presently used 'sort'
std::cout << "index = " << intFloatString.index() << std::endl;
intFloatString = 100.0f;
std::cout << "index = " << intFloatString.index() << std::endl;
intFloatString = "hiya tremendous world";
std::cout << "index = " << intFloatString.index() << std::endl;
// strive with get_if:
if (const auto intPtr (std::get_if<int>(&intFloatString)); intPtr)
std::cout << "int!" << *intPtr << "n";
else if (const auto floatPtr (std::get_if<float>(&intFloatString)); floatPtr)
std::cout << "float!" << *floatPtr << "n";
if (std::holds_alternative<int>(intFloatString))
std::cout << "the variant holds an int!n";
else if (std::holds_alternative<float>(intFloatString))
std::cout << "the variant holds a floatn";
else if (std::holds_alternative<std::string>(intFloatString))
std::cout << "the variant holds a stringn";
// strive/catch and bad_variant_access
strive
{
auto f = std::get<float>(intFloatString);
std::cout << "float! " << f << "n";
}
catch (std::bad_variant_access&)
{
std::cout << "our variant would not maintain float at this second...n";
}
// go to:
std::go to(SampleVisitor{}, intFloatString);
intFloatString = 10;
std::go to(SampleVisitor{}, intFloatString);
intFloatString = 10.0f;
std::go to(SampleVisitor{}, intFloatString);
}
Play with the code @Coliru
the output:
int: 0
index = 0
index = 1
allocating 18 bytes
index = 2
the variant holds a string
our variant would not maintain float at this second...
string: hiya tremendous world
international op delete known as
int: 10
float: 10
We’ve got a number of issues confirmed within the instance above:
- You already know what’s the presently used sort by way of
index()
or test by way ofholds_alternative
. - You possibly can entry the worth by utilizing
get_if
orget
(however that may throwbad_variant_access
exception) - Kind Security – the variant doesn’t enable to get a worth of the kind that’s not lively
- In case you don’t initialize a variant with a worth, then the variant is initialized with the primary sort. In that case the primary various sort should have a default constructor.
- No further heap allocation occurs
- You need to use a customer to invoke some motion on a presently maintain sort.
- The variant class calls destructors and constructors of non-trivial sorts, so within the instance, the string object is cleaned up earlier than we swap to new variants.
When to Use
I’d say that except you’re doing a little low-level stuff, presumably solely with easy sorts, then unions would possibly nonetheless be okay. However for all different makes use of circumstances, the place you want variant sorts, std::variant
is a option to go!
Some potential makes use of:
- All of the locations the place you would possibly get just a few sorts for a single area: so issues like parsing command strains, ini information, language parsers, and so forth, and so forth.
- Expressing effectively a number of potential outcomes of a computation: like discovering roots of equations
- Error dealing with – for instance you possibly can return
variant<Object, ErrorCode>
. If the worth is offered, then you definately returnObject
in any other case you assign some error code (as of C++23 you should usestd::anticipated
). - State machines
- Polymorphism with out
vtables
and inheritance (due to visiting sample)
A Practical Background
It’s additionally price mentioning that variant sorts (additionally known as a tagged union, a discriminated union, or a sum sort) comes from the practical language world and Kind Principle.
After a little bit demo and introduction, we will now discuss some extra particulars… so learn on.
The Collection
This text is a part of my collection about C++17 Library Utilities. Right here’s the listing of the opposite subjects that I’ll cowl:
Plus different 110+ articles with the C++17 tag @C++Tales.
Assets about C++17 STL:
std::variant
Creation
There are a number of methods you possibly can create and initialize std::variant
:
// default initialization: (sort has to has a default ctor)
std::variant<int, float> intFloat;
std::cout << intFloat.index() << ", worth " << std::get<int>(intFloat) << "n";
// monostate for default initialization:
class NotSimple
{
public:
NotSimple(int, float) { }
};
// std::variant<NotSimple, int> cannotInit; // error
std::variant<std::monostate, NotSimple, int> okInit;
std::cout << okInit.index() << "n";
// cross a worth:
std::variant<int, float, std::string> intFloatString { 10.5f };
std::cout << intFloatString.index() << ", worth " << std::get<float>(intFloatString) << "n";
// ambiguity
// double would possibly convert to drift or int, so the compiler can not determine
//std::variant<int, float, std::string> intFloatString { 10.5 };
// ambiguity resolved by in_place
std::variant<lengthy, float, std::string> longFloatString { std::in_place_index<1>, 7.6 }; // double!
std::cout << longFloatString.index() << ", worth " << std::get<float>(longFloatString) << "n";
// in_place for complicated sorts
std::variant<std::vector<int>, std::string> vecStr { std::in_place_index<0>, { 0, 1, 2, 3 }};
std::cout << vecStr.index() << ", vector dimension " << std::get<std::vector<int>>(vecStr).dimension() << "n";
// copy-initialize from different variant:
std::variant<int, float> intFloatSecond { intFloat };
std::cout << intFloatSecond.index() << ", worth " << std::get<int>(intFloatSecond) << "n";
Play with the code right here @Compiler Explorer
The output:
0, worth 0
0
1, worth 10.5
1, worth 7.6
0, vector dimension 4
0, worth 0
Notes:
- By default, a variant object is initialized with the primary sort,
- if that’s not potential when the kind doesn’t have a default constructor, then you definately’ll get a compiler error
- you should use
std::monostate
to cross it as the primary sort in that case
- You possibly can initialize it with a worth, after which the perfect matching sort is used
- if there’s an ambiguity, then you should use a model
std::in_place_index
to explicitly point out what sort ought to be used.
- if there’s an ambiguity, then you should use a model
std::in_place
additionally lets you create extra complicated sorts and cross extra parameters to the constructor
About std::monostate
Within the instance you would possibly discover a particular sort known as std::monostate
. It’s simply an empty sort that can be utilized with variants to characterize empty state. The kind may be helpful when the primary various doesn’t have a default constructor. In that state of affairs you possibly can place std::monostate
as the primary various.
Altering values
There are 4 methods to alter the present worth of the variant:
- the project operator
emplace
get
after which assign a brand new worth for the presently lively sort- a customer
The vital half is to know that the whole lot is sort protected and likewise the thing lifetime is honoured.
std::variant<int, float, std::string> intFloatString { "Good day" };
intFloatString = 10; // we're now an int
intFloatString.emplace<2>(std::string("Good day")); // we're now string once more
// std::get returns a reference, so you possibly can change the worth:
std::get<std::string>(intFloatString) += std::string(" World");
intFloatString = 10.1f;
if (auto pFloat = std::get_if<float>(&intFloatString); pFloat)
*pFloat *= 2.0f;
See the dwell instance @Coliru
Output:
Good day
10
Good day
Good day World
20.2
Object Lifetime
If you use union
, it is advisable to handle the inner state: name constructors or destructors. That is error susceptible and straightforward to shoot your self within the foot. However std::variant
handles object lifetime as you count on. That signifies that if it’s about to alter the presently saved sort then a destructor of the underlying sort is named.
std::variant<std::string, int> v { "Good day A Fairly Lengthy String" };
// v allocates some reminiscence for the string
v = 10; // we name destructor for the string!
// no reminiscence leak
Or see this instance with a customized sort:
class MyType
{
public:
MyType() { std::cout << "MyType::MyTypen"; }
~MyType() { std::cout << "MyType::~MyTypen"; }
};
class OtherType
{
public:
OtherType() { std::cout << "OtherType::OtherTypen"; }
OtherType(const OtherType&) {
std::cout << "OtherType::OtherType(const OtherType&)n";
}
~OtherType() { std::cout << "OtherType::~OtherTypen"; }
};
int principal()
{
std::variant<MyType, OtherType> v;
v = OtherType();
return 0;
}
This may produce the next output:
MyType::MyType
OtherType::OtherType
MyType::~MyType
OtherType::OtherType(const OtherType&)
OtherType::~OtherType
OtherType::~OtherType
Play with the code @Coliru
Firstly, we initialize with a default worth of sort MyType
; then we modify the worth with an occasion of OtherType
, and earlier than the project, the destructor of MyType
is named. Later we destroy the non permanent object and the thing saved within the variant.
Accessing the Saved Worth
From all the examples, you’ve seen to this point you would possibly get an thought the best way to entry the worth. However let’s make a abstract of this vital operation.
To start with, even when you understand what’s the presently lively sort you can not do:
std::variant<int, float, std::string> intFloatString { "Good day" };
std::string s = intFloatString;
// error: conversion from
// 'std::variant<int, float, std::string>'
// to non-scalar sort 'std::string' requested
// std::string s = intFloatString;
So it’s important to use helper features to entry the worth.
You’ve gotten std::get<Kind|Index>(variant)
which is a non member operate. It returns a reference to the specified sort if it’s lively (You possibly can cross a Kind or Index). If not then you definately’ll get std::bad_variant_access
exception.
std::variant<int, float, std::string> intFloatString;
strive
{
auto f = std::get<float>(intFloatString);
std::cout << "float! " << f << "n";
}
catch (std::bad_variant_access&)
{
std::cout << "our variant would not maintain float at this second...n";
}
The following choice is std::get_if
. This operate can also be a non-member and received’t throw. It returns a pointer to the lively sort or nullptr
. Whereas std::get
wants a reference to the variant, std::get_if
takes a pointer. I’m undecided why we now have this inconsistency.
if (const auto intPtr = std::get_if<0>(&intFloatString))
std::cout << "int!" << *intPtr << "n";
Nevertheless, in all probability a very powerful option to entry a worth inside a variant is by utilizing guests.
Guests for std::variant
With the introduction of std::variant
we additionally bought a helpful STL operate known as std::go to
.
It will probably name a given “customer” on all handed variants.
Right here’s the declaration:
template <class Customer, class... Variants>
constexpr go to(Customer&& vis, Variants&&... vars);
And it’ll name vis
on the presently lively sort of variants.
In case you cross just one variant, then it’s important to have overloads for the categories from that variant. In case you give two variants, then it’s important to have overloads for all potential pairs of the categories from the variants.
A customer is “a callable that accepts each potential various from each variant “.
Let’s see some examples:
// a generic lambda:
auto PrintVisitor = [](const auto& t) { std::cout << t << "n"; };
std::variant<int, float, std::string> intFloatString { "Good day" };
std::go to(PrintVisitor, intFloatString);
Within the above instance, a generic lambda is used to generate all potential overloads. Since all the sorts within the variant helps <<
then we will print them.
Within the one other case we will use a customer to alter the worth:
auto PrintVisitor = [](const auto& t) { std::cout << t << "n"; };
auto TwiceMoreVisitor = [](auto& t) { t*= 2; };
std::variant<int, float> intFloat { 20.4f };
std::go to(PrintVisitor, intFloat);
std::go to(TwiceMoreVisitor, intFloat);
std::go to(PrintVisitor, intFloat);
Generic lambdas can work if our sorts share the identical “interface”, however in many of the circumstances, we’d love to do some totally different actions primarily based on an lively sort.
That’s why we will outline a construction with a number of overloads for the operator ()
:
struct MultiplyVisitor
{
float mFactor;
MultiplyVisitor(float issue) : mFactor(issue) { }
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 right here...
}
};
std::go to(MultiplyVisitor(0.5f), intFloat);
std::go to(PrintVisitor, intFloat);
Within the instance, you would possibly discover that I’ve used a state to carry the specified scaling issue worth.
With lambdas, we bought used to declaring issues simply subsequent to its utilization. And when it is advisable to write a separate construction, it is advisable to exit of that native scope. That’s why it may be helpful to make use of overload
building.
Overload
With this utility you possibly can write all a number of lambdas for all matching sorts in a single place:
std::go to
(
overload
(
[](const int& i) { PRINT("int: " + i); },
[](const std::string& s) { PRINT("it is a string: " + s); },
[](const float& f) { PRINT("float" + f); }
),
yourVariant;
);
At present this helper just isn’t a part of the library (it would get into with C++20), however the code would possibly appear like that:
template<class... Ts> struct overload : Ts... { utilizing Ts::operator()...; };
template<class... Ts> overload(Ts...) -> overload<Ts...>;
These two strains appear like a little bit of magic 🙂 However all they do is that they create a struct that inherits all given lambdas and makes use of their Ts::operator()
. The entire construction could be now handed to std::go to
.
For instance:
std::variant<int, float, std::string> intFloatString { "Good day" };
std::go to(overload{
[](int& i) { i*= 2; },
[](float& f) { f*= 2.0f; },
[](std::string& s) { s = s + s; }
}, intFloatString);
std::go to(PrintVisitor, intFloatString);
// prints: "HelloHello"
Play with the code @Coliru
Right here’s a full weblog publish explaining the characteristic: 2 Traces Of Code and three C++17 Options – The overload Sample – C++ Tales.
And different articles:
Different std::variant
Operations
Only for the sake of completeness:
- You possibly can evaluate two variants of the identical sort:
- in the event that they include the identical lively various then the corresponding comparability operator is named.
- If one variant has an “earlier” various then it’s “much less than” the variant with the subsequent lively various.
- Variant is a worth sort, so you possibly can transfer it.
- std::hash on a variant can also be potential.
Exception Security Ensures
To date the whole lot seems good and clean… however what occurs when there’s an exception in the course of the creation of the choice in a variant?
For instance
class ThrowingClass
{
public:
specific ThrowingClass(int i) { if (i == 0) throw int (10); }
operator int () { throw int(10); }
};
int principal(int argc, char** argv)
{
std::variant<int, ThrowingClass> v;
// change the worth:
strive
{
v = ThrowingClass(0);
}
catch (...)
{
std::cout << "catch(...)n";
// we preserve the outdated state!
std::cout << v.valueless_by_exception() << "n";
std::cout << std::get<int>(v) << "n";
}
// inside emplace
strive
{
v.emplace<0>(ThrowingClass(10)); // calls the operator int
}
catch (...)
{
std::cout << "catch(...)n";
// the outdated state was destroyed, so we're not in invalid state!
std::cout << v.valueless_by_exception() << "n";
}
return 0;
}
Play with the code @Coliru
The output:
catch(...)
0
0
catch(...)
1
Within the first case – with the project operator – the exception is thrown within the constructor of the kind. This occurs earlier than the outdated worth is changed within the variant, so the variant state is unchanged. As you possibly can see we will nonetheless entry int
and print it.
Nevertheless, within the second case – emplace
– the exception is thrown after the outdated state of the variant is destroyed. Emplace calls operator int
to switch the worth, however that throws. After that, the variant is in a fallacious state, as we can not get well.
Additionally observe {that a} variant that’s “worthless by exception” is in an invalid state. Accessing a worth from such variant just isn’t potential. That’s why variant::index
returns variant_npos
, and std::get
and std::go to
will throw bad_variant_access
.
Efficiency & Reminiscence Issues
std::variant
makes use of the reminiscence in an identical option to union: so it is going to take the max dimension of the underlying sorts. However since we’d like one thing that may know what’s the presently lively various, then we have to add some more room.
Plus the whole lot must honour the alignment guidelines.
Listed below are some primary sizes:
std::cout << "sizeof string: "
<< sizeof(std::string) << "n";
std::cout << "sizeof variant<int, string>: "
<< sizeof(std::variant<int, std::string>) << "n";
std::cout << "sizeof variant<int, float>: "
<< sizeof(std::variant<int, float>) << "n";
std::cout << "sizeof variant<int, double>: "
<< sizeof(std::variant<int, double>) << "n";
On GCC 8.1, 32 bit I’ve:
sizeof string: 32
sizeof variant<int, string>: 40
sizeof variant<int, float>: 8
sizeof variant<int, double>: 16
Play with the code @Coliru
What’s extra fascinating is that std::variant
received’t allocate anyextra area! No dynamic allocation occurs to carry variants. and the discriminator.
Whilst you pay some further area for all of the type-safe performance, it shouldn’t value you relating to runtime efficiency.
Migration From enhance::variant
Increase Variant was launched across the 12 months 2004, so it was 13 years of expertise earlier than std::variant
was added into the Customary. The STL sort takes from the expertise of the enhance model and improves it.
Listed below are the primary adjustments:
Additional reminiscence allocation | Attainable on project, see Design Overview – By no means Empty | No |
visiting | apply_visitor | std::go to |
get by index | no | sure |
recursive variant | sure, see make_recursive_variant | no |
duplicated entries | no | sure |
empty various | enhance::clean |
std::monostate |
You can even see the slides from Variants – Previous, Current, and Future – David Sankel – CppCon 2016 The place there’s extra dialogue concerning the adjustments and the proposal.
Or the video @Youtube
Examples of std::variant
After we realized many of the std::variant
particulars, we will now discover just a few examples. To date, the code I used was a bit synthetic, however on this part, I attempted to search for some real-life examples.
Error Dealing with
The fundamental thought is to wrap the potential return sort with some ErrorCode, and that means enable to output extra details about the errors. With out utilizing exceptions or output parameters. That is just like what std::anticipated
may be in C++23..
enum class ErrorCode
{
Okay,
SystemError,
IoError,
NetworkError
};
std::variant<std::string, ErrorCode> FetchNameFromNetwork(int i)
{
if (i == 0)
return ErrorCode::SystemError;
if (i == 1)
return ErrorCode::NetworkError;
return std::string("Good day World!");
}
int principal()
{
auto response = FetchNameFromNetwork(0);
if (std::holds_alternative<std::string>(response))
std::cout << std::get<std::string>(response) << "n";
else
std::cout << "Error!n";
response = FetchNameFromNetwork(10);
if (std::holds_alternative<std::string>(response))
std::cout << std::get<std::string>(response) << "n";
else
std::cout << "Error!n";
return 0;
}
Play with the instance @Coliru
Within the instance, I’m returning ErrorCode
or a sound sort – on this case, a string.
In C++23 you should use
std::anticipated
. See at std::anticipated – cppreference.com.
Computing Roots of an Equation
Generally the computation would possibly give us a number of choices, for instance, actual roots of the equation. With variant, we will wrap all of the obtainable choices and specific clearly what number of roots can we discover.
utilizing DoublePair = std::pair<double, double>
utilizing EquationRoots = std::variant<DoublePair, double, std::monostate>;
EquationRoots FindRoots(double a, double b, double c)
{
if (a == 0.0)
return std::nan("");
auto d = b*b-4*a*c; // discriminant
if (d > 0.0)
{
double root1 = (-b + std::sqrt(d)) / (2*a);
double root2 = (-b - std::sqrt(d)) / (2*a);
return std::make_pair(root1, root2);
}
else if (d == 0.0)
return (-1*b)/(2*a);
return std::monostate();
}
struct RootPrinterVisitor
{
void operator()(const DoublePair& arg)
{
std::cout << "2 roots: " << arg.first << " " << arg.second << 'n';
}
void operator()(double arg)
{
if (std::isnan(arg))
std::cout << "not a quadratic equation!" << arg << 'n';
else
std::cout << "1 root discovered: " << arg << 'n';
}
void operator()(std::monostate)
{
std::cout << "No actual roots discovered.n";
}
};
int principal()
{
std::go to(RootPrinterVisitor{}, FindRoots(10,0,-2));
std::go to(RootPrinterVisitor{}, FindRoots(2,0,-1));
std::go to(RootPrinterVisitor{}, FindRoots(1,2,1));
std::go to(RootPrinterVisitor{}, FindRoots(1,2,3));
std::go to(RootPrinterVisitor{}, FindRoots(0,2,3));
}
Play with the code @Compiler Explorer.
The output:
2 roots discovered: 0.447214 -0.447214
2 roots discovered: 0.707107 -0.707107
1 root discovered: -1
No actual roots discovered.
not a quadratic equation! nan
Parsing a Command Line
Command line would possibly include textual content arguments that may be interpreted in just a few methods:
- as integer
- as boolean flag
- as a string (not parsed)
- …
We will construct a variant that may maintain all of the potential choices.
Right here’s a easy model with int
and string
:
class CmdLine
{
public:
utilizing Arg = std::variant<int, std::string>;
non-public:
std::map<std::string, Arg> mParsedArgs;
public:
specific CmdLine(int argc, char** argv) { ParseArgs(argc, argv); }
// ...
};
And the parsing code:
CmdLine::Arg TryParseString(char* arg)
{
// strive with int first
int iResult = 0;
auto res = std::from_chars(arg, arg+strlen(arg), iResult);
if (res.ec == std::errc::invalid_argument)
{
// if not potential, then simply assume it is a string
return std::string(arg);
}
return iResult;
}
void CmdLine::ParseArgs(int argc, char** argv)
{
// the shape: -argName worth -argName worth
// unnamed? later...
for (int i = 1; i < argc; i+=2)
{
if (argv[i][0] != '-') // tremendous superior sample matching! :)
throw std::runtime_error("fallacious command identify");
mParsedArgs[argv[i]+1] = TryParseString(argv[i+1]);
}
}
For the time being of writing, std::from_chars
in GCC solely helps integers, in MSVC floating level assist is on the best way. However the thought of the TryParseString
is to strive with parsing the enter string to the perfect matching sort. So if it seems like an integer, then we attempt to fetch integer. In any other case, we’ll return an unparsed string. After all, we will prolong this method.
Instance how we will use it:
strive
{
CmdLine cmdLine(argc, argv);
auto arg = cmdLine.Discover("paramInt");
if (arg && std::holds_alternative<int>(*arg))
std::cout << "paramInt is "
<< std::get<int>(*arg) << "n";
arg = cmdLine.Discover("textParam");
if (arg && std::holds_alternative<std::string>(*arg))
std::cout << "textParam is "
<< std::get<std::string>(*arg) << "n";
}
catch (std::runtime_error &err)
{
std::cout << err.what() << "n";
}
Play with the code @Coliru
Parsing a Config File
I don’t have a code for that, however the thought comes from the earlier instance of a command line. Within the case of a configuration file, we often work with pairs of <Identify, Worth>
. The place Worth
may be a distinct sort: string
, int
, array, bool
, float
, and so forth.
In my expertise I’ve seen examples the place even void*
was used to carry such unknown sort so we might enhance the design by utilizing std::variant
if we all know all of the potential sorts, or leverage std::any
.
State Machines
How about modelling a state machine? For instance door’s state:
We will use several types of states and the use guests as occasions:
struct DoorState
{
struct DoorOpened {};
struct DoorClosed {};
struct DoorLocked {};
utilizing State = std::variant<DoorOpened, DoorClosed, DoorLocked>;
void open()
{
m_state = std::go to(OpenEvent{}, m_state);
}
void shut()
{
m_state = std::go to(CloseEvent{}, m_state);
}
void lock()
{
m_state = std::go to(LockEvent{}, m_state);
}
void unlock()
{
m_state = std::go to(UnlockEvent{}, m_state);
}
State m_state;
};
And listed here are the occasions:
struct OpenEvent
{
State operator()(const DoorOpened&){ return DoorOpened(); }
State operator()(const DoorClosed&){ return DoorOpened(); }
// can not open locked doorways
State operator()(const DoorLocked&){ return DoorLocked(); }
};
struct CloseEvent
{
State operator()(const DoorOpened&){ return DoorClosed(); }
State operator()(const DoorClosed&){ return DoorClosed(); }
State operator()(const DoorLocked&){ return DoorLocked(); }
};
struct LockEvent
{
// can not lock opened doorways
State operator()(const DoorOpened&){ return DoorOpened(); }
State operator()(const DoorClosed&){ return DoorLocked(); }
State operator()(const DoorLocked&){ return DoorLocked(); }
};
struct UnlockEvent
{
// can not unlock opened doorways
State operator()(const DoorOpened&){ return DoorOpened(); }
State operator()(const DoorClosed&){ return DoorClosed(); }
// unlock
State operator()(const DoorLocked&){ return DoorClosed(); }
};
Play with the code utilizing the next instance: @Coliru
The concept is predicated on the weblog posts:
Polymorphism
More often than not in C++ we will safely use runtime polymorphism primarily based on v-table
method. You’ve gotten a group of associated sorts – that shares the identical interface, and you’ve got a properly outlined digital technique that may be invoked.
However what in case you have “unrelated” sorts that don’t share the identical base class? What should you’d prefer to rapidly add new performance with out altering the code of the supported sorts?
In such conditions, we now have a helpful sample of Customer. I’ve even described in my older publish.
With std::variant
and std::go to
we will construct the next instance:
class Triangle
{
public:
void Render() { std::cout << "Drawing a triangle!n"; }
};
class Polygon
{
public:
void Render() { std::cout << "Drawing a polygon!n"; }
};
class Sphere
{
public:
void Render() { std::cout << "Drawing a sphere!n"; }
};
int principal()
{
std::vector<std::variant<Triangle, Polygon, Sphere>> objects {
Polygon(),
Triangle(),
Sphere(),
Triangle()
};
auto CallRender = [](auto& obj) { obj.Render(); };
for (auto& obj : objects)
std::go to(CallRender, obj);
}
Play with the code: @Coliru
The output:
Drawing a polygon!
Drawing a triangle!
Drawing a sphere!
Drawing a triangle!
Within the above instance, I’ve proven solely the primary case of invoking a technique from unrelated sorts. I wrap all of the potential form sorts right into a single variant after which use a customer to dispatch the decision to the right sort.
In case you’d like, for instance, to type objects, then we will write one other customer, that holds some state. And that means you enable to have extra performance with out altering the categories.
I even have one other article the place I clarify this sort of polymorphism intimately: Runtime Polymorphism with std::variant and std::go to – C++ Tales.
You possibly can discover extra about this sample and its benefits in:
Different Makes use of
There are numerous many extra instance, see this tweet:
do you might have any real-life examples of
std::variant?#cpp
#cpp17— Bartlomiej Filipek (@fenbf) 24 maja
2018
You possibly can open this tweet and comply with the dialogue.
C++20 & C++23 enhancements
After a few years with C++20 and C++23 std::variant
bought just a few updates. For instance due to P2231 applied in opposition to C++20, we will use std::variant
in fixed expressions:
#embody <iostream>
#embody <variant>
constexpr std::variant<int, double> foo(int x) {
std::variant<int, double> oi { x * 0.1 };
if (x > 10)
oi = x;
return oi;
}
int principal() {
constexpr auto dv = foo(1);
static_assert(std::holds_alternative<double>(dv));
constexpr auto iv = foo(100);
static_assert(std::holds_alternative<int>(iv));
}
See at Compiler Explorer.
Moreover there was additionally an vital repair for circumstances like:
variant<string, bool> x = "abc"; // holds bool !!
Now with A sane std::variant changing constructor you possibly can count on that "abc"
string literal will converst into std::string
.
And you may take a look at different options like: std::go to()
for courses derived from std::variant
and std::variant
and std::elective
ought to propagate copy/transfer triviality.
Wrap Up
After studying this publish, you have to be outfitted with all of the information required to make use of std::variant
in your initiatives!
Whereas an identical sort has been obtainable for years – within the type of enhance.variant – I’m comfortable to see the official STL model. That means we will count on increasingly code that makes use of this helpful wrapper sort.
Listed below are the issues to recollect about std::variant
:
- It holds one among a number of options in a type-safe means
- No further reminiscence allocation is required. The variant wants the dimensions of the max of the sizes of the options, plus some little further area for figuring out the presently lively worth.
- By default, it initializes with the default worth of the primary various
- You possibly can assess the worth by utilizing
std::get
,std::get_if
or by utilizing a type of a customer. - To test the presently lively sort you should use
std::holds_alternative
orstd::variant::index
std::go to
is a option to invoke an operation on the presently lively sort within the variant. It’s a callable object with overloads for all of the potential sorts within the variant(s).- Not often
std::variant
would possibly get into invalid state, you possibly can test it by way ofvalueless_by_exception
I’d prefer to thank Patrice Roy (@PatriceRoy1), Mandar Kulkarni (@mjkcool) for locating time to do a evaluate of this text!
See additionally another posts about std::variant
:
Again to you
- Have you ever tried
std::variant
? - What’s the commonest use case for you?
Tell us within the feedback under.
[ad_2]