-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvariant-any-cpp17.cpp
More file actions
149 lines (108 loc) · 4.06 KB
/
variant-any-cpp17.cpp
File metadata and controls
149 lines (108 loc) · 4.06 KB
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#pragma clang diagnostic ignored "-Wunused-variable"
#pragma clang diagnostic ignored "-Wunused-value"
#define CATCH_CONFIG_MAIN // Tells Catch2 to provide a main()
#include "../catch/catch_amalgamated.hpp"
#include "utils.h"
using namespace std;
// std::variant
// - variant == Rust enum
// - a type-safe union — holds exactly one of a fixed set of types.
// - enum vs variant: C++ enum is just named integers; `std::variant` is a
// "type-safe" tagged union that can hold different types with actual values
// closer to Rust enums.
//
// Key notes:
// - Fixed set of types known at compile time — type-safe, no heap allocation
// - Always holds exactly one type — never empty (unless std::monostate added)
// - get<T> throws; get_if<T> returns nullptr — prefer get_if for safety
// - std::visit forces you to handle all types — like Rust match
// - Use for: error handling (variant<Result, Error>), state machines, AST nodes
// std::any
// - holds a value of any type with type erasure.
// - any → Rust Box<dyn Any> (from std::any::Any).
//
// Key notes:
// - Any type at runtime — uses heap allocation for large types
// - No compile-time type info — checked at runtime only
// - Slower and less safe than variant — only use when types truly unknown at
// compile time
// - Use for: plugin systems, config values, type-erased containers
#include <variant>
//// 1. simple usage
TEST_CASE("var-any-1") {
// define
std::variant<int, double, std::string> var;
var = 42; // holds int
var = 3.14; // now holds double
var = "hello"; // now holds string
// check which type is active
std::holds_alternative<int>(var); // false
std::holds_alternative<std::string>(var); // true
// access — throws if wrong type
std::get<std::string>(var); // "hello" ✓
// std::get<int>(var); // ❌ throws std::bad_variant_access
// safe access — returns nullptr if wrong type
if (auto* p = std::get_if<std::string>(&var))
std::cout << *p << "\n";
}
//// 2. Return Result/Error from fns like in Rust
struct Result { int x; };
enum Error { SEG_FAULT, INVALID_ID };
std::variant<Result, Error> foo(int x) {
if (x == 42) return Result {99};
return Error::INVALID_ID;
}
// helper
template<typename... Ts>
struct overloaded : Ts... { using Ts::operator()...; };
TEST_CASE("var-any-2") {
auto var = foo(422);
// exhaustiveness match
std::visit(overloaded {
[](const Result& res) { std::cout << res.x << "\n" ; },
[](const Error& err) { std::cout << err << "\n"; }
}, var);
}
//// 3. IpAddr variant example
/* Rust
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
*/
struct IPV4 { uint16_t x, y, z, t; };
struct IPV6 { string addr; };
using IpAddr = std::variant<IPV4, IPV6>;
TEST_CASE("var-any-3") {
IpAddr ip4 = IPV4 { 192,1,1,1 };
IpAddr ip6 = IPV6 { "some-addr" };
// !! exhaustiveness match
// e.g. add IPV8 -> IpAddr = std::variant<IPV4, IPV6, IPV8>;
// and visit will error at compile time
IpAddr res = std::visit(overloaded {
[](const IPV4& addr) -> IpAddr { return addr; },
[](const IPV6& addr) -> IpAddr { return addr; }
}, ip4);
// safe access — returns nullptr if wrong type
if (IPV4* val = std::get_if<IPV4>(&res))
REQUIRE(val->x == 192);
}
//// 4. std::any
#include <any>
TEST_CASE("var-any-4") {
std::any my_any = 42;
my_any = 3.14;
my_any = std::vector<int>{1,2,3}; // any type ✓
my_any = std::string("hello");
// check type
my_any.type() == typeid(std::string); // true
my_any.has_value(); // true
// access — throws std::bad_any_cast if wrong type
auto str = std::any_cast<std::string>(my_any); // ✓
// auto i = std::any_cast<int>(my_any); // ❌ throws
// safe cast — returns nullptr if wrong type
if (auto* val = std::any_cast<std::string>(&my_any))
REQUIRE(*val == "hello");
my_any.reset(); // clear — now empty
REQUIRE(my_any.has_value() == false);
}