Overview
Description
This library implements a type-safe discriminated/tagged union type,
variant<T…>
, that is API-compatible with the C++17 Standard’s
std::variant<T…>
.
A variant<T1, T2, …, Tn>
variable can hold a value of any of the
types T1
, T2
, …, Tn
. For example,
variant<int64_t, double, std::string>
can hold an int64_t
value, a
double
value, or a string
value.
Such a type is sometimes called a "tagged union", because it’s roughly equivalent to
struct V
{
enum tag { tag_int64_t, tag_double, tag_string };
tag tag_;
union
{
int64_t i_;
double d_;
std::string s_;
};
};
Usage Examples
Variants can be used to represent dynamically-typed values. A configuration file of the form
server.host=test.example.com
server.port=9174
cache.max_load=0.7
can be represented as std::map<std::string, variant<int64_t, double,
std::string>>
.
Variants can also represent polymorphism. To take a classic example, a polymorphic collection of shapes:
#define _USE_MATH_DEFINES
#include <iostream>
#include <vector>
#include <memory>
#include <cmath>
class Shape
{
public:
virtual ~Shape() = default;
virtual double area() const = 0;
};
class Rectangle: public Shape
{
private:
double width_, height_;
public:
Rectangle( double width, double height ):
width_( width ), height_( height ) {}
virtual double area() const { return width_ * height_; }
};
class Circle: public Shape
{
private:
double radius_;
public:
explicit Circle( double radius ): radius_( radius ) {}
virtual double area() const { return M_PI * radius_ * radius_; }
};
double total_area( std::vector<std::unique_ptr<Shape>> const & v )
{
double s = 0.0;
for( auto const& p: v )
{
s += p->area();
}
return s;
}
int main()
{
std::vector<std::unique_ptr<Shape>> v;
v.push_back( std::unique_ptr<Shape>( new Circle( 1.0 ) ) );
v.push_back( std::unique_ptr<Shape>( new Rectangle( 2.0, 3.0 ) ) );
std::cout << "Total area: " << total_area( v ) << std::endl;
}
can instead be represented as a collection of variant<Rectangle, Circle>
values. This requires the possible Shape
types be known in advance, as is
often the case. In return, we no longer need virtual functions, or to allocate
the values on the heap with new Rectangle
and new Circle
:
#define _USE_MATH_DEFINES
#include <iostream>
#include <vector>
#include <cmath>
#include <boost/variant2/variant.hpp>
using namespace boost::variant2;
struct Rectangle
{
double width_, height_;
double area() const { return width_ * height_; }
};
struct Circle
{
double radius_;
double area() const { return M_PI * radius_ * radius_; }
};
double total_area( std::vector<variant<Rectangle, Circle>> const & v )
{
double s = 0.0;
for( auto const& x: v )
{
s += visit( []( auto const& y ){ return y.area(); }, x );
}
return s;
}
int main()
{
std::vector<variant<Rectangle, Circle>> v;
v.push_back( Circle{ 1.0 } );
v.push_back( Rectangle{ 2.0, 3.0 } );
std::cout << "Total area: " << total_area( v ) << std::endl;
}
Construction and Assignment
If we look at the
v.push_back( Circle{ 1.0 } );
line, we can deduce that variant<Rectangle, Circle>
can be (implicitly)
constructed from Circle
(and Rectangle
), and indeed it can. It can also
be assigned a Circle
or a Rectangle
:
variant<Rectangle, Circle> v = Circle{ 1.0 }; // v holds Circle
v = Rectangle{ 2.0, 3.0 }; // v now holds Rectangle
If we try to construct variant<int, float>
from something that is neither
int
nor float
, say, (short)1
, the behavior is "as if" the variant
has
declared two constructors,
variant::variant(int x);
variant::variant(float x);
and the standard overload resolution rules are used to pick the one that will
be used. So variant<int, float>((short)1)
will hold an int
.
Inspecting the Value
Putting values into a variant
is easy, but taking them out is necessarily a
bit more convoluted. It’s not possible for variant<int, float>
to define a
member function get() const
, because such a function will need its return
type fixed at compile time, and whether the correct return type is int
or
float
will only become known at run time.
There are a few ways around that. First, there is the accessor member function
std::size_t variant::index() const noexcept;
that returns the zero-based index of the current type. For variant<int,
float>
, it will return 0
for int
and 1
for float
.
Once we have the index, we can use the free function get<N>
to obtain the
value. Since we’re passing the type index to get
, it knows what to return.
get<0>(v)
will return int
, and get<1>(v)
will return float
:
void f( variant<int, float> const& v )
{
switch( v.index() )
{
case 0:
// use get<0>(v)
break;
case 1:
// use get<1>(v)
break;
default:
assert(false); // never happens
}
}
If we call get<0>(v)
, and v.index()
is not currently 0
, an exception
(of type bad_variant_access
) will be thrown.
An alternative approach is to use get<int>(v)
or get<float>(v)
. This
works similarly.
Another alternative that avoids the possibility of bad_variant_access
is
to use get_if
. Instead of a reference to the contained value, it returns
a pointer to it, returning nullptr
to indicate type mismatch. get_if
takes a pointer to the variant
, so in our example we’ll use something along
the following lines:
void f( variant<int, float> const& v )
{
if( int const * p = get_if<int>(&v) )
{
// use *p
}
else if( float const * p = get_if<float>(&v) )
{
// use *p
}
else
{
assert(false); // never happens
}
}
Visitation
Last but not least, there’s visit
. visit(f, v)
calls the a function object
f
with the value contained in the variant
v
and returns the result. When
v
is variant<int, float>
, it will call f
with either an int
or a
float
. The function object must be prepared to accept both.
In practice, this can be achieved by having the function take a type that can
be passed either int
or float
, such as double
:
double f( double x ) { return x; }
double g( variant<int, float> const& v )
{
return visit( f, v );
}
By using a function object with an overloaded operator()
:
struct F
{
void operator()(int x) const { /* use x */ }
void operator()(float x) const { /* use x */ }
};
void g( variant<int, float> const& v )
{
visit( F(), v );
}
Or by using a polymorphic lambda, as we did in our Circle
/Rectangle
example:
void g( variant<int, float> const& v )
{
visit( [&]( auto const& x ){ std::cout << x << std::endl; }, v );
}
visit
can also take more than one variant
. visit(f, v1, v2)
calls
f(x1, x2)
, where x1
is the value contained in v1
and x2
is the value
in v2
.
Default Construction
The default constructor of variant
value-initializes the first type in
the list. variant<int, float>{}
holds 0
(of type int
), and
variant<float, int>{}
holds 0.0f
.
This is usually the desired behavior. However, in cases such as
variant<std::mutex, std::recursive_mutex>
, one might legitimately wish to
avoid constructing a std::mutex
by default. A provided type, monostate
,
can be used as the first type in those scenarios. variant<monostate,
std::mutex, std::recursive_mutex>
will default-construct a monostate
,
which is basically a no-op, as monostate
is effectively an empty struct
.
Revision History
Changes in 1.71.0
After the Boost formal review, the implementation has been
changed to provide the strong exception safety guarantee,
instead of basic. expected
has been removed.
Design
Features
This variant
implementation has two distinguishing features:
-
It’s never "valueless", that is,
variant<T1, T2, …, Tn>
has an invariant that it always contains a valid value of one of the typesT1
,T2
, …,Tn
. -
It provides the strong exception safety guarantee on assignment and
emplace
.
This is achieved with the use of double storage, unless all of the contained types have a non-throwing move constructor.
Rationale
Never Valueless
It makes intuitive sense that variant<X, Y, Z>
can hold only values
of type X
, type Y
, or type Z
, and nothing else.
If we think of variant
as an extension of union
, since a union
has a state called "no active member", an argument can be made that a
variant<X, Y, Z>
should also have such an additional state, holding
none of X
, Y
, Z
.
This however makes variant
less convenient in practice and less useful
as a building block. If we really need a variable that only holds X
,
Y
, or Z
, the additional empty state creates complications that need
to be worked around. And in the case where we do need this additional
empty state, we can just use variant<empty, X, Y, Z>
, with a suitable
struct empty {};
.
From a pure design perspective, the case for no additional empty state is solid. Implementation considerations, however, argue otherwise.
When we replace the current value of the variant
(of, say, type X
) with
another (of type Y
), since the new value needs to occupy the same storage
as the old one, we need to destroy the old X
first, then construct a new
Y
in its place. But since this is C++, the construction can fail with an
exception. At this point the variant
is in the "has no active member"
state that we’ve agreed it cannot be in.
This is a legitimate problem, and it is this problem that makes having
an empty/valueless state so appealing. We just leave the variant
empty on
exception and we’re done.
As explained, though, this is undesirable from a design perspective as it makes the component less useful and less elegant.
There are several ways around the issue. The most straightforward one is to
just disallow types whose construction can throw. Since we can always create
a temporary value first, then use the move constructor to initialize the one
in the variant
, it’s enough to require a nonthrowing move constructor,
rather than all constructors to be nonthrowing.
Unfortunately, under at least one popular standard library implementation,
node based containers such as std::list
and std::map
have a potentially
throwing move constructor. Disallowing variant<X, std::map<Y, Z>>
is hardly
practical, so the exceptional case cannot be avoided.
On exception, we could also construct some other value, leaving the variant
valid; but in the general case, that construction can also throw. If one of
the types has a nonthrowing default constructor, we can use it; but if not,
we can’t.
The approach Boost.Variant takes here is to allocate a temporary copy of
the value on the heap. On exception, a pointer to that temporary copy can be
stored into the variant
. Pointer operations don’t throw.
Another option is to use double buffering. If our variant
occupies twice
the storage, we can construct the new value in the unused half, then, once
the construction succeeds, destroy the old value in the other half.
When std::variant
was standardized, none of those approaches was deemed
palatable, as all of them either introduce overhead or are too restrictive
with respect to the types a variant
can contain. So as a compromise,
std::variant
took a way that can (noncharitably) be described as "having
your cake and eating it too."
Since the described exceptional situation is relatively rare, std::variant
has a special case, called "valueless", into which it goes on exception,
but the interface acknowledges its existence as little as possible, allowing
users to pretend that it doesn’t exist.
This is, arguably, not that bad from a practical point of view, but it leaves many of us wanting. Rare states that "never" occur are undertested and when that "never" actually happens, it’s usually in the most inconvenient of times.
This implementation does not follow std::variant
; it statically guarantees
that variant
is never in a valueless state. The function
valueless_by_exception
is provided for compatibility, but it always returns
false
.
Instead, if the contained types are such that it’s not possible to avoid an exceptional situation when changing the contained value, double storage is used.
Strong Exception Safety
The initial submission only provided the basic exception safety guarantee.
If an attempt to change the contained value (via assignment or emplace
)
failed with an exception, and a type with a nonthrowing default constructor
existed among the alternatives, a value of that type was created into the
variant
. The upside of this decision was that double storage was needed
less frequently.
The reviewers were fairly united in hating it. Constructing a random type
was deemed too unpredictable and not complying with the spirit of the
basic guarantee. The default constructor of the chosen type, even if
nonthrowing, may still have undesirable side effects. Or, if not that, a
value of that type may have special significance for the surrounding code.
Therefore, some argued, the variant
should either remain with its
old value, or transition into the new one, without synthesizing other
states.
At the other side of the spectrum, there were those who considered double storage unacceptable. But they considered it unacceptable in principle, regardless of the frequency with which it was used.
As a result, providing the strong exception safety guarantee on assignment
and emplace
was declared an acceptance condition.
In retrospect, this was the right decision. The reason the strong guarantee
is generally not provided is because it doesn’t compose. When X
and Y
provide the basic guarantee on assignment, so does struct { X x; Y y; };
.
Similarly, when X
and Y
have nonthrowing assignments, so does the
struct
. But this doesn’t hold for the strong guarantee.
The usual practice is to provide the basic guarantee on assignment and
let the user synthesize a "strong" assignment out of either a nonthrowing
swap
or a nonthrowing move assignment. That is, given x1
and x2
of
type X
, instead of the "basic" x1 = x2;
, use either X(x2).swap(x1);
or x1 = X(x2);
.
Nearly all types provide a nonthrowing swap
or a nonthrowing move
assignment, so this works well. Nearly all, except variant
, which in the
general case has neither a nonthrowing swap
nor a nonthrowing move
assignment. If variant
does not provide the strong guarantee itself, it’s
impossible for the user to synthesize it.
So it should, and so it does.
Differences with std::variant
The main differences between this implementation and std::variant
are:
-
No valueless-by-exception state:
valueless_by_exception()
always returnsfalse
. -
Strong exception safety guarantee on assignment and
emplace
. -
emplace
first constructs the new value and then destroys the old one; in the single storage case, this translates to constructing a temporary and then moving it into place. -
A converting constructor from, e.g.
variant<int, float>
tovariant<float, double, int>
is provided as an extension. -
The reverse operation, going from
variant<float, double, int>
tovariant<int, float>
is provided as the member functionsubset<U…>
. (This operation can throw if the current state of the variant cannot be represented.) -
variant<T…>
is not (yet) trivial when all contained types are trivial, as mandated by C++17. -
The C++20 additions and changes to
std::variant
have not yet been implemented.
Differences with Boost.Variant
This library is API compatible with std::variant
. As such, its interface
is different from Boost.Variant’s. For example, visitation is performed via
visit
instead of apply_visitor
.
Recursive variants are not supported.
Double storage is used instead of temporary heap backup. This variant
is
always "stack-based", it never allocates, and never throws bad_alloc
on
its own.
Implementation
Reference
<boost/variant2/variant.hpp>
Synopsis
namespace boost {
namespace variant2 {
// in_place_type
template<class T> struct in_place_type_t {};
template<class T> constexpr in_place_type_t<T> in_place_type{};
// in_place_index
template<std::size_t I> struct in_place_index_t {};
template<std::size_t I> constexpr in_place_index_t<I> in_place_index{};
// variant
template<class... T> class variant;
// variant_size
template<class T> struct variant_size {};
template<class T> struct variant_size<T const>: variant_size<T> {};
template<class T> struct variant_size<T volatile>: variant_size<T> {};
template<class T> struct variant_size<T const volatile>: variant_size<T> {};
template<class T> struct variant_size<T&>: variant_size<T> {}; // extension
template<class T> struct variant_size<T&&>: variant_size<T> {}; // extension
template<class T>
inline constexpr size_t variant_size_v = variant_size<T>::value;
template<class... T>
struct variant_size<variant<T...>>:
std::integral_constant<std::size_t, sizeof...(T)> {};
// variant_alternative
template<size_t I, class T> struct variant_alternative {};
template<size_t I, class T> struct variant_alternative<I, T const>;
template<size_t I, class T> struct variant_alternative<I, T volatile>;
template<size_t I, class T> struct variant_alternative<I, T const volatile>;
template<size_t I, class T> struct variant_alternative<I, T&>; // extension
template<size_t I, class T> struct variant_alternative<I, T&&>; // extension
template<size_t I, class T>
using variant_alternative_t = typename variant_alternative<I, T>::type;
template<size_t I, class... T>
struct variant_alternative<I, variant<T...>>;
// variant_npos
constexpr std::size_t variant_npos = -1;
// holds_alternative
template<class U, class... T>
constexpr bool holds_alternative(const variant<T...>& v) noexcept;
// get
template<size_t I, class... T>
constexpr variant_alternative_t<I, variant<T...>>&
get(variant<T...>& v);
template<size_t I, class... T>
constexpr variant_alternative_t<I, variant<T...>>&&
get(variant<T...>&& v);
template<size_t I, class... T>
constexpr const variant_alternative_t<I, variant<T...>>&
get(const variant<T...>& v);
template<size_t I, class... T>
constexpr const variant_alternative_t<I, variant<T...>>&&
get(const variant<T...>&& v);
template<class U, class... T>
constexpr U& get(variant<T...>& v);
template<class U, class... T>
constexpr U&& get(variant<T...>&& v);
template<class U, class... T>
constexpr const U& get(const variant<T...>& v);
template<class U, class... T>
constexpr const U&& get(const variant<T...>&& v);
// get_if
template<size_t I, class... T>
constexpr add_pointer_t<variant_alternative_t<I, variant<T...>>>
get_if(variant<T...>* v) noexcept;
template<size_t I, class... T>
constexpr add_pointer_t<const variant_alternative_t<I, variant<T...>>>
get_if(const variant<T...>* v) noexcept;
template<class U, class... T>
constexpr add_pointer_t<U>
get_if(variant<T...>* v) noexcept;
template<class U, class... T>
constexpr add_pointer_t<const U>
get_if(const variant<T...>* v) noexcept;
// relational operators
template<class... T>
constexpr bool operator==(const variant<T...>& v, const variant<T...>& w);
template<class... T>
constexpr bool operator!=(const variant<T...>& v, const variant<T...>& w);
template<class... T>
constexpr bool operator<(const variant<T...>& v, const variant<T...>& w);
template<class... T>
constexpr bool operator>(const variant<T...>& v, const variant<T...>& w);
template<class... T>
constexpr bool operator<=(const variant<T...>& v, const variant<T...>& w);
template<class... T>
constexpr bool operator>=(const variant<T...>& v, const variant<T...>& w);
// visit
template<class F, class... V>
constexpr /*see below*/ visit(F&& f, V&&... v);
// monostate
struct monostate {};
constexpr bool operator==(monostate, monostate) noexcept { return true; }
constexpr bool operator!=(monostate, monostate) noexcept { return false; }
constexpr bool operator<(monostate, monostate) noexcept { return false; }
constexpr bool operator>(monostate, monostate) noexcept { return false; }
constexpr bool operator<=(monostate, monostate) noexcept { return true; }
constexpr bool operator>=(monostate, monostate) noexcept { return true; }
// swap
template<class... T>
void swap(variant<T...>& v, variant<T...>& w) noexcept( /*see below*/ );
// bad_variant_access
class bad_variant_access;
} // namespace variant2
} // namespace boost
variant
namespace boost {
namespace variant2 {
template<class... T> class variant
{
public:
// constructors
constexpr variant() noexcept( /*see below*/ );
constexpr variant( variant const & r ) noexcept( /*see below*/ );
constexpr variant( variant&& r ) noexcept( /*see below*/ );
template<class U>
constexpr variant( U&& u ) noexcept( /*see below*/ );
template<class U, class... A>
constexpr explicit variant( in_place_type_t<U>, A&&... a );
template<class U, class V, class... A>
constexpr explicit variant( in_place_type_t<U>,
std::initializer_list<V> il, A&&... a );
template<size_t I, class... A>
constexpr explicit variant( in_place_index_t<I>, A&&... a );
template<size_t I, class V, class... A>
constexpr explicit variant( in_place_index_t<I>,
std::initializer_list<V> il, A&&... a );
// destructor
~variant();
// assignment
constexpr variant& operator=( variant const & r ) noexcept( /*see below*/ );
constexpr variant& operator=( variant&& r ) noexcept( /*see below*/ );
template<class U> constexpr variant& operator=( U&& u ) noexcept( /*see below*/ );
// modifiers
template<class U, class... A>
constexpr U& emplace( A&&... a );
template<class U, class V, class... A>
constexpr U& emplace( std::initializer_list<V> il, A&&... a );
template<size_t I, class... A>
constexpr variant_alternative_t<I, variant<T...>>&
emplace( A&&... a );
template<size_t I, class V, class... A>
constexpr variant_alternative_t<I, variant<T...>>&
emplace( std::initializer_list<V> il, A&&... a );
// value status
constexpr bool valueless_by_exception() const noexcept;
constexpr size_t index() const noexcept;
// swap
void swap( variant& r ) noexcept( /*see below*/ );
// converting constructors (extension)
template<class... U> variant( variant<U...> const& r )
noexcept( /*see below*/ );
template<class... U> variant( variant<U...>&& r )
noexcept( /*see below*/ );
// subset (extension)
template<class... U> constexpr variant<U...> subset() & ;
template<class... U> constexpr variant<U...> subset() && ;
template<class... U> constexpr variant<U...> subset() const& ;
template<class... U> constexpr variant<U...> subset() const&& ;
};
} // namespace variant2
} // namespace boost
In the descriptions that follow, let i
be in the range [0, sizeof…(T))
,
and Ti
be the i
-th type in T…
.
Constructors
constexpr variant() noexcept( std::is_nothrow_default_constructible_v<T0> );
-
- Effects:
-
Constructs a
variant
holding a value-initialized value of typeT0
. - Ensures:
-
index() == 0
. - Throws:
-
Any exception thrown by the value-initialization of
T0
. - Remarks:
-
This function does not participate in overload resolution unless
std::is_default_constructible_v<T0>
istrue
.
constexpr variant( variant const & w )
noexcept( mp_all<std::is_nothrow_copy_constructible<T>...>::value );
-
- Effects:
-
Initializes the variant to hold the same alternative and value as
w
. - Throws:
-
Any exception thrown by the initialization of the contained value.
- Remarks:
-
This function does not participate in overload resolution unless
std::is_copy_constructible_v<Ti>
istrue
for alli
.
constexpr variant( variant&& w )
noexcept( mp_all<std::is_nothrow_move_constructible<T>...>::value );
-
- Effects:
-
Initializes the variant to hold the same alternative and value as
w
. - Throws:
-
Any exception thrown by the move-initialization of the contained value.
- Remarks:
-
This function does not participate in overload resolution unless
std::is_move_constructible_v<Ti>
istrue
for alli
.
template<class U> constexpr variant( U&& u ) noexcept(/*see below*/);
-
Let
Tj
be a type that is determined as follows: build an imaginary functionFUN(Ti)
for each alternative typeTi
. The overloadFUN(Tj)
selected by overload resolution for the expressionFUN(std::forward<U>(u))
defines the alternativeTj
which is the type of the contained value after construction.- Effects:
-
Initializes
*this
to hold the alternative typeTj
and initializes the contained value fromstd::forward<U>(u)
. - Ensures:
-
holds_alternative<Tj>(*this)
. - Throws:
-
Any exception thrown by the initialization of the contained value.
- Remarks:
-
The expression inside
noexcept
is equivalent tostd::is_nothrow_constructible_v<Tj, U>
. This function does not participate in overload resolution unless-
sizeof…(T)
is nonzero, -
std::is_same_v<std::remove_cvref_t<U>, variant>
isfalse
, -
std::remove_cvref_t<U>
is neither a specialization ofin_place_type_t
nor a specialization ofin_place_index_t
, -
std::is_constructible_v<Tj, U>
istrue
, and -
the expression
FUN(std::forward<U>(u))
is well-formed.
-
template<class U, class... A>
constexpr explicit variant( in_place_type_t<U>, A&&... a );
-
- Effects:
-
Initializes the contained value of type
U
with the argumentsstd::forward<A>(a)…
. - Ensures:
-
holds_alternative<U>(*this)
. - Throws:
-
Any exception thrown by the initialization of the contained value.
- Remarks:
-
This function does not participate in overload resolution unless there is exactly one occurrence of
U
inT…
andstd::is_constructible_v<U, A…>
is true.
template<class U, class V, class... A>
constexpr explicit variant( in_place_type_t<U>, std::initializer_list<V> il,
A&&... a );
-
- Effects:
-
Initializes the contained value of type
U
with the argumentsil
,std::forward<A>(a)…
. - Ensures:
-
holds_alternative<U>(*this)
. - Throws:
-
Any exception thrown by the initialization of the contained value.
- Remarks:
-
This function does not participate in overload resolution unless there is exactly one occurrence of
U
inT…
andstd::is_constructible_v<U, initializer_list<V>&, A…>
istrue
.
template<size_t I, class... A>
constexpr explicit variant( in_place_index_t<I>, A&&... a );
-
- Effects:
-
Initializes the contained value of type
TI
with the argumentsstd::forward<A>(a)…
. - Ensures:
-
index() == I
. - Throws:
-
Any exception thrown by the initialization of the contained value.
- Remarks:
-
This function does not participate in overload resolution unless
I < sizeof…(T)
andstd::is_constructible_v<TI, A…>
istrue
.
template<size_t I, class V, class... A>
constexpr explicit variant( in_place_index_t<I>, std::initializer_list<V> il,
A&&... a );
-
- Effects:
-
Initializes the contained value of type
TI
with the argumentsil
,std::forward<A>(a)…
. - Ensures:
-
index() == I
. - Throws:
-
Any exception thrown by the initialization of the contained value.
- Remarks:
-
This function does not participate in overload resolution unless
I < sizeof…(T)
andstd::is_constructible_v<TI, initializer_list<V>&, A…>
istrue
.
Destructor
~variant();
-
- Effects:
-
Destroys the currently contained value.
Assignment
constexpr variant& operator=( const variant& r )
noexcept( mp_all<std::is_nothrow_copy_constructible<T>...>::value );
-
Let
j
ber.index()
.- Effects:
-
emplace<j>(get<j>(r))
. - Returns:
-
*this
. - Ensures:
-
index() == r.index()
. - Remarks:
-
This operator does not participate in overload resolution unless
std::is_copy_constructible_v<Ti> && std::is_copy_assignable_v<Ti>
istrue
for alli
.
constexpr variant& operator=( variant&& r )
noexcept( mp_all<std::is_nothrow_move_constructible<T>...>::value );
-
Let
j
ber.index()
.- Effects:
-
emplace<j>(get<j>(std::move(r)))
. - Returns:
-
*this
. - Ensures:
-
index() == r.index()
. - Remarks:
-
This operator does not participate in overload resolution unless
std::is_move_constructible_v<Ti> && std::is_move_assignable_v<Ti>
istrue
for alli
.
template<class U> constexpr variant& operator=( U&& u )
noexcept( /*see below*/ );
-
Let
Tj
be a type that is determined as follows: build an imaginary functionFUN(Ti)
for each alternative typeTi
. The overloadFUN(Tj)
selected by overload resolution for the expressionFUN(std::forward<U>(u))
defines the alternativeTj
which is the type of the contained value after construction.- Effects:
-
emplace<j>(std::forward<U>(u))
. - Returns:
-
*this
. - Ensures:
-
index() == j
. - Remarks:
-
The expression inside
noexcept
isstd::is_nothrow_constructible_v<Tj, U&&>
. This operator does not participate in overload resolution unless-
std::is_same_v<std::remove_cvref_t<T>, variant>
isfalse
, -
std::is_constructible_v<Tj, U&&> && std::is_assignable_v<Tj&, U&&>
istrue
, and -
the expression
FUN(std::forward<U>(u))
(withFUN
being the above-mentioned set of imaginary functions) is well-formed.
-
Modifiers
template<class U, class... A>
constexpr U& emplace( A&&... a );
-
Let
I
be the zero-based index ofU
inT…
.- Effects:
-
Equivalent to:
return emplace<I>(std::forward<A>(a)…);
- Remarks:
-
This function shall not participate in overload resolution unless
std::is_constructible_v<U, A&&…>
istrue
andU
occurs exactly once inT…
.
template<class U, class V, class... A>
constexpr U& emplace( std::initializer_list<V> il, A&&... a );
-
Let
I
be the zero-based index ofU
inT…
.- Effects:
-
Equivalent to:
return emplace<I>(il, std::forward<A>(a)…);
- Remarks:
-
This function shall not participate in overload resolution unless
std::is_constructible_v<U, std::initializer_list<V>&, A&&…>
istrue
andU
occurs exactly once inT…
.
template<size_t I, class... A>
constexpr variant_alternative_t<I, variant<T...>>&
emplace( A&&... a );
-
- Requires:
-
I < sizeof…(T)
. - Effects:
-
Destroys the currently contained value, then initializes a new contained value as if using the expression
Ti(std::forward<A>(a)…)
. - Ensures:
-
index() == I
. - Returns:
-
A reference to the new contained value.
- Throws:
-
Nothing unless the initialization of the new contained value throws.
- Exception Safety:
-
Strong. On exception, the contained value is unchanged.
- Remarks:
-
This function shall not participate in overload resolution unless
std::is_constructible_v<Ti, A&&…>
istrue
.
template<size_t I, class V, class... A>
constexpr variant_alternative_t<I, variant<T...>>&
emplace( std::initializer_list<V> il, A&&... a );
-
- Requires:
-
I < sizeof…(T)
. - Effects:
-
Destroys the currently contained value, then initializes a new contained value as if using the expression
Ti(il, std::forward<A>(a)…)
. - Ensures:
-
index() == I
. - Returns:
-
A reference to the new contained value.
- Throws:
-
Nothing unless the initialization of the new contained value throws.
- Exception Safety:
-
Strong. On exception, the contained value is unchanged.
- Remarks:
-
This function shall not participate in overload resolution unless
std::is_constructible_v<Ti, std::initializer_list<V>&, A&&…>
istrue
.
Value Status
constexpr bool valueless_by_exception() const noexcept;
-
- Returns:
-
false
.
Note
|
This function is provided purely for compatibility with std::variant .
|
constexpr size_t index() const noexcept;
-
- Returns:
-
The zero-based index of the active alternative.
Swap
void swap( variant& r ) noexcept( mp_all<std::is_nothrow_move_constructible<T>...,
is_nothrow_swappable<T>...>::value );
-
- Effects:
-
-
If
index() == r.index()
, callsswap(get<I>(*this), get<I>(r))
, whereI
isindex()
. -
Otherwise, as if
variant tmp(std::move(*this)); *this = std::move(r); r = std::move(tmp);
-
Converting Constructors (extension)
template<class... U> variant( variant<U...> const& r )
noexcept( mp_all<std::is_nothrow_copy_constructible<U>...>::value );
-
- Effects:
-
Initializes the contained value from the contained value of
r
. - Throws:
-
Any exception thrown by the initialization of the contained value.
- Remarks:
-
This function does not participate in overload resolution unless all types in
U…
are inT…
andstd::is_copy_constructible_v<Ui>::value
istrue
for allUi
.
template<class... U> variant( variant<U...>&& r )
noexcept( mp_all<std::is_nothrow_move_constructible<U>...>::value );
-
- Effects:
-
Initializes the contained value from the contained value of
std::move(r)
. - Throws:
-
Any exception thrown by the initialization of the contained value.
- Remarks:
-
This function does not participate in overload resolution unless all types in
U…
are inT…
andstd::is_move_constructible_v<Ui>::value
istrue
for allUi
.
Subset (extension)
template<class... U> constexpr variant<U...> subset() & ;
template<class... U> constexpr variant<U...> subset() const& ;
-
- Returns:
-
A
variant<U…>
whose contained value is copy-initialized from the contained value of*this
and has the same type. - Throws:
-
-
If the active alternative of
*this
is not among the types inU…
,bad_variant_access
. -
Otherwise, any exception thrown by the initialization of the contained value.
-
- Remarks:
-
This function does not participate in overload resolution unless all types in
U…
are inT…
andstd::is_copy_constructible_v<Ui>::value
istrue
for allUi
.
template<class... U> constexpr variant<U...> subset() && ;
template<class... U> constexpr variant<U...> subset() const&& ;
-
- Returns:
-
A
variant<U…>
whose contained value is move-initialized from the contained value of*this
and has the same type. - Throws:
-
-
If the active alternative of
*this
is not among the types inU…
,bad_variant_access
. -
Otherwise, any exception thrown by the initialization of the contained value.
-
- Remarks:
-
This function does not participate in overload resolution unless all types in
U…
are inT…
andstd::is_move_constructible_v<Ui>::value
istrue
for allUi
.
variant_alternative
template<size_t I, class T> struct variant_alternative<I, T const>;
template<size_t I, class T> struct variant_alternative<I, T volatile>;
template<size_t I, class T> struct variant_alternative<I, T const volatile>;
template<size_t I, class T> struct variant_alternative<I, T&>; // extension
template<size_t I, class T> struct variant_alternative<I, T&&>; // extension
-
If
typename variant_alternative<I, T>::type
exists and isU
,-
variant_alternative<I, T const>::type
isU const
; -
variant_alternative<I, T volatile>::type
isU volatile
; -
variant_alternative<I, T const volatile>::type
isU const volatile
. -
variant_alternative<I, T&>::type
isU&
. -
variant_alternative<I, T&&>::type
isU&&
.
Otherwise, these structs have no member
type
. -
template<size_t I, class... T>
struct variant_alternative<I, variant<T...>>;
-
When
I < sizeof…(T)
, the nested typetype
is an alias for theI
-th (zero-based) type inT…
. Otherwise, there is no membertype
.
holds_alternative
template<class U, class... T>
constexpr bool holds_alternative(const variant<T...>& v) noexcept;
-
- Requires:
-
The type
U
occurs exactly once inT…
. Otherwise, the program is ill-formed. - Returns:
-
true
ifindex()
is equal to the zero-based index ofU
inT…
.
get
template<size_t I, class... T>
constexpr variant_alternative_t<I, variant<T...>>&
get(variant<T...>& v);
template<size_t I, class... T>
constexpr variant_alternative_t<I, variant<T...>>&&
get(variant<T...>&& v);
template<size_t I, class... T>
constexpr const variant_alternative_t<I, variant<T...>>&
get(const variant<T...>& v);
template<size_t I, class... T>
constexpr const variant_alternative_t<I, variant<T...>>&&
get(const variant<T...>&& v);
-
- Effects:
-
If
v.index()
isI
, returns a reference to the object stored in the variant. Otherwise, throwsbad_variant_access
. - Remarks:
-
These functions do not participate in overload resolution unless
I
<sizeof…(T)
.
template<class U, class... T>
constexpr U& get(variant<T...>& v);
template<class U, class... T>
constexpr U&& get(variant<T...>&& v);
template<class U, class... T>
constexpr const U& get(const variant<T...>& v);
template<class U, class... T>
constexpr const U&& get(const variant<T...>&& v);
-
- Requires:
-
The type
U
occurs exactly once inT…
. Otherwise, the program is ill-formed. - Effects:
-
If
v
holds a value of typeU
, returns a reference to that value. Otherwise, throwsbad_variant_access
.
get_if
template<size_t I, class... T>
constexpr add_pointer_t<variant_alternative_t<I, variant<T...>>>
get_if(variant<T...>* v) noexcept;
template<size_t I, class... T>
constexpr add_pointer_t<const variant_alternative_t<I, variant<T...>>>
get_if(const variant<T...>* v) noexcept;
-
- Effects:
-
A pointer to the value stored in the variant, if
v != nullptr && v->index() == I
. Otherwise,nullptr
. - Remarks:
-
These functions do not participate in overload resolution unless
I
<sizeof…(T)
.
template<class U, class... T>
constexpr add_pointer_t<U>
get_if(variant<T...>* v) noexcept;
template<class U, class... T>
constexpr add_pointer_t<const U>
get_if(const variant<T...>* v) noexcept;
-
- Requires:
-
The type
U
occurs exactly once inT…
. Otherwise, the program is ill-formed. - Effects:
-
Equivalent to:
return get_if<I>(v);
withI
being the zero-based index ofU
inT…
.
Relational Operators
template<class... T>
constexpr bool operator==(const variant<T...>& v, const variant<T...>& w);
-
- Returns:
-
v.index() == w.index() && get<I>(v) == get<I>(w)
, whereI
isv.index()
.
template<class... T>
constexpr bool operator!=(const variant<T...>& v, const variant<T...>& w);
-
- Returns:
-
!(v == w)
.
template<class... T>
constexpr bool operator<(const variant<T...>& v, const variant<T...>& w);
-
- Returns:
-
v.index() < w.index() || (v.index() == w.index() && get<I>(v) < get<I>(w))
, whereI
isv.index()
.
template<class... T>
constexpr bool operator>(const variant<T...>& v, const variant<T...>& w);
-
- Returns:
-
w < v
.
template<class... T>
constexpr bool operator<=(const variant<T...>& v, const variant<T...>& w);
-
- Returns:
-
v.index() < w.index() || (v.index() == w.index() && get<I>(v) <= get<I>(w))
, whereI
isv.index()
.
template<class... T>
constexpr bool operator>=(const variant<T...>& v, const variant<T...>& w);
-
- Returns:
-
w <= v
.
visit
template<class F, class... V>
constexpr /*see below*/ visit(F&& f, V&&... v);
-
- Returns:
-
std::forward<F>(f)(get<I>(std::forward<V>(v))…)
, whereI…
isv.index()…
.
swap
template<class... T>
void swap(variant<T...>& v, variant<T...>& w) noexcept( /*see below*/ );
-
- Effects:
-
Equivalent to
v.swap(w)
.
bad_variant_access
class bad_variant_access: public std::exception
{
public:
bad_variant_access() noexcept = default;
char const * what() const noexcept
{
return "bad_variant_access";
}
};
Copyright and License
This documentation is copyright 2018, 2019 Peter Dimov and is distributed under the Boost Software License, Version 1.0.