Last time we looked at how to assign a value to an object of type any
or variant
. In this article we’re going to look at how to extract this value in order to output it or assign it to another variable (of known type). We’ll also consider how to use any
and variant
with streams.
Considering any
first, it is important to recognize that, true to its name, it can wrap any type. Code that dereferences an any
object must therefore be very careful in order to remain legal, type-safe, C++. The (only) way to extract the value from an any
object is to use a cast
, called std::any_cast
. This has the same syntax as the familiar static_cast
and dynamic_cast
, and has slightly more in common with the second of these as it is run-time checked. If the object cannot be cast exactly to the target type (enclosed in triangular brackets), an exception of type std::bad_any_cast
is thrown.
Of course, throwing and catching exceptions is a performance hit, so you will probably want to check the return value of the type()
member against a typeid()
object (from header <typeinfo>
):
any a = 1;
if (a.type() == typeid(int)) {
cout << "a holds an int\n";
int i = any_cast<int>(a); // safe, won't throw
cout << "a = " << i << '\n';
}
else {
cout << "a does not hold an int\n";
}
Moving onto variant
, the way to extract the value is similar as for std::tuple
, using std::get
with either an index or type:
variant<monostate,int,double> v1 = 1, v2 = 2.3;
if (v1.index() == 1) {
cout << "v1 holds an int\n";
int i = get<1>(v1); // safe, won't throw
cout << "v1 = " << i << '\n';
}
if (holds_alternative<double>(v2)) {
cout << "v2 holds a double\n";
double d = get<double>(v2); // safe, won't throw
cout << "v2 = " << d << '\n';
}
A bad use of get
will result in an exception of type std::bad_variant_access
being thrown, however checking with an if-statement, or switching on the member function index()
, ensures this will not happen. (Note that indexing starts from zero, which in this case is the index of std::monostate
which would indicate a null-value; void
is not a valid parameter type for variant
.)
It is possible to use (checked or unchecked) any_cast
and get
with output streams directly, thus not using an intermediate named variable as shows here. However an exception thrown by an entity put to a stream may cause problems with untidy or truncated output and even stream termination, so this probably should be avoided. (The type of the entity put to a stream must always be known at compile-time, so little is gained by avoiding the use of an intermediate variable.) Another possible scenario is overloading operator <<
for any
or a specific variant
type, we’ll get to this in the next article.
Reading from streams must use an intermediate variable, or some method of extracting the any
or variant
from the input string. The following code indicates an outline of such a method, real code would probably check the input string against multiple regexes:
any a;
variant<int,double,string> v;
string s;
getline(cin, s);
if (isalpha(s.front())) {
a = s; v = s;
} else if (s.find('.')) {
a = stod(s); v = stod(s);
} else {
a = stoi(s); v = stoi(s);
}
The most important thing to note is that the string conversion functions stoi()
and stod()
can be used to assign to both the any
and the variant
as the type is implied from their return types.
Finally a quick mention of the function template std::make_any
which has similar syntax to std::make_unique
and std::make_shared
; that is, it forwards on the supplied parameters to the constructor of the type specified in angle-brackets. Next in this mini-series we’ll compare these two types again as we look to create a JSON type that can be output to streams.
Sorry, I don’t think that’s a prototypical advice of how to use either any or variant.
It is obvious, that the most prominent advantage of variant against any is that the first is compile-time, the second is run-time.
Stop giving such bad advice! C++ is not Java!
LikeLike
I’m afraid I don’t agree. Both any and variant are run-time checked:
auto s = any_cast<string>(my_any); // can throw std::bad_any_cast
auto s = get<string>(my_variant); // can throw std::bad_variant_access
As to giving bad advice, I have tried to explain that variant is basically a struct of a union and an enum, while any holds something like a polymorphic base class pointer. C++ is not Java, so for effective use a basic understanding of implementation is useful.
LikeLike