Document Number: | P0088R2, ISO/IEC JTC1 SC22 WG21 |
Audience: | LWG |
Date: | 2016-03-21 |
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
.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. Blue markup indicates changes to the previous revision, with insertions and deletions signaled in an obvious way.
The definition of3.2.1 Header <experimental/tuple> synopsis #include <tuple> namespace std { namespace experimental { inline namespace fundamentals_vXXXX { // See C++14 §20.4.2.5, tuple helper classes template <class T> constexpr size_t tuple_size_v = tuple_size<T>::value; // 3.2.2, tuple_find static constexpr size_t tuple_not_found = -1; } // namespace fundamentals_vXXXX } // namespace experimental } // namespace std
tuple_not_found
shall belong to [tuple.helper]
; it shall not get a new section name.
Insert a new element in Table 1, C++ library headers of [general.namespaces], named <
.experimental/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
<
synopsis [variant.synopsis]experimental/variant>namespace std {
namespace experimental { inline namespace fundamentals_vXXXX {// ?.3, variant of value types template <class... Types> class variant; // ?.4, 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{}; // ?.5, Value access template <class T, class... Types> constexpr bool holds_alternative(const variant<Types...>&) noexcept; template <size_t I, class... Types> constexprtuple_element_tvariant_alternative_t<I, variant<Types...>>& get(variant<Types...>&); template <size_t I, class... Types> constexprtuple_element_tvariant_alternative_t<I, variant<Types...>>&& get(variant<Types...>&&); template <size_t I, class... Types> constexprtuple_element_tvariant_alternative_t<I, variant<Types...>> const& get(const variant<Types...>&); template <size_t I, class... Types> constexprtuple_element_tvariant_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<tuple_element_tvariant_alternative_t<I, variant<Types...>>> get_if(variant<Types...>*) noexcept; template <size_t I, class... Types> constexpr add_pointer_t<consttuple_element_tvariant_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; // ?.6, 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...>&); // ?.7, Visitation template <class Visitor, class... Variants> constexpr see below visit(Visitor&&, Variants&&...); // ?.8, Class monostate struct monostate; // ?.9, 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; // ?.10, Specialized algorithms template <class... Types> void swap(variant<Types...>&, variant<Types...>&) noexcept(see below); // ?.11, class bad_variant_access class bad_variant_access;} // namespace fundamentals_vXXXX } // namespace experimental// ?.12,tupleinterfacevariant 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> structtuple_sizevariant_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> structtuple_elementvariant_alternative<I, variant<Types...>>; static constexpr size_t variant_npos = -1; // ?.13, Hash support template <class T> struct hash; template <class... Types> struct hash<experimental::variant<Types...>>; template <> struct hash<experimental::monostate>; // ?.14, Allocator-related traits template <class T, class Alloc> struct uses_allocator; template <class... Types, class Alloc> struct uses_allocator<experimental::variant<Types...>, Alloc>; } // namespace stdIncluding this header also makes the following declarations from [tuple.helper] available:
template <class T> class tuple_size<const T>; template <class T> class tuple_size<volatile T>; template <class T> class tuple_size<const volatile T>; template <size_t I, class T> class tuple_element<I, const T>; template <size_t I, class T> class tuple_element<I, volatile T>; template <size_t I, class T> class tuple_element<I, const volatile T>; static constexpr size_t tuple_not_found = -1;
?.3
variant
of value types [variant.variant]namespace std {
namespace experimental { inline namespace fundamentals_vXXXX {template <class... Types> class variant { public: // ?.3.1 Constructors constexpr variant() noexcept(see below); variant(const variant&)noexcept(see below); 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&a, const variant&); template <class Alloc> variant(allocator_arg_t, const Alloc&a, 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);private: aligned_storage<Unspecified>::type storage; // exposition only size_t value_type_index; // exposition only};} // namespace fundamentals_vXXXX } // namespace experimental} // namespace stdAny 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 areference_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 valuefrom the value contained inwithw
get<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 withstd::forward<T_j>(get<j>(w))
get<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
.Let
template <class T> constexpr variant(T&& t) noexcept(see below);
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 regular overload resolution for the expressionFUN(std::forward<T>(t))
defines the alternativeT_j
which is the type of the contained value after construction.
Requires:Builds an imaginary functionFUN(T_i)
for each alternative typeT_i
. The expressionFUN(std::forward<T>(t))
must be valid according to regular overload resolution, otherwise the program is ill-formed. The selected functionFUN(T_j)
defines the alternativeT_j
that will be activated by the call to this constructorwhich is the type of the contained value after construction. [Note:is ill-formed, as both alternative types have an equally viable constructor for the argument. — end note]variant<string, string> v("abc");
- Effects:
- Initializes
*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)
.- Postconditions:
holds_alternative<T_j>(*this)
istrue
, with.T_j
selected by the imaginary function overload resolution described above- 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) isvalid according to regular overload resolutionwell 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
as if constructing an objectof typeT
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()
.
- 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 constructor (withj
beingrhs.index()
),*this
will remain unchanged.- If an exception is thrown during the call to
T_j
's move constructor, thevariant
will hold no value.- 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 instd::forward<T_j>(get<j>(rhs))
*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>(rhs) withstd::forward<T_j>(get<j>(rhs))
j
beingrhs.index()
.
If an exception is thrown during the call toT_j
's move constructorion (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
.- 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 constructorion (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
.Let
template <class T> variant& operator=(T&& t) noexcept(see below);
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 regular overload resolution for the expressionFUN(std::forward<T>(t))
defines the alternativeT_j
which is the type of the contained value after assignment.
Requires:Builds an imaginary functionFUN(T_i)
for each alternative typeT_i
. The expressionFUN(std::forward<T>(t))
must be valid according to regular overload resolution, otherwise the program is ill-formed. The selected functionFUN(T_j)
defines the alternativeT_j
that will be activated by the assignmentwhich is the type of the contained value after assignment. [Note:is ill-formed, as both alternative types have an equally viable constructor for the argument. — end note]variant<string, string> v; v = "abc";
- Effects:
No effect ifIfdecay_t<T>(t)
isvoid
.*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)
.
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
objectwillmight not hold a value.- 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) isvalid according to regular overload resolutionwell 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_
ij&, T&&> && is_nothrow_constructible_v<T_ij, T&&>for all. If an exception is thrown during the assignment ofi
std::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
objectwillmight not hold a value.?.3.4 Modifiers [variant.mod]
template <class T, class... Args> void emplace(Args&&... args);
- Effects:
Destroys the currently contained value ifEquivalent tovalueless_by_exception()
isfalse
. Then direct-initializes the contained value as if constructing a value of typeT
with the argumentsstd::forward<Args>(args)...
.emplace<I>(std::forward<Args>(args)...)
whereI
is the zero-based index ofT
inTypes...
.If an exception is thrown during the initialization of the contained value, thevariant
willmight not hold a value.- Postcondition:
holds_alternative<T>(*this)
istrue
.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, Args&&...>
istrue
, andT
occurs exactly once inTypes...
.
template <class T, class U, class... Args> void emplace(initializer_list<U> il, Args&&... args);
- Effects:
Destroys the currently contained value ifEquivalent tovalueless_by_exception()
isfalse
. Then direct-initializes the contained value as if constructing an object of typeT
with the argumentsil, std::forward<Args>(args)...
.emplace<I>(il, std::forward<Args>(args)...)
whereI
is the zero-based index ofT
inTypes...
.If an exception is thrown during the initialization of the contained value, thevariant
willmight not hold a value.- Postcondition:
holds_alternative<T>(*this)
istrue
.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, 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)...
.If an exception is thrown during the initialization of the contained value, thevariant
willmight not hold a value.- 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
willmight 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)...
.If an exception is thrown during the initialization of the contained value, thevariant
willmight not hold a value.- 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
willmight 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 valuewhether the. [Note: Avariant
holds a value (returnsfalse
)variant
willmight 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 thecurrently activealternative 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
.
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.- Throws:
- Any exception thrown by
swap(get<i>(*this), get<i>(rhs))
withi
beingindex()
orvariant
'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
all alternative types satisfy the. If an exception is thrown during the call to functionSwappable
requirements (17.6.3.2) with the corresponding alternative inrhs
swap(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.?.4 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
must 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.?.5 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>
constexprtuple_element_tvariant_alternative_t<I, variant<Types...>>& get(variant<Types...>& v);
template <size_t I, class... Types>
constexprtuple_element_tvariant_alternative_t<I, variant<Types...>>&& get(variant<Types...>&& v);// Note A
template <size_t I, class... Types>
constexprtuple_element_tvariant_alternative_t<I, variant<Types...>> const& get(const variant<Types...>& v);// Note B
template <size_t I, class... Types>
constexprtuple_element_tvariant_alternative_t<I, variant<Types...>> const&& get(const variant<Types...>&& v);// Notes A and B
- 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
.[Note A:ifT_I
is some reference typeX&
, the return type isX&
, notX&&
. However, if the element type is a non-reference typeT
, the return type isT&&
. — end note][Note B:Constness is shallow. IfT_I
is some reference typeX&
, the return type isX&
, notconst X&
. However, if the element type is non-reference typeT
, the return type isconst T&
. This is consistent with how constness is defined to work for member variables of reference type. — end note]
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<tuple_element_tvariant_alternative_t<I, variant<Types...>>> get_if(variant<Types...>* v) noexcept;
template <size_t I, class... Types>
constexpr add_pointer_t<consttuple_element_tvariant_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...
.?.6 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()
, otherwise.false
template <class... Types> constexpr bool operator!=(const variant<Types...>& v, const variant<Types...>& w);
ReturnsEffects:- 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()
, otherwise.false
template <class... Types> constexpr bool operator>(const variant<Types...>& v, const variant<Types...>& w);
ReturnsEffects:- Equivalent to
return
w < v
.
template <class... Types> constexpr bool operator<=(const variant<Types...>& v, const variant<Types...>& w);
ReturnsEffects:- Equivalent to
return
!(v > w)
.
template <class... Types> constexpr bool operator>=(const variant<Types...>& v, const variant<Types...>& w);
ReturnsEffects:- Equivalent to
return
!(v < w)
.?.7 Visitation [variant.visit]
template <class Visitor, class... Variants>
constexpr see below visit(Visitor&& vis, Variants&&... vars);
- Requires:
- The expression in the Effects element must 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 possiblecommon_type
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 must be implemented in constant time, i.e. it must not depend onsizeof...(Types)
. Forsizeof...(Variants) > 1
, the invocation of the callable object has no complexity requirements.?.8 Class
monostate
[variant.monostate]struct monostate{};
The class
monostate
can serve as a first alternative type for avariant
to make thevariant
type default constructible.?.9
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]?.10 Specialized algorithms [variant.specalg]
template <class... Types> void swap(variant<Types...>& v, variant<Types...>& w) noexcept(see below);
- Effects:
CallsEquivalent tov.swap(w)
.- Remarks:
- The expression inside
noexcept
is equivalent tonoexcept(v.swap(w))
.?.11 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.Postconditions:what() returns an implementation-defined NTBS.
const char* what() const noexcept override;
- Returns:
- an implementation-defined NTBS.
?.12
tuple
interface to class templatevariant
variant
helper classes[variant.tuple][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>
structtuple_sizevariant_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>
.
tuple_elementvariant_alternative<I, variant<Types...>>::type
- Requires:
I < sizeof...(Types)
- Value:
- The type
T_I
.?.13 Hash support [variant.hash]
template <class... Types> struct hash<
experimental::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<
shall meet the requirements of class templateexperimental::variant<Types...>>hash
.
template <> struct hash<
experimental::monostate>;The template specialization
hash<
shall meet the requirements of class templateexperimental::monostate>hash
.?.14 Allocator-related traits [variant.traits]
template <class... Types, class Alloc>
struct uses_allocator<experimental::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