December 25, 2023
+bsl::variant
: An Allocator-Aware Discriminated Union¶
+Summary¶
+The latest release of BDE (4.0) provides the header <bsl_variant.h>
,
+which contains an allocator-aware implementation of the C++17 <variant>
+header with select C++20 enhancements. Some std::variant
features are not
+yet supported by bsl::variant
, however.
The type bsl::variant<T...>
is a discriminated union of the types T...
,
+similar to bdlb::Variant<T...>
. The types T...
are known as
+alternatives. For example, an object of type
+bsl::variant<int, double, const char*>
can hold either an int
, or a
+double
, or a const char*
, but only one of these alternatives at a time.
+The bsl::variant
object also keeps track of which alternative is currently
+active, so, for example, if the bsl::variant
object holds a double
+object, then an attempt to retrieve the stored const char*
object will fail
+by returning a null pointer or throwing an exception.
Differences Between std::variant
and bsl::variant
¶
+bsl::variant
is allocator-aware if at least one alternative is
+allocator-aware. For example, if one of the alternatives is
+bsl::vector<T>
, then the allocator specified at construction of the
+bsl::variant
object will be used by the bsl::vector
to allocate memory
+to hold its buffer of T
’s. Consequently, bsl::variant
differs from
+std::variant
in that:
-
+
There are additional overloads for several constructors taking an allocator +parameter. These constructors are valid even when no alternative is +allocator-aware; however, the allocator parameter will be ignored in that +case.
+There is an additional
get_allocator
method, which participates in +overload resolution only if at least one alternative is allocator-aware.
+If at least one alternative is allocator-aware, the footprint of +
bsl::variant
is larger than that ofstd::variant
because the former +needs to store the allocator that it uses to supply memory.
+
The declarations of the additional overloads are shown below, with some details +elided for readability:
+// CREATORS
+variant(bsl::allocator_arg_t, allocator_type);
+
+variant(bsl::allocator_arg_t, allocator_type, const variant& original);
+
+variant(bsl::allocator_arg_t, allocator_type, variant&& original);
+
+template <class t_TYPE>
+variant(bsl::allocator_arg_t, allocator_type, t_TYPE&& value);
+
+template <class t_TYPE, class... t_ARGS>
+explicit variant(bsl::allocator_arg_t,
+ allocator_type,
+ bsl::in_place_type_t<t_TYPE>,
+ t_ARGS&&... args);
+
+template <class t_TYPE, class t_ELEM_TYPE, class... t_ARGS>
+explicit variant(bsl::allocator_arg_t,
+ allocator_type,
+ bsl::in_place_type_t<t_TYPE>,
+ std::initializer_list<t_ELEM_TYPE> arg,
+ t_ARGS&&... args);
+
+template <size_t t_INDEX, class... t_ARGS>
+explicit variant(bsl::allocator_arg_t,
+ allocator_type,
+ bsl::in_place_index_t<t_INDEX>,
+ t_ARGS&&... args);
+
+template <size_t t_INDEX, class t_ELEM_TYPE, class... t_ARGS>
+explicit variant(bsl::allocator_arg_t,
+ allocator_type,
+ bsl::in_place_index_t<t_INDEX>,
+ std::initializer_list<t_ELEM_TYPE> arg,
+ t_ARGS&&... args);
+
+// ACCESSORS
+get_allocator() const noexcept;
+
Note that the constructors above have the same constraints and mandates as +their standard counterparts.
+Important
+We recommend referring to the cppreference documentation for variant
+in order to understand the constructors of bsl::variant
, because the
+documentation generated by Doxygen can be hard to understand.
The current implementation of bsl::variant
also differs from
+std::variant
in the following ways:
-
+
bsl::visit
can only visit a single variant at a time.
+Most of the methods and free functions of
bsl::variant
are not +constexpr
and do not havenoexcept
specifications.
+bsl::variant
does not have any trivial member functions. In C++17, +std::variant
has a conditionally trivial destructor, and in C++20, the +copy constructor, move constructor, copy assignment operator, and move +assignment operator ofstd::variant
are also conditionally trivial.
+bsl::variant
does not provide the strong exception safety guarantee for +any function call that changes which alternative is currently active within a +bsl::variant
object.
+
Also note that additional limitations apply in C++03 (and some of the above
+constructor signatures are different). See bslstl_variant.h
for details.
Differences Between bdlb::Variant
and bsl::variant
¶
+Although bsl::variant
and bdlb::Variant
are both discriminated union
+types, bsl::variant
is not a drop-in replacement for bdlb::Variant
. A
+quick reference, comparing analogous functions in the bdlb::Variant
and
+bsl::variant
interfaces, is given in the next subsection. However, there
+are also significant behavioral differences, which will be explained in more
+detail. A particularly notable difference is that bdlb::Variant
and
+bsl::variant
deduce the return types of visitors using different rules.
+Therefore, a visitor class that was designed to visit bdlb::Variant
objects
+may need to be modified before it can be used to visit bsl::variant
+objects; some guidance is provided in Migrating Visitors From bdlb::Variant to bsl::variant. In addition,
+bsl::variant
does not have a null state, though it may sometimes fail to
+hold a value (see The “Valueless By Exception” State). If a bsl::variant
needs
+to be able to represent a state that is none of T1
, …, Tn
, then
+bsl::variant<bsl::monostate, T1, ..., Tn>
should be used.
Quick reference¶
+The constructors for bsl::variant
are similar to those of bdlb::Variant
+but use the leading allocator convention (with bsl::allocator_arg_t
) while
+bdlb::Variant
uses the trailing allocator convention. Most other methods
+of bdlb::Variant
have a differently-named counterpart for bsl::variant
:
bdlb::Variant |
+bsl::variant |
+
---|---|
|
+
|
+
|
+
|
+
|
+
|
+
|
+
|
+
|
+
|
+
|
+
|
+
|
+
|
+
|
+
|
+
|
+
|
+
|
+
|
+
|
+
|
+
|
+See below |
+
|
+
|
+
|
+See below |
+
|
+See below |
+
|
+See below |
+
Note that the numbering for alternatives of bsl::variant
starts from 0,
+whereas bdlb::Variant
starts from 1. For example,
+bdlb::Variant<T1, T2>::typeIndex()
returns 1 when the T2
alternative is
+active, and bsl::variant<bsl::monostate, T1, T2>::index()
does likewise.
+But if there is no need to represent a null state, then
+bsl::variant<T1, T2>
may be a suitable replacement. In that case, the
+index
method returns 1 when the T2
alternative is active.
Notable behavioral differences¶
+Some of the bsl::variant
equivalents given in the table in the preceding
+section behave differently from their bdlb::Variant
counterparts. In
+addition, some operations that are spelled the same way for bdlb::Variant
+and bsl::variant
, such as the swap
method, nonetheless have different
+behavior. Here are some notable differences:
-
+
In C++11 and later,
+bsl::variant
has full support for move semantics for +both variants and visitors. In C++03,bsl::variant
still supports moving +from variant alternatives (but not from visitors). For example, when using +bsl::variant
, a single visitor can be applied to both lvalue and rvalue +bsl::variant
expressions, and the visitor will be called with an argument +of the appropriate value category:++struct PushBackVisitor { + bsl::vector<bsl::string>& d_v; + + PushBackVisitor(bsl::vector<bsl::string>& v) : d_v(v) {} + + void operator()(const bsl::string& arg) + { + d_v.push_back(arg); + } + + void operator()(bsl::string&& arg) + { + d_v.push_back(std::move(arg)); + } + + void operator()(int arg) + { + bsl::ostringstream oss(d_v.get_allocator()); + oss << arg; + d_v.push_back(std::move(oss).str()); + } +}; + +template <class t_VARIANT> +void pushBackVariant(bsl::vector<bsl::string>& vector, + t_VARIANT&& variant) + // Append to the specified 'vector' the value held by the specified + // 'variant', which must be of type 'bsl::variant<int, bsl::string>'. +{ + bsl::visit(PushBackVisitor(vector), + std::forward<t_VARIANT>(variant)); +} +
Note that if a
+bdlb::Variant
had been used in the above example, then the +bsl::string&&
overload would never be called; it would be necessary to +write a second visitor taking absl::string&
overload that moves from the +parameter, and take care to use that visitor only on variant objects that no +longer need to retain their value.
+The
+bsl::get
methods, which return a reference to an alternative, fail by +throwing an exception if the desired alternative is not active inside the +bsl::variant
object, whereasbdlb::Variant
does not use exceptions. +Thebsl::get_if
method, which returns a null pointer on failure, can be +used to avoid exceptions:++bdlb::Variant<int, double> bdlbVariant = 1; +bsl::variant<int, double> bslVariant = 1; + +bdlbVariant.the<int>(); // returns a reference to the stored 'int' +bsl::get<int>(bslVariant); // returns a reference to the stored 'int' +bsl::get_if<int>(bslVariant); // returns a pointer to the stored 'int' + +bdlbVariant.the<double>(); // undefined behavior +bsl::get<double>(bslVariant); // throws 'bsl::bad_variant_access' +bsl::get_if<double>(bslVariant); // returns a null pointer +
+
+bsl::variant
does not support a null state that is comparable to +bdlb::Variant
’s default-constructed state; instead,bsl::variant
+always holds a value other than in certain situations where setting a value +fails due to an exception. Default construction ofbsl::variant
+value-initializes the first alternative type.bsl::variant
can be given +a comparable null state by usingbsl::monostate
as the first alternative.++// Create a null 'bdlb::Variant' object. +bdlb::Variant<int, double> bdlbVariant; + +// Create a 'bsl::variant' object holding an 'int' with value 0. +bsl::variant<int, double> bslVariant; + +// Create a 'bsl::variant' object that can hold a 'bsl::monostate' (which +// may be used by the application to represent the null state), an 'int', +// or a 'double'. Because the 'bsl::monostate' alternative is first, the +// default constructor activates that alternative. +bsl::variant<bsl::monostate, int, double> bslVariant2; +
+The
bsl::visit
method deduces its return type automatically, in contrast +tobdlb::Variant::apply
, which returnsvoid
when the visitor does not +have aResultType
member type. In cases where conflicting return types +are deduced for different alternatives or (in C++03) return type deduction +fails, an explicit return type can be passed as a template parameter to +bsl::visit
(in C++03, the methodbsl::visitR
must be used). An +example is provided in Migrating Visitors From bdlb::Variant to bsl::variant.
+In C++11 and later, the constructors and assignment operators of +
+bsl::variant
can accept argument types that have an unambiguous “best +match” with one of the variant types, whereasbdlb::Variant
requires a +type that matches exactly with one of the alternative types. (The C++03 +implementation ofbsl::variant
also has this restriction.)++// ERROR: initializer doesn't match any alternative type +bdlb::Variant<int, bsl::string> bdlbVariant = "a C string"; + +// OK: activates the 'bsl::string' alternative in C++11 and later +bsl::variant<int, bsl::string> bslVariant = "a C string"; +
+
+bsl::variant
follows the guidance in +P0178R0: a precondition of theswap
method +is that the two objects being swapped have equal allocators. If this +precondition is not met, the free functionswap
must be used instead. In +contrast, forbdlb::Variant
, member and non-memberswap
both have +wide contracts.++bslma::TestAllocator ta1("ta1"); +bslma::TestAllocator ta2("ta2"); + +const bsl::string s1 = "this is a string"; +const bsl::string s2 = "this is another string"; + +bdlb::Variant<int, bsl::string> bdlbVariant1(s1, &ta1); +bdlb::Variant<int, bsl::string> bdlbVariant2(s2, &ta2); + +bsl::variant<int, bsl::string> bslVariant1(bsl::allocator_arg, &ta1, s1); +bsl::variant<int, bsl::string> bslVariant2(bsl::allocator_arg, &ta2, s2); + +swap(bdlbVariant1, bdlbVariant2); +assert(bdlbVariant1.the<bsl::string>() == s2); +assert(bdlbVariant2.the<bsl::string>() == s1); +bdlbVariant1.swap(bdlbVariant2); +assert(bdlbVariant1.the<bsl::string>() == s1); +assert(bdlbVariant2.the<bsl::string>() == s2); + +swap(bslVariant1, bslVariant2); +assert(bsl::get<bsl::string>(bslVariant1) == s2); +assert(bsl::get<bsl::string>(bslVariant2) == s1); +// undefined behavior, 'bslVariant1' and 'bslVariant2' have different +// allocators! +bslVariant1.swap(bslVariant2); +
+bsl::variant
supports relational and equality comparison operators. Two +objects of typebsl::variant<T...>
can be compared with a particular +operator if and only if all alternatives support that operator (that is, all +ofT...
).
+
Comparison of visitation APIs¶
+bdlb::Variant
has a complicated set of visitation overloads due to the fact
+that it supports a null state. In contrast, bsl::variant
does not have a
+null state; therefore, only two visitation facilities are provided:
+bsl::visit(visitor, variant)
, which deduces the return type of visitor
,
+and bsl::visit<R>(visitor, variant)
, which returns the explicitly specified
+type R
. Note that bsl::visitR<R>
is identical to bsl::visit<R>
but
+is provided for C++03 compatibility.
If a bdlb::Variant<T1, T2>
object that is never null is
+replaced by a bsl::variant<T1, T2>
object in a new version of client code,
+then bsl::visit
should replace both bdlb::Variant::apply
and
+bdlb::Variant::applyRaw
, and bsl::visit<R>
should replace both
+bdlb::Variant::apply<R>
and bdlb::Variant::applyRaw<R>
:
struct Visitor {
+ double operator()(int x) const
+ {
+ return x + 1;
+ }
+ double operator()(double x) const
+ {
+ return x + 1;
+ }
+ typedef double ResultType; // needed by 'bdlb::Variant::apply'
+};
+
+bdlb::Variant<int, double> bdlbVariant = 1;
+bsl::variant<int, double> bslVariant = 1;
+
+bdlbVariant.applyRaw(Visitor()); // returns 2.0
+bsl::visit(Visitor(), bslVariant); // returns 2.0
+
On the other hand, if a bdlb::Variant<T1, T2>
object is replaced by a
+bsl::variant<bsl::monostate, T1, T2>
object, where the first alternative is
+used to represent the null state, then the visitor passed to bsl::visit
or
+bsl::visit<R>
must always be able to accept an argument of type
+bsl::monostate
, since the latter is one of the alternatives.
+Furthermore, bsl::variant
does not provide any direct equivalent to the apply
+overloads that accept a default value. Instead, the visitor must be configured
+with the desired default value before bsl::visit
is called:
template <class t_TYPE>
+struct MyVisitorWithDefault {
+ t_TYPE d_defaultValue;
+
+ MyVisitorWithDefault(t_TYPE defaultValue)
+ : d_defaultValue(defaultValue) {}
+
+ void operator()(int x) const
+ {
+ bsl::cout << "Visited int value " << x << '\n';
+ }
+
+ void operator()(double x) const
+ {
+ bsl::cout << "Visited double value " << x << '\n';
+ }
+
+ void operator()(bsl::monostate) const
+ {
+ (*this)(d_defaultValue);
+ }
+};
+
+void visitMyVariant(const bsl::Variant<bsl::monostate, int, double>& v)
+{
+ // print 42 if 'v' is unset
+ bsl::visit(MyVisitorWithDefault<int>(42), v);
+ // print 3.14 if 'v' is unset
+ bsl::visit(MyVisitorWithDefault<double>(3.14), v);
+}
+
Note that in the example above, the explicit template argument specification at
+the call sites of bsl::visit
can be omitted in C++17 and later.
Because bsl::variant
does not support a null state, there is no need for a
+visitor to handle the null state. However, if a user chooses to introduce an
+alternative of type bsl::monostate
into their bsl::variant
type in
+order to represent the absence of any other alternative, then they must ensure
+that every visitor for that bsl::variant
type has an overload that can
+accept bsl::monostate
, even if they know that this overload will never be
+called. There is no analogue to the bdlb::Variant::applyRaw
visitation
+method (which does not require the null state of bdlb::Variant
to be
+handled).
Migrating Visitors From bdlb::Variant
to bsl::variant
¶
+bdlb::Variant::apply
and bsl::visit
both support explicit return type
+specification. In cases where the return type is not explicitly specified,
+however, bdlb::Variant::apply
and bsl::visit
determine their
+respective return types using different rules:
-
+
bdlb::Variant::apply
looks within the visitor’s type for a member type +calledResultType
. If that type is valid,apply
’s return type is +that type. Otherwise,apply
returnsvoid
.
+bsl::visit
attempts to deduce its return type based on the actual type +that invocation of the visitor will return for each alternative. If these +types do not agree with each other, the call is ill-formed. In C++03, the +return type deduction facility is imperfect due to language limitations. In +cases where the return type cannot be determined in C++03, the call is +ill-formed.
+
As a consequence of this difference, some changes might be required to a
+visitor that is designed for use with bdlb::Variant::apply
before that
+visitor can be used with bsl::variant
.
If a visitor does not have a ResultType
typedef:
-
+
If invoking the visitor yields the same type for all alternatives, then the +visitor can simply be used as-is with
bsl::visit
; the return type of +bsl::visit
is that type.
+If invoking the visitor does not yield the same type for all alternatives, or +if return type deduction fails in C++03, then
bsl::visit<R>
must be used +(orbsl::visitR<R>
in C++03), which has return typeR
. Note that +R
may bevoid
.
+
bdlb::Variant<int, double> bdlbVariant = 1;
+bsl::variant<int, double> bslVariant = 1;
+
+struct Visitor1 {
+ char operator()(int) const;
+ char operator()(double) const;
+};
+
+bdlbVariant.applyRaw(Visitor1()); // returns 'void'
+bsl::visit(Visitor1(), bslVariant); // returns 'char'
+
+struct Visitor2 {
+ char operator()(int) const;
+ short operator()(double) const;
+};
+
+bdlbVariant.applyRaw(Visitor2()); // returns 'void'
+bsl::visit(Visitor2(), bslVariant); // ERROR: conflicting return types
+
+bsl::visit<short>(Visitor2(), bslVariant); // returns 'short'
+
If a visitor has a ResultType
typedef, bsl::visit
will ignore
+ResultType
other than in some cases in C++03 where the return type cannot
+otherwise be deduced. If invoking the visitor would return ResultType
for
+all alternatives, then bsl::visit
will automatically deduce the same return
+type as bdlb::Variant::apply
. If the preceding condition is not met, then
+the presence of ResultType
can cause problems when using bsl::variant
.
+To avoid such problems, bsl::visit<R>
(or bsl::visitR<R>
in C++03)
+should always be used instead of the deduced bsl::visit
. In addition, in
+order to avoid bugs due to accidental use of the deduced bsl::visit
,
+consider taking one of the following steps, if possible:
-
+
Change the definition of the visitor so that the invocation will return +
ResultType
for every alternative.
+If the visitor will not be used with
bdlb::Variant
anymore, then remove +ResultType
, which will ensure that accidental use of the deduced +bsl::visit
will always give a compilation error due to conflicting return +types.
+If the visitor will still be used with
bdlb::Variant
, change the existing +call sites so that they specify an explicit return type, then remove +ResultType
.
+
The “Valueless By Exception” State¶
+Although bsl::variant
does not support a null state, it can sometimes be
+left in a state where it does not hold any value. This state can be produced
+only by operations that destroy the currently active alternative and then
+construct a new one: if the construction of the new alternative exits via an
+exception, then there will be no object held by the variant. For this reason,
+this state is called “valueless by exception”.
The valueless by exception state is not intended to be used as a null state.
+Although certain operations can produce a valueless by exception state, it is
+unspecified whether they actually do; there is no supported method for creating
+this state deliberately. The valueless_by_exception
accessor can be used
+to check whether a bsl::variant
is valueless by exception.
An exception of type bsl::bad_variant_access
is thrown if a variant is
+visited while it is valueless by exception.