Document Number: | P0088R3, ISO/IEC JTC1 SC22 WG21 |
Audience: | LWG |
Date: | 2016-06-23 |
Author: | Axel Naumann (axel@cern.ch) |
Variant is the very spice of life,
That gives it all its flavor.
- William Cowper's "The Task", or actually a variant thereof
C++17 needs a type-safe union:
Lets not make the same mistake we made with std::optional
by putting this library into a TS. We waited three years where no substantial feedback or discussion occurred, and then moved it into the IS virtually unchanged. Meanwhile, the C++ community suffered, and we continue to suffer from lack of this essential vocabulary type in interfaces.
The implications of the consensus variant
design are well understood and have been explored over several LEWG discussions, over a thousand emails, a joint LEWG/EWG session, and not to mention 12 years of experience with Boost and other libraries. The last major change made to the proposal was non-breaking and added exception throws where previously there was undefined behavior. Since then, all suggested modifications have been cosmetic, rehashes of older discussions, or would be handled just as well by defect resolutions.
The C++ community should not wait three years for a widely useful library that is already done, fits its purpose, and has had such extensive review. There is a low chance that we will regret including variant in C++17, but a high chance that we will regret omitting it.
This proposal attempts to apply the lessons learned from optional
(1). It behaves as below:
variant<int, float> v, w;
v = 12;
int i = get<int>(v);
w = get<int>(v);
w = get<0>(v); // same effect as the previous line
w = v; // same effect as the previous line
get<double>(v); // ill formed
get<3>(v); // ill formed
try {
get<float>(w); // will throw.
}
catch (bad_variant_access&) {}
The LEWG review in Urbana resulted in the following straw polls that motivated changes in this revision of the paper:
tuple
-like interface instead of the collection of variant
-specific functions, is_alternative
etc.? SF=8 WF=5 N=2 WA=1 SA=0variant
should be as constexpr
as std::optional
variant<int, int>
and variant<int, const int>
.In Lenexa, LEWG decided that variant
should model a discriminated union.
variant<int, string> x = "abc";
? SF=5 WF=4 N=1 WA=1 SA=0variant<string> == const char *
and variant<const char *, string> == const char *
? SF=0 WF=2 N=5 WA=3 SA=3variant<string> == variant<const char *>
, and variant<A, B, C> == variant<X, Y, Z>
? SF=0 WF=1 N=0 WA=4 SA=8variant<int, const int>
, qualified types in general? SF=9 WF=4 N=1 WA=1 SA=1visit(VISITOR, var1, var2, var3, ...)
? SF=0 WF=7 N=7 WA=1 SA=0visit(VISITOR, v1, v2)
? SF=0 WF=1 N=10 WA=1 SA=3common_type
: 12op()()
, rest must convert to that: 1variant<return types>
: 2variant<return types>
if they're different, otherwise single return type: 0void * data()
T* get<T>(variant<A, B, C> *)
(a la any_cast
)index()
return -1 on empty? (The alternative is to make non-emptiness a precondition.) SF=4 WF=1 N=3 WA=1 SA=2variant::{visit,get}
have preconditions that the variant
not be empty? SF=4 WF=8 N=2 WA=0 SA=0This addressed items raised by LWG.
template <class T> variant operator=(T&&)
, using a hypothetical function taking the alternative types.visit(Visitor)
.valid()
to !corrupted_by_exception()
(14 votes, runner-up: 7 votes) that was later on changed to !valueless_by_exception()
after a discussion and based on a poll on the LWG and LEWG email lists, with 32 responses.As requested by the LEWG review in Urbana, this revision
variant
to be empty;void
as alternatives behave;tuple
for parameter pack operations; is_alternative
does not yet exist as part of tuple
and is thus kept;index()
to return -1
(now also known is tuple_not_found
) if !valid()
;Beyond these requests, this revision
variant
, an alternative, and a different variant
type;variant
a regular type.variant
now models a discriminated union.hash<variant<int>>
can now return different values than hash<int>
(and it should - presumably it should take the index() into account).template <size_t,...> get<I,...>(variant)
.is_alternative
that is not strictly needed to make variant
usable (LEWG feedback).std::swap()
specialization; the default is just fine.visit()
is now variadic.type_list
; reduced probability of !valid()
for copy assignment / construction.valid()
a visible state for value extraction functions (get()
, visit()
).valid()
precondition for copy / move construction from a variant
.!v.valid()
, make get<...>(v)
and visit(v)
throw.template <class T> variant::variant(T&&)
and template <class T> variant::operator=(T&&)
, using a hypothetical function taking the alternative types.visit(Visitor)
.valid()
to !valueless_by_exception()
, following the strong recommendation from a L(E)WG poll.tuple_find
: it was not relevant for using variant
as that already provides index- and type-based accesses; it was a considerable fraction of the proposed wording; it warrants a dedicated design paper, should someone wish to have it.emplaced_
... becomes in_place_
...constexpr
support: construction, accessors, destruction,tuple_not_found
that got removed by mistake. Call it variant_npos
.tuple_size
to variant_size
, tuple_element
to variant_alternative
to clarify that this is not tuple-like. This avoids a clash with structured binding. The committee seems to have changed its common mind regarding these templates, reverting LEWG's decision from the first revision.variant
valueless_by_exception
if an exception is thrown during emplace / construction; merely state that it might become valueless_by_exception
.noexcept(see below)
for swap
.std::move()
.experimental::
removed, is_constructible_v<T_j, T&&>
is now spelled is_constructible_v<T_j, T>
; some postconditions that are already specified in the equivalent Effects elements have been removed. These tweaks have not been highlighted in blue.LEWG opted against introducing an explicit additional variant state, representing its invalid (and possibly empty, default constructed) state. This is meant to simplify the variant
use: as getting a variant
into the invalid state is sufficiently difficult, it was felt that there is no need to regularly check for a variant becoming invalid. This prevents all get<int>(v)
calls from being protected by if (v.valid())
.
Accessing an invalid variant's value is undefined behavior, whatever alternative is accessed.
The variant
's invalid state needs to be visible: accessing its contents or visiting it will violate preconditions; users must be able to verify that a variant
is not in this state.
When in the invalid state, index()
returns variant_npos
; variant
provides valid()
as a usability feature.
This usually does not need to be checked given how rare the invalid case is. It (generally) keeps a variant with N alternatives as an N-state type.
Default construction of a variant
should be allowed, to increase usability for instance in containers. LEWG opted against a variant
default-initialized into its invalid state, to make invalid variant
s really rare.
Instead, the variant
can be initialized with the first alternative (similar to the behavior of initialization of a union
) only if that is default constructible. For cases where this behavior should be explicit, and for cases where no such default constructible alternative exists, there is a separate type monostate
that can be used as first alternative, to explicitly enable default construction.
No header called variant
exists; testing for this header's existence is thus sufficient.
The insertions and deletions in this section describe the changes to the C++ Working Paper. Grayish background indicates proposed wording.
Insert a new element in Table 14, C++ library headers of [general.namespaces], named <variant>
.
? Variants [variant]
?.1 In general [variant.general]
A variant object holds and manages the lifetime of a value. If the variant holds a value, that value's type has to be one of the template argument types given to
variant
. These template arguments are called alternatives.?.2 Header
<variant>
synopsis [variant.synopsis]namespace std { // ?.3, variant of value types template <class... Types> class variant; // ?.4, variant helper classes template <class T> struct variant_size; // undefined template <class T> struct variant_size<const T>; template <class T> struct variant_size<volatile T>; template <class T> struct variant_size<const volatile T>; template <class T> constexpr size_t variant_size_v = variant_size<T>::value; template <class... Types> struct variant_size<variant<Types...>>; template <size_t I, class T> struct variant_alternative; // undefined template <size_t I, class T> struct variant_alternative<I, const T>; template <size_t I, class T> struct variant_alternative<I, volatile T>; template <size_t I, class T> struct variant_alternative<I, const volatile T>; template <size_t I, class T> using variant_alternative_t = typename variant_alternative<I, T>::type; template <size_t I, class... Types> struct variant_alternative<I, variant<Types...>>; constexpr size_t variant_npos = -1; // ?.5, In-place construction template <class T> struct in_place_type_t{ explicit in_place_type_t() = default; }; template <class T> constexpr in_place_type_t<T> in_place_type{}; template <size_t I> struct in_place_index_t{ explicit in_place_index_t() = default; }; template <size_t I> constexpr in_place_index_t<I> in_place_index{}; // ?.6, Value access template <class T, class... Types> constexpr bool holds_alternative(const variant<Types...>&) noexcept; template <size_t I, class... Types> constexpr variant_alternative_t<I, variant<Types...>>& get(variant<Types...>&); template <size_t I, class... Types> constexpr variant_alternative_t<I, variant<Types...>>&& get(variant<Types...>&&); template <size_t I, class... Types> constexpr variant_alternative_t<I, variant<Types...>> const& get(const variant<Types...>&); template <size_t I, class... Types> constexpr variant_alternative_t<I, variant<Types...>> const&& get(const variant<Types...>&&); template <class T, class... Types> constexpr T& get(variant<Types...>&); template <class T, class... Types> constexpr T&& get(variant<Types...>&&); template <class T, class... Types> constexpr const T& get(const variant<Types...>&); template <class T, class... Types> constexpr const T&& get(const variant<Types...>&&); template <size_t I, class... Types> constexpr add_pointer_t<variant_alternative_t<I, variant<Types...>>> get_if(variant<Types...>*) noexcept; template <size_t I, class... Types> constexpr add_pointer_t<const variant_alternative_t<I, variant<Types...>>> get_if(const variant<Types...>*) noexcept; template <class T, class... Types> constexpr add_pointer_t<T> get_if(variant<Types...>*) noexcept; template <class T, class... Types> constexpr add_pointer_t<const T> get_if(const variant<Types...>*) noexcept; // ?.7, Relational operators template <class... Types> constexpr bool operator==(const variant<Types...>&, const variant<Types...>&); template <class... Types> constexpr bool operator!=(const variant<Types...>&, const variant<Types...>&); template <class... Types> constexpr bool operator<(const variant<Types...>&, const variant<Types...>&); template <class... Types> constexpr bool operator>(const variant<Types...>&, const variant<Types...>&); template <class... Types> constexpr bool operator<=(const variant<Types...>&, const variant<Types...>&); template <class... Types> constexpr bool operator>=(const variant<Types...>&, const variant<Types...>&); // ?.8, Visitation template <class Visitor, class... Variants> constexpr see below visit(Visitor&&, Variants&&...); // ?.9, Class monostate struct monostate; // ?.10, monostate relational operators constexpr bool operator<(monostate, monostate) noexcept; constexpr bool operator>(monostate, monostate) noexcept; constexpr bool operator<=(monostate, monostate) noexcept; constexpr bool operator>=(monostate, monostate) noexcept; constexpr bool operator==(monostate, monostate) noexcept; constexpr bool operator!=(monostate, monostate) noexcept; // ?.11, Specialized algorithms template <class... Types> void swap(variant<Types...>&, variant<Types...>&) noexcept(see below); // ?.12, class bad_variant_access class bad_variant_access; // ?.13, Hash support template <class T> struct hash; template <class... Types> struct hash<variant<Types...>>; template <> struct hash<monostate>; // ?.14, Allocator-related traits template <class T, class Alloc> struct uses_allocator; template <class... Types, class Alloc> struct uses_allocator<variant<Types...>, Alloc>; } // namespace std
?.3
variant
of value types [variant.variant]namespace std { template <class... Types> class variant { public: // ?.3.1 Constructors constexpr variant() noexcept(see below); variant(const variant&); variant(variant&&) noexcept(see below); template <class T> constexpr variant(T&&) noexcept(see below); template <class T, class... Args> constexpr explicit variant(in_place_type_t<T>, Args&&...); template <class T, class U, class... Args> constexpr explicit variant(in_place_type_t<T>, initializer_list<U>, Args&&...); template <size_t I, class... Args> constexpr explicit variant(in_place_index_t<I>, Args&&...); template <size_t I, class U, class... Args> constexpr explicit variant(in_place_index_t<I>, initializer_list<U>, Args&&...); // allocator-extended constructors template <class Alloc> variant(allocator_arg_t, const Alloc&); template <class Alloc> variant(allocator_arg_t, const Alloc&, const variant&); template <class Alloc> variant(allocator_arg_t, const Alloc&, variant&&); template <class Alloc, class T> variant(allocator_arg_t, const Alloc&, T&&); template <class Alloc, class T, class... Args> variant(allocator_arg_t, const Alloc&, in_place_type_t<T>, Args&&...); template <class Alloc, class T, class U, class... Args> variant(allocator_arg_t, const Alloc&, in_place_type_t<T>, initializer_list<U>, Args&&...); template <class Alloc, size_t I, class... Args> variant(allocator_arg_t, const Alloc&, in_place_index_t<I>, Args&&...); template <class Alloc, size_t I, class U, class... Args> variant(allocator_arg_t, const Alloc&, in_place_index_t<I>, initializer_list<U>, Args&&...); // ?.3.2, Destructor ~variant(); // ?.3.3, Assignment variant& operator=(const variant&); variant& operator=(variant&&) noexcept(see below); template <class T> variant& operator=(T&&) noexcept(see below); // ?.3.4, Modifiers template <class T, class... Args> void emplace(Args&&...); template <class T, class U, class... Args> void emplace(initializer_list<U>, Args&&...); template <size_t I, class... Args> void emplace(Args&&...); template <size_t I, class U, class... Args> void emplace(initializer_list<U>, Args&&...); // ?.3.5, Value status constexpr bool valueless_by_exception() const noexcept; constexpr size_t index() const noexcept; // ?.3.6, Swap void swap(variant&) noexcept(see below); }; } // namespace std
Any instance of
variant
at any given time either holds a value of one of its alternative types, or it holds no value. When an instance ofvariant
holds a value of alternative typeT
, it means that a value of typeT
, referred to as thevariant
object's contained value, is allocated within the storage of thevariant
object. Implementations are not permitted to use additional storage, such as dynamic memory, to allocate the contained value. The contained value shall be allocated in a region of thevariant
storage suitably aligned for all types inTypes...
. It is implementation defined whether over-aligned types are supported.All types in
Types...
shall be (possibly cv-qualified) object types, (possibly cv-qualified) void, or references. [Note: Implementations could decide to store references in a wrapper. — end note]?.3.1 Constructors [variant.ctor]
In the descriptions that follow, let
i
be in the range[0,sizeof...(Types))
, andT_i
be thei
th type inTypes...
.
constexpr variant() noexcept(see below);
- Effects:
- Constructs a
variant
holding a value-initialized value of typeT_0
.- Postconditions:
valueless_by_exception()
isfalse
andindex()
is0
.- Throws:
- Any exception thrown by the value initialization of
T_0
.- Remarks:
- This function shall be
constexpr
if and only if the value initialization of the alternative typeT_0
would satisfy the requirements for aconstexpr
function. The expression insidenoexcept
is equivalent tois_nothrow_default_constructible_v<T_0>
. This function shall not participate in overload resolution unlessis_default_constructible_v<T_0>
istrue
. [Note: see also classmonostate
. — end note]
variant(const variant& w);
- Effects:
- If
w
holds a value, initializes thevariant
to hold the same alternative asw
and direct-initializes the contained value withget<j>(w)
, wherej
isw.index()
. Otherwise, initializes thevariant
to not hold a value.- Throws:
- Any exception thrown by direct-initializing any
T_i
for alli
.- Remarks:
- This function shall not participate in overload resolution unless
is_copy_constructible_v<T_i>
istrue
for alli
.
variant(variant&& w) noexcept(see below);
- Effects:
- If
w
holds a value, initializes thevariant
to hold the same alternative asw
and direct-initializes the contained value withget<j>(std::move(w))
, wherej
isw.index()
. Otherwise, initializes thevariant
to not hold a value.- Throws:
- Any exception thrown by move-constructing any
T_i
for alli
.- Remarks:
- The expression inside
noexcept
is equivalent to the logical AND ofis_nothrow_move_constructible_v<T_i>
for alli
. This function shall not participate in overload resolution unlessis_move_constructible_v<T_i>
istrue
for alli
.
template <class T> constexpr variant(T&& t) noexcept(see below);
- Let
T_j
be a type that is determined as follows: build an imaginary functionFUN(T_i)
for each alternative typeT_i
. The overloadFUN(T_j)
selected by overload resolution for the expressionFUN(std::forward<T>(t))
defines the alternativeT_j
which is the type of the contained value after construction.- Effects:
- Initializes
*this
to hold the alternative typeT_j
and direct-initializes the contained value as if direct-non-list-initializing it withstd::forward<T>(t)
.- Postconditions:
holds_alternative<T_j>(*this)
istrue
.- Throws:
- Any exception thrown by the initialization of the selected alternative
T_j
.- Remarks:
- This function shall not participate in overload resolution unless
is_same_v<decay_t<T>, variant>
isfalse
, unlessis_constructible_v<T_j, T>
istrue
, and unless the expressionFUN(std::forward<T>(t))
(withFUN
being the above-mentioned set of imaginary functions) is well formed. [Note:is ill-formed, as both alternative types have an equally viable constructor for the argument. — end note] The expression insidevariant<string, string> v("abc");
noexcept
is equivalent tois_nothrow_constructible_v<T_j, T>
. IfT_j
's selected constructor is aconstexpr
constructor, this constructor shall be aconstexpr
constructor.
template <class T, class... Args> constexpr explicit variant(in_place_type_t<T>, Args&&... args);
- Effects:
- Initializes the contained value of type
T
with the argumentsstd::forward<Args>(args)...
.- Postcondition:
holds_alternative<T>(*this)
istrue
.- Throws:
- Any exception thrown by calling the selected constructor of
T
.- Remarks:
- This function shall not participate in overload resolution unless there is exactly one occurrence of
T
inTypes...
andis_constructible_v<T, Args...>
istrue
. IfT
's selected constructor is aconstexpr
constructor, this constructor shall be aconstexpr
constructor.
template <class T, class U, class... Args> constexpr explicit variant(in_place_type_t<T>, initializer_list<U> il, Args&&... args);
- Effects:
- Initializes the contained value as if constructing an object of type
T
with the argumentsil, std::forward<Args>(args)...
.- Postcondition:
holds_alternative<T>(*this)
istrue
.- Throws:
- Any exception thrown by calling the selected constructor of
T
.- Remarks:
- This function shall not participate in overload resolution unless there is exactly one occurrences of
T
inTypes...
andis_constructible_v<T, initializer_list<U>&, Args...>
istrue
. IfT
's selected constructor is aconstexpr
constructor, this constructor shall be aconstexpr
constructor.
template <size_t I, class... Args> constexpr explicit variant(in_place_index_t<I>, Args&&... args);
- Effects:
- Initializes the contained value as if constructing an object of type
T_I
with the argumentsstd::forward<Args>(args)...
.- Postcondition:
index()
isI
.- Throws:
- Any exception thrown by calling the selected constructor of
T_I
.- Remarks:
- This function shall not participate in overload resolution unless
I
is less thansizeof...(Types)
andis_constructible_v<T_I, Args...>
istrue
. IfT_I
's selected constructor is aconstexpr
constructor, this constructor shall be aconstexpr
constructor.
template <size_t I, class U, class... Args> constexpr explicit variant(in_place_index_t<I>, initializer_list<U> il, Args&&... args);
- Effects:
- Initializes the contained value as if constructing an object of type
T_I
with the argumentsil, std::forward<Args>(args)...
.- Postcondition:
index()
isI
.- Remarks:
- This function shall not participate in overload resolution unless
I
is less thansizeof...(Types)
andis_constructible_v<T_I, initializer_list<U>&, Args...>
istrue
. IfT_I
's selected constructor is aconstexpr
constructor, this constructor shall be aconstexpr
constructor.
// allocator-extended constructors
template <class Alloc>
variant(allocator_arg_t, const Alloc& a);
template <class Alloc>
variant(allocator_arg_t, const Alloc& a, const variant& v);
template <class Alloc>
variant(allocator_arg_t, const Alloc& a, variant&& v);
template <class Alloc, class T>
variant(allocator_arg_t, const Alloc& a, T&& t);
template <class Alloc, class T, class... Args>
variant(allocator_arg_t, const Alloc& a, in_place_type_t<T>, Args&&... args);
template <class Alloc, class T, class U, class... Args>
variant(allocator_arg_t, const Alloc& a, in_place_type_t<T>, initializer_list<U> il, Args&&... args);
template <class Alloc, size_t I, class... Args>
variant(allocator_arg_t, const Alloc& a, in_place_index_t<I>, Args&&... args);
template <class Alloc, size_t I, class U, class... Args>
variant(allocator_arg_t, const Alloc& a, in_place_index_t<I>, initializer_list<U> il, Args&&... args);
- Requires:
Alloc
shall meet the requirements for anAllocator
(17.6.3.5).- Effects:
- Equivalent to the preceding constructors except that the contained value is constructed with uses-allocator construction (20.7.7.2).
?.3.2 Destructor [variant.dtor]
~variant();
- Effects:
- If
valueless_by_exception()
isfalse
, destroys the currently contained value.- Remarks:
- If
is_trivially_destructible_v<T_i> == true
for allT_i
then this destructor shall be a trivial destructor.?.3.3 Assignment [variant.assign]
variant& operator=(const variant& rhs);
- Effects:
- If neither
*this
norrhs
holds a value, there is no effect. Otherwise,- if
*this
holds a value butrhs
does not, destroys the value contained in*this
and sets*this
to not hold a value. Otherwise,- if
index() == rhs.index()
, assigns the value contained inrhs
to the value contained in*this
. Otherwise,- copies the value contained in
rhs
to a temporary, then destroys any value contained in*this
. Sets*this
to hold the same alternative index asrhs
and initializes the value contained in*this
as if direct-non-list-initializing an object of typeT_j
withstd::forward<T_j>(TMP)
, withTMP
being the temporary andj
beingrhs.index()
.- Returns:
*this
.- Postconditions:
index() == rhs.index()
- Remarks:
- This function shall not participate in overload resolution unless
is_copy_constructible_v<T_i> && is_move_constructible_v<T_i> && is_copy_assignable_v<T_i>
istrue
for alli
.
- If an exception is thrown during the call to
T_j
's copy assignment, the state of the contained value is as defined by the exception safety guarantee ofT_j
's copy assignment;index()
will bej
.- If an exception is thrown during the call to
T_j
's copy construction (withj
beingrhs.index()
),*this
will remain unchanged.- If an exception is thrown during the call to
T_j
's move construction, thevariant
will hold no value.
variant& operator=(variant&& rhs) noexcept(see below);
- Effects:
- If neither
*this
norrhs
holds a value, there is no effect. Otherwise,- if
*this
holds a value butrhs
does not, destroys the value contained in*this
and sets*this
to not hold a value. Otherwise,- if
index() == rhs.index()
, assignsget<j>(std::move(rhs))
to the value contained in*this
, withj
beingindex()
. Otherwise,- destroys any value contained in
*this
. Sets*this
to hold the same alternative index asrhs
and initializes the value contained in*this
as if direct-non-list-initializing an object of typeT_j
withget<j>(std::move(rhs))
withj
beingrhs.index()
.- Returns:
*this
.- Remarks:
- This function shall not participate in overload resolution unless
is_move_constructible_v<T_i> && is_move_assignable_v<T_i>
istrue
for alli
. The expression insidenoexcept
is equivalent to:is_nothrow_move_constructible_v<T_i> && is_nothrow_move_assignable_v<T_i>
for alli
. If an exception is thrown during the call toT_j
's move construction (withj
beingrhs.index()
), thevariant
will hold no value. If an exception is thrown during the call toT_j
's move assignment, the state of the contained value is as defined by the exception safety guarantee ofT_j
's move assignment;index()
will bej
.
template <class T> variant& operator=(T&& t) noexcept(see below);
- Let
T_j
be a type that is determined as follows: build an imaginary functionFUN(T_i)
for each alternative typeT_i
. The overloadFUN(T_j)
selected by overload resolution for the expressionFUN(std::forward<T>(t))
defines the alternativeT_j
which is the type of the contained value after assignment.- Effects:
- If
*this
holds aT_j
, assignsstd::forward<T>(t)
to the value contained in*this
. Otherwise, destroys any value contained in*this
, sets*this
to hold the alternative typeT_j
as selected by the imaginary function overload resolution described above, and direct-initializes the contained value as if direct-non-list-initializing it withstd::forward<T>(t)
.- Postcondition:
holds_alternative<T_j>(*this)
istrue
, withT_j
selected by the imaginary function overload resolution described above.- Returns:
*this
.- Remarks:
- This function shall not participate in overload resolution unless
is_same_v<decay_t<T>, variant>
isfalse
, unlessis_assignable_v<T_j&, T> && is_constructible_v<T_j, T>
istrue
, and unless the expressionFUN(std::forward<T>(t))
(withFUN
being the above-mentioned set of imaginary functions) is well formed. [Note:is ill-formed, as both alternative types have an equally viable constructor for the argument. — end note] The expression insidevariant<string, string> v; v = "abc";
noexcept
is equivalent to:is_nothrow_assignable_v<T_j&, T> && is_nothrow_constructible_v<T_j, T>
. If an exception is thrown during the assignment ofstd::forward<T>(t)
to the value contained in*this
, the state of the contained value andt
are as defined by the exception safety guarantee of the assignment expression;valueless_by_exception()
will befalse
. If an exception is thrown during the initialization of the contained value, thevariant
object might not hold a value.?.3.4 Modifiers [variant.mod]
template <class T, class... Args> void emplace(Args&&... args);
- Effects:
- Equivalent to
emplace<I>(std::forward<Args>(args)...)
whereI
is the zero-based index ofT
inTypes...
.- Remarks:
- This function shall not participate in overload resolution unless
is_constructible_v<T, Args...>
istrue
, andT
occurs exactly once inTypes...
.
template <class T, class U, class... Args> void emplace(initializer_list<U> il, Args&&... args);
- Effects:
- Equivalent to
emplace<I>(il, std::forward<Args>(args)...)
whereI
is the zero-based index ofT
inTypes...
.- Remarks:
- This function shall not participate in overload resolution unless
is_constructible_v<T, initializer_list<U>&, Args...>
istrue
, andT
occurs exactly once inTypes...
.
template <size_t I, class... Args> void emplace(Args&&... args);
- Requires:
I < sizeof...(Types)
- Effects:
- Destroys the currently contained value if
valueless_by_exception()
isfalse
. Then direct-initializes the contained value as if constructing a value of typeT_I
with the argumentsstd::forward<Args>(args)...
.- Postcondition:
index()
isI
.- Throws:
- Any exception thrown during the initialization of the contained value.
- Remarks:
- This function shall not participate in overload resolution unless
is_constructible_v<T_I, Args...>
istrue
. If an exception is thrown during the initialization of the contained value, thevariant
might not hold a value.
template <size_t I, class U, class... Args> void emplace(initializer_list<U> il, Args&&... args);
- Requires:
I < sizeof...(Types)
- Effects:
- Destroys the currently contained value if
valueless_by_exception()
isfalse
. Then direct-initializes the contained value as if constructing an object of typeT_I
with the argumentsil, std::forward<Args>(args)...
.- Postcondition:
index()
isI
.- Throws:
- Any exception thrown during the initialization of the contained value.
- Remarks:
- This function shall not participate in overload resolution unless
is_constructible_v<T_I, initializer_list<U>&, Args...>
istrue
. If an exception is thrown during the initialization of the contained value, thevariant
might not hold a value.?.3.5 Value status [variant.status]
constexpr bool valueless_by_exception() const noexcept;
- Effects:
- Returns
false
if and only if thevariant
holds a value. [Note: Avariant
might not hold a value if an exception is thrown during a type-changing assignment or emplacement. The latter means that even avariant<float,int>
can becomevalueless_by_exception()
, for instance by— end note]struct S { operator int() { throw 42; }}; variant<float, int> v{12.f}; v.emplace<1>(S());
constexpr size_t index() const noexcept;
- Effects:
- If
valueless_by_exception()
istrue
, returnsvariant_npos
. Otherwise, returns the zero-based index of the alternative of the contained value.?.3.6 Swap [variant.swap]
void swap(variant& rhs) noexcept(see below);
- Effects:
- if
valueless_by_exception() && rhs.valueless_by_exception()
no effect. Otherwise,- if
index() == rhs.index()
, callsswap(get<i>(*this), get<i>(rhs))
wherei
isindex()
. Otherwise,- exchanges values of
rhs
and*this
.- Throws:
- Any exception thrown by
swap(get<i>(*this), get<i>(rhs))
withi
beingindex()
andvariant
's move constructor and move assignment operator.- Remarks:
- This function shall not participate in overload resolution unless
is_swappable_v<T_i>
istrue
for alli
. If an exception is thrown during the call to functionswap(get<i>(*this), get<i>(rhs))
, the states of the contained values of*this
and ofrhs
are determined by the exception safety guarantee ofswap
for lvalues ofT_i
withi
beingindex()
. If an exception is thrown during the exchange of the values of*this
andrhs
, the states of the values of*this
and ofrhs
are determined by the exception safety guarantee ofvariant
's move constructor and move assignment operator. The expression insidenoexcept
is equivalent to the logical AND ofis_nothrow_move_constructible_v<T_i> && is_nothrow_swappable_v<T_i>
for alli
.?.4
variant
helper classes [variant.helper]
template <class T> struct variant_size;
- Remarks:
- All specializations of
variant_size<T>
shall meet theUnaryTypeTrait
requirements (20.10.1) with aBaseCharacteristic
ofintegral_constant<size_t, N>
for someN
.
template <class T> class variant_size<const T>;
template <class T> class variant_size<volatile T>;
template <class T> class variant_size<const volatile T>;
- Let
VS
denotevariant_size<T>
of the cv-unqualified typeT
. Then each of the three templates shall meet theUnaryTypeTrait
requirements (20.10.1) with aBaseCharacteristic
ofintegral_constant<size_t, VS::value>
.
template <class... Types>
struct variant_size<variant<Types...>>
: integral_constant<size_t, sizeof...(Types)> { };
template <size_t I, class T> class variant_alternative<I, const T>;
template <size_t I, class T> class variant_alternative<I, volatile T>;
template <size_t I, class T> class variant_alternative<I, const volatile T>;
- Let
VA
denotevariant_alternative<I, T>
of the cv-unqualified typeT
. Then each of the three templates shall meet theTransformationTrait
requirements (20.10.1) with a member typedeftype
that names the following type:
- for the first specialization,
add_const_t<VA::type>
,- for the second specialization,
add_volatile_t<VA::type>
, and- for the third specialization,
add_cv_t<VA::type>
.
variant_alternative<I, variant<Types...>>::type
- Requires:
I < sizeof...(Types)
- Value:
- The type
T_I
.?.5 In-place construction [variant.in_place]
template <class T> struct in_place_type_t{ explicit in_place_type_t() = default; };
template <class T> constexpr in_place_type_t<T> in_place_type{};
template <size_t I> struct in_place_index_t{ explicit in_place_index_t() = default; };
template <size_t I> constexpr in_place_index_t<I> in_place_index{};
Template specializations of
in_place_type_t
are empty structure types used as unique types to disambiguate constructor overloading. They signal (through the template parameter) the alternative to be constructed. Specifically,variant
has a constructor within_place_type_t<T>
as the first argument followed by an argument pack; this indicates thatT
should be constructed in-place (as if by a call to a placement new expression) with the forwarded argument pack as parameters. If avariant
'sTypes...
has multiple occurrences ofT
,in_place_index_t
shall be used.Template specializations of
in_place_index_t
are empty structure types used as unique types to disambiguate constructor overloading, and signaling (through the template parameter) the alternative to be constructed. Specifically,variant
has a constructor within_place_index_t<I>
as the first argument followed by an argument pack; this indicates thatT_I
should be constructed in-place (as if by a call to a placement new expression) with the forwarded argument pack as parameters.?.6 Value access [variant.get]
template <class T, class... Types> constexpr bool holds_alternative(const variant<Types...>& v) noexcept;
- Requires:
- The type
T
occurs exactly once inTypes...
. Otherwise, the program is ill-formed.- Returns:
true
ifindex()
is equal to the zero-based index ofT
inTypes...
.
template <size_t I, class... Types>
constexpr variant_alternative_t<I, variant<Types...>>& get(variant<Types...>& v);
template <size_t I, class... Types>
constexpr variant_alternative_t<I, variant<Types...>>&& get(variant<Types...>&& v);
template <size_t I, class... Types>
constexpr variant_alternative_t<I, variant<Types...>> const& get(const variant<Types...>& v);
template <size_t I, class... Types>
constexpr variant_alternative_t<I, variant<Types...>> const&& get(const variant<Types...>&& v);
- Requires:
I < sizeof...(Types)
, andT_I
is not (possibly cv-qualified)void
. Otherwise the program is ill-formed.- Effects:
- If
v.index()
isI
, returns a reference to the object stored in the variant. Otherwise, throws an exception of typebad_variant_access
.
template <class T, class... Types> constexpr T& get(variant<Types...>& v);
template <class T, class... Types> constexpr T&& get(variant<Types...>&& v);
template <class T, class... Types> constexpr const T& get(const variant<Types...>& v);
template <class T, class... Types> constexpr const T&& get(const variant<Types...>&& v);
- Requires:
- The type
T
occurs exactly once inTypes...
, andT
is not (possibly cv-qualified)void
. Otherwise, the program is ill-formed.- Effects:
- If
v
holds a value of typeT
, returns a reference to that value. Otherwise, throws an exception of typebad_variant_access
.
template <size_t I, class... Types>
constexpr add_pointer_t<variant_alternative_t<I, variant<Types...>>> get_if(variant<Types...>* v) noexcept;
template <size_t I, class... Types>
constexpr add_pointer_t<const variant_alternative_t<I, variant<Types...>>> get_if(const variant<Types...>* v) noexcept;
- Requires:
I < sizeof...(Types)
andT_I
is not (possibly cv-qualified)void
; otherwise the program is ill-formed.- Returns:
- A pointer to the value stored in the variant, if
v != nullptr
andv->index() == I
. Otherwise, returnsnullptr
.
template <class T, class... Types>
constexpr add_pointer_t<T> get_if(variant<Types...>* v) noexcept;
template <class T, class... Types>
constexpr add_pointer_t<const T> get_if(const variant<Types...>* v) noexcept;
- Requires:
- The type
T
occurs exactly once inTypes...
, andT
is not (possibly cv-qualified)void
. Otherwise, the program is ill-formed.- Effects:
- Equivalent to
return get_if<i>(v)
withi
being the zero-based index ofT
inTypes...
.?.7 Relational operators [variant.relops]
template <class... Types> constexpr bool operator==(const variant<Types...>& v, const variant<Types...>& w);
- Requires:
get<i>(v) == get<i>(w)
is a valid expression returning a type that is convertible tobool
, for alli
.- Effects:
- Equivalent to
return (v.valueless_by_exception() && w.valueless_by_exception()) || (v.index() == w.index() && get<i>(v) == get<i>(w))
withi
beingv.index()
.
template <class... Types> constexpr bool operator!=(const variant<Types...>& v, const variant<Types...>& w);
- Effects:
- Equivalent to
return
!(v == w)
.
template <class... Types> constexpr bool operator<(const variant<Types...>& v, const variant<Types...>& w);
- Requires:
get<i>(v) < get<i>(w)
is a valid expression returning a type that is convertible tobool
, for alli
.- Effects:
- Equivalent to
return (v.index() < w.index()) || (v.index() == w.index() && !v.valueless_by_exception() && get<i>(v) < get<i>(w))
withi
beingv.index()
.
template <class... Types> constexpr bool operator>(const variant<Types...>& v, const variant<Types...>& w);
- Effects:
- Equivalent to
return
w < v
.
template <class... Types> constexpr bool operator<=(const variant<Types...>& v, const variant<Types...>& w);
- Effects:
- Equivalent to
return
!(v > w)
.
template <class... Types> constexpr bool operator>=(const variant<Types...>& v, const variant<Types...>& w);
- Effects:
- Equivalent to
return
!(v < w)
.?.8 Visitation [variant.visit]
template <class Visitor, class... Variants>
constexpr see below visit(Visitor&& vis, Variants&&... vars);
- Requires:
- The expression in the Effects element shall be a valid expression of the same type and value category, for all combinations of alternative types of all variants. Otherwise, the program is ill-formed.
- Effects:
- Let
is...
bevars.index()...
. ReturnsINVOKE(forward<Visitor>(vis), get<is>(forward<Variants>(vars))...);
.- Remarks:
- The return type is the common type of all possible
INVOKE
expressions of the Effects element.- Throws:
bad_variant_access
if anyvariant
invars
isvalueless_by_exception()
.- Complexity:
- For
sizeof...(Variants) <= 1
, the invocation of the callable object is implemented in constant time, i.e. it does not depend onsizeof...(Types)
. Forsizeof...(Variants) > 1
, the invocation of the callable object has no complexity requirements.?.9 Class
monostate
[variant.monostate]struct monostate{};
The class
monostate
can serve as a first alternative type for avariant
to make thevariant
type default constructible.?.10
monostate
relational operators [variant.monostate.relops]
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; }
constexpr bool operator==(monostate, monostate) noexcept { return true; }
constexpr bool operator!=(monostate, monostate) noexcept { return false; }
- [Note:
monostate
object have only a single state; they thus always compare equal.— end note]?.11 Specialized algorithms [variant.specalg]
template <class... Types> void swap(variant<Types...>& v, variant<Types...>& w) noexcept(see below);
- Effects:
- Equivalent to
v.swap(w)
.- Remarks:
- The expression inside
noexcept
is equivalent tonoexcept(v.swap(w))
.?.12 Class
bad_variant_access
[variant.bad_variant_access]class bad_variant_access : public exception { public: bad_variant_access() noexcept; virtual const char* what() const noexcept; };
Objects of type
bad_variant_access
are thrown to report invalid accesses to the value of avariant
object.
bad_variant_access() noexcept;
- Effects:
- Constructs a
bad_variant_access
object.
const char* what() const noexcept override;
- Returns:
- an implementation-defined NTBS.
?.13 Hash support [variant.hash]
template <class... Types> struct hash<variant<Types...>>;
The template specialization
hash<T>
shall meet the requirements of class templatehash
(C++14 §20.9.13) for allT
inTypes...
. The template specializationhash<variant<Types...>>
shall meet the requirements of class templatehash
.
template <> struct hash<monostate>;
The template specialization
hash<monostate>
shall meet the requirements of class templatehash
.?.14 Allocator-related traits [variant.traits]
template <class... Types, class Alloc>
struct uses_allocator<variant<Types...>, Alloc> : true_type { };
- Requires:
Alloc
shall be anAllocator
(17.6.3.5).- [Note:
- Specialization of this trait informs other library components that
variant
can be constructed with an allocator, even though it does not have a nestedallocator_type
. — end note]
A variant has proven to be a useful tool. This paper proposes the necessary ingredients.
Thank you, Nevin ":-)" Liber, for bringing sanity to this proposal. Agustín K-ballo Bergé and Antony Polukhin provided very valuable feedback, criticism and suggestions. Thanks also to Vincenzo Innocente and Philippe Canal for their comments.
1. Working Draft, Technical Specification on C++ Extensions for Library Fundamentals. N4335
2. Improving pair and tuple, revision 2. N4064