std::variant (C++17)
The class template std::variant represents a type-safe union. An instance of std::variant at any given time either holds a value of one of its alternative types, or in the case of error — no value (this state is hard to achieve, see valueless_by_exception).
Variation is a Unified return type.
As with unions, if a variant holds a value of some object type T, the object representation of T is allocated directly within the object representation of the variant itself. Variant is not allowed to allocate additional (dynamic) memory.
A variant is not permitted to hold references, arrays, or the type void. Empty variants are also ill-formed (std::variant<std::monostate> can be used instead).
In a nutshell, Union + Tags = std::variant
Variant is used when the value may fall into one or other category, and the concrete category is determined at run time.
Variant can also be used for just one alternative if addition of new alternatives is foreseen.
Advantages
- It comes in handy when you have a range of potential types for a given value, but you are unsure of its exact type until the runtime.
- You can bypass runtime type checks by representing the value using a std::variant.
- It enhances code’s readability and safety as well.
Usage of same type and different type in std::variant
Program 1
#include <variant>
#include <string>
#include <iostream>
int main()
{
// Different Type
std::variant<int, std::string, float> v = "abc";
std::cout << "v.index = " << v.index() << '\n';
v = {};
std::cout << "v.index = " << v.index() << '\n';
v = 2;
std::cout << "v.index = " << v.index() << '\n';
v = 2.56f;
std::cout << "v.index = " << v.index() << '\n';
// Same type
std::variant<std::string, std::string> v2;
v2.emplace<1>("ghi"); // OK
std::cout << std::get<1>(v2) << '\n';
//std::cout << "Index 0 " << std::get<0>(v2) << '\n'; // Program terminate
//v2.emplace<std::string>("abc"); -> Error
// The below throws an error
// error: static assertion failed: variant must have no void alternative
//std::variant<void, int> v3;
// The below throws an error
//error: invalid use of incomplete type 'struct std::array<int, 5>'
//std::variant<int, std::array<int, 5>> v4;
// Union tags(TokenType) and union(token)
enum TokenType : std::size_t {
Keyword = 0, Identifier = 1, Symbol = 2
};
std::variant<std::string, std::string, char> token;
token.emplace<0>("Hello");
}Output:
v.index = 1
v.index = 0
v.index = 0
v.index = 2
ghiClick here to debug the above program.
There are four primary non-member functions of std::variant
1. std::visit
2. std::holds_alternative
3. std::get
Get Developer Notes’s stories in your inbox
Join Medium for free to get updates from this writer.
4. std::get_if
std::visit, std::holds_alternative, std::get, std::get_if
1. std::variant can have types as struct, enum, empty struct.
2. std::holds_alternative + std::get = std::get_if
3. It’s good practice to use the observer index to log if holds_alternative is used.
Program 2
#include <iostream>
#include <variant>
#include <optional>
#include <atomic>
// helper type for the visitor #4
template<class... Ts>
struct overloaded : Ts... { using Ts::operator()...; };
// explicit deduction guide (not needed as of C++20)
template<class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
class sweet
{
public:
class chocolateC
{
public:
enum class choc
{
name,
no_choc
};
std::atomic<int> name{0};
std::atomic<int> no_choc{0};
};
class ice_creamC
{
public:
enum class ice {
no_icecream,
buy_icecream
};
std::atomic<int> no_icecream{0};
std::atomic<int> buy_icecream{0};
};
chocolateC chocC_;
ice_creamC iceC_;
}; // struct sweet
struct count
{
class sweet sweet_;
struct chocolate
{
std::string name;
std::string no_choc;
};
struct ice_cream
{
int no_icecream;
bool buy_icecream;
};
struct bubblegum {};
using sweets = std::variant<chocolate, ice_cream, bubblegum>;
// function1 returns any of the chocolate member
sweets function1(std::string str)
{
sweets sres;
sres = chocolate();
if (!str.empty())
{
std::get<chocolate>(sres).name = str;
++sweet_.chocC_.name;
} else {
std::get<chocolate>(sres).name = "no chocolate";
++sweet_.chocC_.no_choc;
}
return sres;
}
// function2 returns any of the ice_cream member
sweets function2(bool val)
{
sweets sres;
sres = ice_cream();
if (val)
{
std::get<ice_cream>(sres).buy_icecream = val;
++sweet_.iceC_.buy_icecream;
} else {
std::get<ice_cream>(sres).no_icecream = 5;
++sweet_.iceC_.no_icecream;
}
return sres;
}
void extract (const count::sweets& res)
{
std::cout << "Extract the contents using std::visit "<< std::endl;
return std::visit(overloaded{[&](const chocolate& choc){
if (!choc.name.empty()) {
std::cout << "Chocolate " << sweet_.chocC_.name << std::endl;
} else {
std::cout << "No chocolate " << sweet_.chocC_.no_choc << std::endl;
}
},
[&](const ice_cream& ic){
if (!ic.buy_icecream) {
std::cout << "No icecream " << sweet_.iceC_.no_icecream << std::endl;
} else {
std::cout << "Icecream " << sweet_.iceC_.buy_icecream << std::endl;
}
},
[&](const bubblegum& bg){
std::cout << "No Bubblegum" << std::endl;
}}, res);
}
void print_count(const count::sweets& res)
{
if (const count::chocolate* pval = std::get_if<count::chocolate>(&res)) {
std::cout << "No chocolate " << sweet_.chocC_.no_choc << std::endl;
std::cout << "Chocolate " << sweet_.chocC_.name << std::endl;
} else {
std::cout << "No chocolate content the index is " << res.index() << std::endl;
}
if (std::holds_alternative<count::ice_cream>(res)) {
const auto no_ice = std::get<count::ice_cream>(res).no_icecream;
std::cout << "No icecream " << sweet_.iceC_.no_icecream << std::endl;
std::cout << "Icecream " << sweet_.iceC_.buy_icecream << std::endl;
} else {
std::cout << "No icecream content the index is " << res.index() << std::endl;
}
if (const count::bubblegum* pval = std::get_if<count::bubblegum>(&res)) {
std::cout << "Bubblegum is available " << std::endl;
} else {
std::cout << "No bubblegum content the index is " << res.index() << std::endl;
}
}
}; // struct count
int main()
{
count count_obj;
const auto res = count_obj.function1("kitkat");
count_obj.print_count(res);
count_obj.extract(res);
std::cout << "***********\nAdd icecream\n***********" << std::endl;
const auto ice_res = count_obj.function2(true);
count_obj.print_count(ice_res);
count_obj.extract(ice_res);
}Output:
No chocolate 0
Chocolate 1
No icecream content the index is 0
No bubblegum content the index is 0
Extract the contents using std::visit
Chocolate 1
***********
Add icecream
***********
No chocolate content the index is 1
No icecream 0
Icecream 1
No bubblegum content the index is 1
Extract the contents using std::visit
Icecream 1Click here to debug the above program.
Tips
1. Checking if std::holds_alternative contains true or false is wrong.
Eg 1:
return std::holds_alternative<count::ice_cream>(res)) ? true : false;
Eg 2:
bool ret = std::holds_alternative<count::ice_cream>(res)) ? true : false;How to copy std::variant to another std::variant?
using type_one_t = std::variant<int, double>;
using type_two_t = std::variant<int, std::string, double>;
type_two_t variant_two = std::visit(
[](const auto& value) { return type_two_t{value}; },
variant_one);- The idea is to use the std::visit.
2. It may return either of the types from type_one_t.
3. Then construct a new variant of type type_two_t. This is necessary, because the visitor must return the same type for all alternatives, so we just return the variant we want to construct. — type_two_t{value};
#include <iostream>
#include <map>
#include <unordered_map>
#include <variant>
using type_one_t = std::variant<int, double>;
using type_two_t = std::variant<int, std::string, double>;
int main(int argc, char* argv[])
{
const double dob_val = 34.0;
std::unordered_map<int, type_one_t> m1;
m1.insert(std::make_pair(1, dob_val));
std::map<int, type_two_t> m2;
//for (auto it1 = m1.begin(); it1 != m1.end(); it1++)
// key = it1->first, value = it1->second
for (const auto& [key, value] : m1)
{
type_two_t val = std::visit(
[](const auto& value) { return type_two_t{value}; },
value);
m2.try_emplace(key, std::move(val));
}
return 0;
}Click here to debug the above program.
Disadvantages
- Code bloating
Eg: If a function returns variant, then the caller has to do all the visitation coding to handle the returned value.








