| 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::optionalvariant<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 variants 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
variantof 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 stdAny instance of
variantat any given time either holds a value of one of its alternative types, or it holds no value. When an instance ofvariantholds a value of alternative typeT, it means that a value of typeT, referred to as thevariantobject's contained value, is allocated within the storage of thevariantobject. 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 thevariantstorage 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
ibe in the range[0,sizeof...(Types)), andT_ibe theith type inTypes....
constexpr variant() noexcept(see below);
- Effects:
- Constructs a
variantholding a value-initialized value of typeT_0.- Postconditions:
valueless_by_exception()isfalseandindex()is0.- Throws:
- Any exception thrown by the value initialization of
T_0.- Remarks:
- This function shall be
constexprif and only if the value initialization of the alternative typeT_0would satisfy the requirements for aconstexprfunction. The expression insidenoexceptis 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
wholds a value, initializes thevariantto hold the same alternative aswand direct-initializes the contained value withget<j>(w), wherejisw.index(). Otherwise, initializes thevariantto not hold a value.- Throws:
- Any exception thrown by direct-initializing any
T_ifor alli.- Remarks:
- This function shall not participate in overload resolution unless
is_copy_constructible_v<T_i>istruefor alli.
variant(variant&& w) noexcept(see below);
- Effects:
- If
wholds a value, initializes thevariantto hold the same alternative aswand direct-initializes the contained value withget<j>(std::move(w)), wherejisw.index(). Otherwise, initializes thevariantto not hold a value.- Throws:
- Any exception thrown by move-constructing any
T_ifor alli.- Remarks:
- The expression inside
noexceptis 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>istruefor alli.
template <class T> constexpr variant(T&& t) noexcept(see below);
- Let
T_jbe 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_jwhich is the type of the contained value after construction.- Effects:
- Initializes
*thisto hold the alternative typeT_jand 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))(withFUNbeing 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");noexceptis equivalent tois_nothrow_constructible_v<T_j, T>. IfT_j's selected constructor is aconstexprconstructor, this constructor shall be aconstexprconstructor.
template <class T, class... Args> constexpr explicit variant(in_place_type_t<T>, Args&&... args);
- Effects:
- Initializes the contained value of type
Twith 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
TinTypes...andis_constructible_v<T, Args...>istrue. IfT's selected constructor is aconstexprconstructor, this constructor shall be aconstexprconstructor.
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
Twith 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
TinTypes...andis_constructible_v<T, initializer_list<U>&, Args...>istrue. IfT's selected constructor is aconstexprconstructor, this constructor shall be aconstexprconstructor.
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_Iwith 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
Iis less thansizeof...(Types)andis_constructible_v<T_I, Args...>istrue. IfT_I's selected constructor is aconstexprconstructor, this constructor shall be aconstexprconstructor.
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_Iwith the argumentsil, std::forward<Args>(args)....- Postcondition:
index()isI.- Remarks:
- This function shall not participate in overload resolution unless
Iis less thansizeof...(Types)andis_constructible_v<T_I, initializer_list<U>&, Args...>istrue. IfT_I's selected constructor is aconstexprconstructor, this constructor shall be aconstexprconstructor.
// 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:
Allocshall 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> == truefor allT_ithen this destructor shall be a trivial destructor.?.3.3 Assignment [variant.assign]
variant& operator=(const variant& rhs);
- Effects:
- If neither
*thisnorrhsholds a value, there is no effect. Otherwise,- if
*thisholds a value butrhsdoes not, destroys the value contained in*thisand sets*thisto not hold a value. Otherwise,- if
index() == rhs.index(), assigns the value contained inrhsto the value contained in*this. Otherwise,- copies the value contained in
rhsto a temporary, then destroys any value contained in*this. Sets*thisto hold the same alternative index asrhsand initializes the value contained in*thisas if direct-non-list-initializing an object of typeT_jwithstd::forward<T_j>(TMP), withTMPbeing the temporary andjbeingrhs.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>istruefor 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 (withjbeingrhs.index()),*thiswill remain unchanged.- If an exception is thrown during the call to
T_j's move construction, thevariantwill hold no value.
variant& operator=(variant&& rhs) noexcept(see below);
- Effects:
- If neither
*thisnorrhsholds a value, there is no effect. Otherwise,- if
*thisholds a value butrhsdoes not, destroys the value contained in*thisand sets*thisto not hold a value. Otherwise,- if
index() == rhs.index(), assignsget<j>(std::move(rhs))to the value contained in*this, withjbeingindex(). Otherwise,- destroys any value contained in
*this. Sets*thisto hold the same alternative index asrhsand initializes the value contained in*thisas if direct-non-list-initializing an object of typeT_jwithget<j>(std::move(rhs))withjbeingrhs.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>istruefor alli. The expression insidenoexceptis 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 (withjbeingrhs.index()), thevariantwill 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_jbe 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_jwhich is the type of the contained value after assignment.- Effects:
- If
*thisholds aT_j, assignsstd::forward<T>(t)to the value contained in*this. Otherwise, destroys any value contained in*this, sets*thisto hold the alternative typeT_jas 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_jselected 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))(withFUNbeing 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";noexceptis 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 andtare 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, thevariantobject 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)...)whereIis the zero-based index ofTinTypes....- Remarks:
- This function shall not participate in overload resolution unless
is_constructible_v<T, Args...>istrue, andToccurs 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)...)whereIis the zero-based index ofTinTypes....- Remarks:
- This function shall not participate in overload resolution unless
is_constructible_v<T, initializer_list<U>&, Args...>istrue, andToccurs 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_Iwith 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, thevariantmight 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_Iwith 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, thevariantmight not hold a value.?.3.5 Value status [variant.status]
constexpr bool valueless_by_exception() const noexcept;
- Effects:
- Returns
falseif and only if thevariantholds a value. [Note: Avariantmight 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))whereiisindex(). Otherwise,- exchanges values of
rhsand*this.- Throws:
- Any exception thrown by
swap(get<i>(*this), get<i>(rhs))withibeingindex()andvariant's move constructor and move assignment operator.- Remarks:
- This function shall not participate in overload resolution unless
is_swappable_v<T_i>istruefor alli. If an exception is thrown during the call to functionswap(get<i>(*this), get<i>(rhs)), the states of the contained values of*thisand ofrhsare determined by the exception safety guarantee ofswapfor lvalues ofT_iwithibeingindex(). If an exception is thrown during the exchange of the values of*thisandrhs, the states of the values of*thisand ofrhsare determined by the exception safety guarantee ofvariant's move constructor and move assignment operator. The expression insidenoexceptis equivalent to the logical AND ofis_nothrow_move_constructible_v<T_i> && is_nothrow_swappable_v<T_i>for alli.?.4
varianthelper classes [variant.helper]
template <class T> struct variant_size;
- Remarks:
- All specializations of
variant_size<T>shall meet theUnaryTypeTraitrequirements (20.10.1) with aBaseCharacteristicofintegral_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
VSdenotevariant_size<T>of the cv-unqualified typeT. Then each of the three templates shall meet theUnaryTypeTraitrequirements (20.10.1) with aBaseCharacteristicofintegral_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
VAdenotevariant_alternative<I, T>of the cv-unqualified typeT. Then each of the three templates shall meet theTransformationTraitrequirements (20.10.1) with a member typedeftypethat 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_tare empty structure types used as unique types to disambiguate constructor overloading. They signal (through the template parameter) the alternative to be constructed. Specifically,varianthas a constructor within_place_type_t<T>as the first argument followed by an argument pack; this indicates thatTshould 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_tshall be used.Template specializations of
in_place_index_tare empty structure types used as unique types to disambiguate constructor overloading, and signaling (through the template parameter) the alternative to be constructed. Specifically,varianthas a constructor within_place_index_t<I>as the first argument followed by an argument pack; this indicates thatT_Ishould 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
Toccurs exactly once inTypes.... Otherwise, the program is ill-formed.- Returns:
trueifindex()is equal to the zero-based index ofTinTypes....
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_Iis 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
Toccurs exactly once inTypes..., andTis not (possibly cv-qualified)void. Otherwise, the program is ill-formed.- Effects:
- If
vholds 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_Iis not (possibly cv-qualified)void; otherwise the program is ill-formed.- Returns:
- A pointer to the value stored in the variant, if
v != nullptrandv->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
Toccurs exactly once inTypes..., andTis not (possibly cv-qualified)void. Otherwise, the program is ill-formed.- Effects:
- Equivalent to
return get_if<i>(v)withibeing the zero-based index ofTinTypes....?.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))withibeingv.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))withibeingv.index().
template <class... Types> constexpr bool operator>(const variant<Types...>& v, const variant<Types...>& w);
- Effects:
- Equivalent to
returnw < 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
INVOKEexpressions of the Effects element.- Throws:
bad_variant_accessif anyvariantinvarsisvalueless_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
monostatecan serve as a first alternative type for avariantto make thevarianttype default constructible.?.10
monostaterelational 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:
monostateobject 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
noexceptis 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_accessare thrown to report invalid accesses to the value of avariantobject.
bad_variant_access() noexcept;
- Effects:
- Constructs a
bad_variant_accessobject.
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 allTinTypes.... 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:
Allocshall be anAllocator(17.6.3.5).- [Note:
- Specialization of this trait informs other library components that
variantcan 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