Document Number: | P0088R1, ISO/IEC JTC1 SC22 WG21 |
Audience: | LWG |
Date: | 2016-02-13 |
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++ needs a type-safe union; here is a proposal. It 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,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 tuple_not_found
; 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 Fundamentals TS. Grayish background indicates proposed wording.
Insert a new element in Table 1, C++ library headers of [general.namespaces], named <experimental/variant>
.
? Variants [variant]
?.1 In general [variant.general]
Variant objects hold and manage 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
<experimental/variant>
synopsis [variant.synopsis]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{}; template <class T> constexpr in_place_type_t<T> in_place_type{}; template <size_t I> struct in_place_index_t{}; 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> constexpr tuple_element_t<I, variant<Types...>>& get(variant<Types...>&); template <size_t I, class... Types> constexpr tuple_element_t<I, variant<Types...>>&& get(variant<Types...>&&); template <size_t I, class... Types> constexpr tuple_element_t<I, variant<Types...>> const& get(const variant<Types...>&); template <size_t I, class... Types> constexpr tuple_element_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_t<I, variant<Types...>>> get_if(variant<Types...>*) noexcept; template <size_t I, class... Types> constexpr add_pointer_t<const tuple_element_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, tuple interface template <class... Types> struct tuple_size<variant<Types...>>; template <size_t I, class... Types> struct tuple_element<I, variant<Types...>>; // ?.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 R, class Alloc> struct uses_allocator<experimental::variant<R>, Alloc>; } // namespace std
Including this header also makes the following templates 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>;
?.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&&); 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 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 object types, possibly cv-qualified, 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
noexcept
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 from the value contained inw
. 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))
, 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);
- Requires:
- Builds an imaginary function
FUN(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 constructor. [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
, withT_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
. 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 object 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 occurrences 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
hold a value, 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
.
variant& operator=(variant&& rhs) noexcept(see below);
- Effects:
- If neither
*this
norrhs
hold a value, 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()
, assignsstd::forward<T_j>(get<j>(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
withstd::forward<T_j>(get<j>(rhs))
withj
beingrhs.index()
.
If an exception is thrown during the call toT_j
's move constructor (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
.
template <class T> variant& operator=(T&& t) noexcept(see below);
- Requires:
- Builds an imaginary function
FUN(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 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 if
decay_t<T>(t)
isvoid
. 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)
.
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 will 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
. The expression insidenoexcept
is equivalent to:is_nothrow_assignable_v<T_i, T&&> && is_nothrow_constructible_v<T_i, T&&>
for alli
.?.3.4 Modifiers [variant.mod]
template <class T, class... Args> void emplace(Args&&... args);
- Effects:
- Destroys the currently contained value if
valueless_by_exception()
isfalse
. Then direct-initializes the contained value as if constructing a value of typeT
with the argumentsstd::forward<Args>(args)...
. If an exception is thrown during the initialization of the contained value, thevariant
will 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 if
valueless_by_exception()
isfalse
. Then direct-initializes the contained value as if constructing an object of typeT
with the argumentsil, std::forward<Args>(args)...
. If an exception is thrown during the initialization of the contained value, thevariant
will 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
will 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
.
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
will 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
.?.3.5 Value status [variant.status]
constexpr bool valueless_by_exception() const noexcept;
- Effects:
- Returns whether the
variant
holds a value (returnsfalse
). [Note: Avariant
will 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
, returnstuple_not_found
. Otherwise, returns the index of the currently active alternative.?.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))
withi
beingindex()
, otherwise- exchanges values of
rhs
and*this
.
If an exception is thrown during the call to functionswap(get<i>(*this), get<i>(rhs))
, the state of the value ofthis
and ofrhs
is 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 state of the value ofthis
and ofrhs
is determined by the exception safety guarantee ofvariant
's move constructor and assignment operator.- Throws:
- Any exceptions that is thrown by
swap(get<i>(*this), get<i>(rhs))
withi
beingindex()
orvariant
's move constructor and assignment operator.- Remarks:
- This function shall not participate in overload resolution unless all alternative types satisfy the
Swappable
requirements (17.6.3.2) with the corresponding alternative inrhs
.?.4 In-place construction [variant.emplaced]
template <class T> struct in_place_type_t{};
template <class T> constexpr in_place_type_t<T> in_place_type{};
template <size_t I> struct in_place_index_t{};
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 and function 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 and function 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:
- the zero-based index of
T
inTypes...
.
template <size_t I, class... Types>
constexpr tuple_element_t<I, variant<Types...>>& get(variant<Types...>& v);
template <size_t I, class... Types>
constexpr tuple_element_t<I, variant<Types...>>&& get(variant<Types...>&& v);// Note A
template <size_t I, class... Types>
constexpr tuple_element_t<I, variant<Types...>> const& get(const variant<Types...>& v); // Note B
template <size_t I, class... Types>
constexpr tuple_element_t<I, variant<Types...>> const&& get(const variant<Types...>&& v); // Notes A and B
- Requires:
I < sizeof...(Types)
, andT_I
is not a possibly cv-qualifiedvoid
. 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:
- if
T_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. If
T_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 a possibly cv-qualifiedvoid
. 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_t<I, variant<Types...>>> get_if(variant<Types...>* v) noexcept;
template <size_t I, class... Types>
constexpr add_pointer_t<const tuple_element_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 a possibly cv-qualifiedvoid
. Otherwise, the program is ill-formed.- Effects:
- Equivalent to
return get_if<T_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()
, otherwisefalse
.
template <class... Types> constexpr bool operator!=(const variant<Types...>& v, const variant<Types...>& w);
- Returns:
!(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
.- 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()
, otherwisefalse
.
template <class... Types> constexpr bool operator>(const variant<Types...>& v, const variant<Types...>& w);
- Returns:
w < v
.
template <class... Types> constexpr bool operator<=(const variant<Types...>& v, const variant<Types...>& w);
- Returns:
!(v > w)
.
template <class... Types> constexpr bool operator>=(const variant<Types...>& v, const variant<Types...>& w);
- Returns:
!(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, for all combinations of alternative types of all variants.
- Effects:
- Let
is...
bevars.index()...
. ReturnsINVOKE(forward<Visitor>(vis), get<is>(forward<Variants>(vars))...);
.- Remarks:
- The return type is the
common_type
of all possibleINVOKE
expressions of the Effects element.- Throws:
bad_variant_access
if anyvariant
invars
isvalueless_by_exception()
.- Complexity:
- For
sizeof...(Variants)
being1
, the invocation of the callable must be implemented in constant time, i.e. it must not depend onsizeof...(Types)
. Forsizeof...(Variants)
greater1
, the invocation of the callable 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; }
- Effects:
- Compare two
monostate
objects.monostate
object have only a single state; they thus always compare equal.?.10 Specialized algorithms [variant.specalg]
template <class... Types> void swap(variant<Types...>& v, variant<Types...>& w) noexcept(see below);
- Effects:
- Calls
v.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; const char* what() const noexcept override; };
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.tuple]
template <class... Types>
struct tuple_size<variant<Types...>>
: integral_constant<size_t, sizeof...(Types)> { };
tuple_element<I, variant<Types...>>::type
- 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<variant<Types...>>
shall meet the requirements of class templatehash
.
template <> struct hash<experimental::monostate>;
The template specialization
hash<monostate>
shall meet the requirements of class templatehash
.?.14 Allocator-related traits [variant.traits]
template <class R, class Alloc>
struct uses_allocator<experimental::variant<R>, 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