std::any
Any class can hold any type of object. It’s type safe container for single values of any type. A better way to handle any type and replace void*.
Type requeirments:
std::decay_t<T> – must be CopyConstructable
Example:
1 2 3 |
std::any x = 1; x = 0.111; x = “123”; |
To get value from std::any you need to use std::any_cast.
1 2 |
std::any x= 10; std::cout << std::any_cast<int>(x) << std::endl; |
In case if type in std::any_cast is wrong will be called std::bad_any_cast exception.
1 2 3 |
auto k = std::any(12); std::cerr << std::any_cast<int>(k) << std::endl; // ok std::cerr << std::any_cast<std::string>(k) << std::endl; // error - bad_any_cast |
Exception hierarchy:
- std::bad_any_cast -> std::bad_cast -> std::exception
Catch bad_any_cast example:
1 2 3 4 5 6 7 8 |
try{ auto k = std::any(12); std::any_cast<std::string>(k); // error } catch(const std::bad_any_cast& e) { std::cerr << e.what() << std::endl; } |
Implementations are encouraged to avoid dynamic allocations for small objects, but such an optimization may only be applied to types for which std::is_nothrow_move_constructible returns true.
any has own member functions:
has_value() – returns bool has it value or not
type() – returns std::type_info about object
reset() – drop object
emplace() – change object directly
To get know which type is inside std::any:
1 |
x.type().name(); |
Usage example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// you can use it in a container: std::map<std::string, std::any> m; m["integer"] = 10; m["string"] = std::string("Hello World"); m["float"] = 1.0f; for (auto &[key, val] : m) { if (val.type() == typeid(int)) std::cout << "int: " << std::any_cast<int>(val) << "\n"; else if (val.type() == typeid(std::string)) std::cout << "string: " << std::any_cast<std::string>(val) << "\n"; else if (val.type() == typeid(float)) std::cout << "float: " << std::any_cast<float>(val) << "\n"; } |
std::optional
std::optional it’s type which can represent nullable type. Optional means that value can be inside or cannot.
It’s common to avoid some return values like -1 , nullptr, NO_VALUE, etc or where data can be optional.
like:
1 2 3 |
std::string name; std::optional<std::string> surname; std::optional<std::string> email; |
For example:
1 2 3 4 5 6 |
std::optional<std::string> Get1(bool bool_) { if (bool_) return "123"; return {}; } |
and then we can check it, there is two methods:
value() – returns object
has_value() – is value exists
std::variant
std::variant it’s some kind of old c++ feature union, but it’s type safe and can hold complicated types, not only simple build-in types.
std::variant is not allowed to hold references, type void, arrays. variant do not use additional memory allocations in heap. Empty variant is also not allowed. variant can have same type more then once and with different cv.
Example:
1 2 3 4 5 |
std::variant<int, float, double> var; var = 10; var = 2.1f; std::cout << std::get<int>(var) ; // error: last type is float std::cout << std::get<float>(var); // OK |
In case getting wrong type from std::variant, std::bad_variant_access exception will be called.
1 2 3 4 5 6 |
try { std::get<int>(var); } catch(const std::bad_variant_access&){ std::cout << “wrong variant type!” << std::endl; } |
If first type has no default constructor, it will be error while creating std::variant object.
For example:
1 2 3 4 5 6 7 8 9 |
class A { public: A(int a){} int x; }; int main() { std::variant<A, float, int> var; // error: A don’t have default constructor } |
to avoid this issue, you can use std::monostate.
1 |
std::variant<std::monostate, A, float, int> var; // OK |
Reading values from std::variant using std::visitor.
For example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
class A { public: A(int a) :x{ a } {} int x{ 10 }; }; struct Visit { void operator()(int a) { std::cout << "int = " << a << std::endl; } void operator()(float a) { std::cout << "float = " << a << std::endl; } void operator()(A a) { std::cout << "A = " << a.x << std::endl; } void operator()(std::monostate) {} }; int main() { std::variant<std::monostate, A, int, float> x; A a{ 5 }; x = 10; x = 2.1f; std::visit(Visit{}, x); x = A{3}; std::visit(Visit{}, x); return 0; } |
If you have the same interface for types, you can use generic lambda for access.
1 2 3 4 5 6 7 8 9 10 |
int main() { std::variant<int, float, std::string> var; auto l = [](const auto& value) {std::cout << value << std::endl;}; var = 10; std::visit(l, var); var = "123"; std::visit(l, var); return 0; } |
In this case, each type can be printed.
Also using std::variant it’s possible to create polymorphism without using v-table.
Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
class A { public: void DoJob(){} }; class B { public: void DoJob(){} }; class C { public: void DoJob(){} }; int main() { std::vector<std::variant<A, B, C>> vector = { A{}, B{}, C{} }; auto visitor = [](auto& obj) { obj.DoJob();}; for(auto& obj: vector) std::visit(visitor, obj); return 0; } |
One interesting thing is overloading lambdas inside visitor:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
template<class F1, class F2> struct overload: F1, F2 { overload(F1 const& f1, F2 const& f2):F1{f1}, F2{f2}{} using F1::operator(); using F2::operator(); }; int main() { std::variant<std::string, int> var; var = "123"; std::visit( overload( [](int value){std::cout << "int";}, [](std::string&){std::cout << "string";} ), var ); return 0; } |
and with variadic templates:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
template<class ... Fs> struct overload: Fs ... { overload(Fs const& ... fs):Fs{fs}...{} using Fs::operator()...; }; int main() { std::variant<std::string, int> var; var = "123"; auto super_lambda = overload( [](int){}, [](float){}, [](std::string){}, [](auto&){} ); std::visit( super_lambda, var ); return 0; } |
The overload struct is already purposed to C++20 standard.
Here is a paper: p0051r3