These two C++ types have a few things in common: they are both new additions to the C++17 Standard (requiring compiler support, so they are not back-portable), they allow initialization and (re-)assignment from different types (that is, they are in some ways true polymorphic types), and they can both throw exceptions when dereferenced incorrectly.
But they are very different under the hood. This article aims to be one of a planned mini-series covering how these two types are used so that, hopefully, you will know which, if either, to use.
Starting with any
(which needs the <any>
header file), it is possible to create a new variable without an explicit type specifier, such as:
any a1; // no initial value
any a2 = 1.5; // contains a double
any a3{ "Hello, world!"s }; // string literal suffix, not const char*
vector<any> av{}; // vector of uninitialized any-type
a1 = true; // re-assignment
Member functions has_value()
and type()
can be used on an any
object; the first of these returns false
for an uninitialized any
object, otherwise true
, while the second can be used in comparison operations between two any
objects as it returns a std::type_info
object. (This object has a member function name()
which returns a human-readable representation of the type.)
Moving onto variant
(which needs the <variant>
header file), the range of types which it can hold must be specified as template parameters. An uninitialized variant
object takes the type of the first of these, which must again be default-constructible. The code below uses a using
declaration for var
(however, it’s not JavaScript!):
using var = variant<bool,double,string>;
var v1; // no initial value, will be bool
var v2 = 1.5; // contains a double
var v3 { "Hello, world!"s }; // contains a string
vector<var> vv{}; // vector of variants, all bool initially
v1 = true; // re-assignment (to the same type)
If you require a std::variant
to hold only non-default-constructible (user-defined) types, then there is the placeholder std::monostate
that can be used as the first template parameter. The member function index()
can be used on any variant object which returns the number (starting from zero) that the variant object holds, so v3.index()
would here return 2
. The (non-member) function template std::holds_alternative<T>(v)
returns a boolean indicating whether the type T
matches the variant object v
specified within the parentheses.
You may well wonder how these types are implemented as their functionality differs greatly from the strict statically-typed language C++ has always been. Put simply, any
is like a polymorphic abstract base class (ABC), while variant
is like a C-style union
(combined with an enum
for the discriminant field). These implementation details are important when it comes to being able to usefully extract a value from an object of either of these types, which we’ll come to in the next article of this mini-series.