ISO/IEC JTC1 SC22 WG21 N3793 2013-10-03
Fernando Cacciola, fernando.cacciola@gmail.com Andrzej Krzemieński, akrzemi1@gmail.comClass template optional<T>
proposed here is a type that may or may not store a value of type T
in its storage space. Its interface allows to query if a value of type T
is currently stored, and if so, to access it. The interface is based on Fernando Cacciola's Boost.Optional library[2], shipping since March, 2003, and widely used. It requires no changes to core language, and breaks no existing code.
Changes since N3672
T
’'s operators < and == (agreed in Chicago meeting).std::experimental
and in header <experimental/optional>
.is_assignable
constraint in optional::op=(U&&)
).optional
declares and then does not define an operator<()
).optional
copy assignment operator).constexpr
specifier to constructor declarations to match synopsis and Remarks.U
(requested in Chicago meeting) a dangerous idea (it can silently turn explicit conversions into implicit ones).remove_reference
with decay
in the specification of the converting assignment.optional
.Changes since R4C
Changes since N3527:
emplace
to in_place
based on feedback from LEWG.op = {}
syntax and explained why we had to apply some tricks to support it. See The op = {}
syntax.swap
that T
be MoveAssignamle. See Requirements for swap
.<utility>
or <optional>
?.operator<
now uses less<T>
rather than T::operator<
. See Relational operators for rationale.optional
in this proposal and boost::optional
. See Comparison with Boost.Optional.experimental
, because we are not targetting a TS anymore.emplace
now returns void
— LWG recommendation.less<T>
in implementation — LWG recommendation.optional
) that Optional cannot be implemented with aligned_storage
.Changes since N3406=12-0096:
optional<T>
is hashable for hashable T
’s.get_value_or
is now optional
-specific member function, renamed to value_or
.value
— an alternative to operator*
that checks if the object is engaged.optional<T>
is a literal type.optional<T>
and T
are now allowed.Changes since N1878=05-0138:
nullopt
instead of nullptr
to indicate the 'disengaged' (uninitialized) optional.noexcept
, variadic templates, perfect forwarding, static initialization.This proposal depends on library proposal N3471: it requires that Standard Library components move
, forward
and member functions of initializer_list
are constexpr
. The paper has already been incorporated into the Working Draft of the Standard N3485. This proposal also depends on language proposal N2439 (Rvalue references for *this
). While this feature proposal has been incorporated into C++11, we are aware of only two compilers that implemented it: Clang and GCC 4.8.1. There is a risk that if compiler vendors do not implement it, they will also not be able to fully implement this proposal. In that case, the signature of member function optional<T>::value_or
from this proposal will need to be modified.
N3507 (A URI Library for C++) depends on this library.
This proposal basically tries to follow Boost.Optional's interface. Here we list the significant differences.
aspect | this proposal | Boost.Optional |
---|---|---|
Move semantics | yes | no |
noexcept | yes | no |
hash support | yes | no |
a throwing value accessor | yes | no |
literal type | partially | no |
in place construction |
| utility in_place_factory |
disengaged state tag | nullopt | none |
optional references | no (optionally) | yes |
conversion from optional<U> to optional<T> | no | yes |
dupplicated interface functions ( is_initialized , reset , get ) | no | yes |
explicit convert to ptr ( get_ptr ) | no | yes |
Since optional<T>
can be thought of as an "almost T
", one could expect that if the following works:
void fun(std::string s); fun("text");
the following should also work:
void gun(optional<std::string> s); gun("text");
Supporting this was also requested in Chicago meeting.
However, naively implementing a converting constructor would also enable a non-explicit converting constructor from any type U
to type optional<T>
for any type T
. This would turn some types that are explicitly constructible into optional types that are implicitly constructible. Consider:
void explicit_conv( int * ptr ) { unique_ptr<int> v = ptr; // ILLEGAL } void implicit_conv( int * ptr ) { optional<unique_ptr<int>> v = ptr; // LEGAL }
In order to make the former example work on the one hand and to prevent the problem with the latter example on the other, we considered a solution that could be informally called a conditionally-explicit converting constructor. We could achieve this by specifying two constructor templates with identical template and function parameters, one explicit and one non-explicit, and make them mutually exclusive by means of SFINAE:
template <class U> // enable_if: Constructible<T, U&&> && !Convertible<U&&, T> explicit optional<T>::optional(U&&); template <class U> // enable_if: Convertible<U&&, T> optional<T>::optional(U&&);
Such concept-like behaviour as used above can be implemented in C++ with type traits and enable_if
. It was noted, however, that the existence of such converting constructor would cause unexpected ambiguities in overload resolution. Consider the following scenario. We start from a working program:
// library void fun(string const& s); // usage fun("hello");
At some point we decide to add a second overload that accepts an optional string:
// library void fun(string const& s); void fun(optional<string> const& s); // new overload // usage fun("hello"); // ERROR: ambiguity
Does it make sense to add an overload for optional rather than substituting it for the original? It might be useful for performance reasons: if you already have string
it is cheaper to bind it directly to string const&
than to create a temporary optional object and trigger the copy constructor of string
:
// library void fun(optional<string> const& s); // only this fun // usage string s = "hello"; fun(s); // copy ctor invoked!
This example shows how an implicit conversion can cause an inadvertent and unexpected (potentially expensive) copy constructor. For this reason we do not propose a converting constructor from arbitrary type U
. (Although we do propose a converting constructor from T
.)
T
)An object of type T
is convertible to an engaged object of type optional<T>
:
optional<int> oi = 1; // works
This convenience feature is not strictly necessary because you can achieve the same effect by using tagged forwarding constructor:
optional<int> oi{in_place, 1};
If the latter appears too inconvenient, one can always use function make_optional
described below:
optional<int> oi = make_optional(1); auto oj = make_optional(1);
The implicit converting constructor comes in handy in case of optional function arguments:
void fun(std::string s, optional<int> oi = nullopt); fun("dog", 2); fun("dog"); fun("dog", nullopt); // just to be explicit
While being a nice convenience the converting constructor causes certain problems. It automatically implies mixed comparisons, and the latter are considered error-prone by many. For instance:
optional<bool> ob; assert (!ob == (ob == false)); // error
The expectation in assertion isn't always satisfied. Also, some people prefer a mixed comparison to be a type error -- a type-safety feature.
T
If T
is EqualityComparable
then (and only then) we expect optional<T>
to be EqualityComparable
.
For orderting predicates, we do not require that T
is LessThanComparable
. Instead we require that expression T{} < T{}
is valid and convertible to bool
. We consider requirements that require Strict Weak Ordering axioms too strict. less-than comparisons should still work if user uses T::operator<
for something tricky.
op = {}
syntaxWe put the extra requirements in the standardese to make sure that the following syntax works for resetting the optional:
op = {};
We consider that this will become a common idiom for resetting (putting into default-constructed state) values in C++. While you get that syntax for free for POD types, in optional
we have to take extra care to enable it; this is because optional
provides three assignment operators: copy/move assignment, assignment from T
and from nullopt_t
. If we just provided the "intuitive" declaration of assignment from const T&
and T&&
, the expression above would become ambiguous. The expression above is processed as:
op = DEDUCED{};
where DEDUCED
needs to be deduced from all available overloads. We would have two candidates: move assignment and assignemnt from T&&
, which would cause an ambiguity. Therefore, we require that the assignment from T&&
is declared in a more convoluted way — as a template:
template <class U> optional& optional<T>::operator=(U&&);
// enable if decay<U> == T
The additional requirement that decay<U> == T
says that the only valid instantiations from this template are these for const T&
and T&&
(and some other less relevant variations of references to T
). But it is still a template, and templates do not participate in the resolution of type DEDUCED
.
For the same reason, we require that tag nullopt_t
is not DefaultConstructible. Otherwise, because optional
provides an assignment from nullopt_t
, DEDUCED
might also have been deduced as nullopt_t
.
Note that it is not the only way to disengage an optional object. You can also use:
op = std::nullopt;
Optional does not allocate memory. So it can do without allocators. However, it can be useful in compound types like:
typedef vector< optional<vector<int, MyAlloc>>, MyAlloc>; MyVec; MyVec v{ v2, MyAlloc{} };
One could expect that the allocator argument is forwarded in this constructor call to the nested vectors that use the same allocator. Allocator support would enable this. std::tuple
offers this functionality.
Grayish background indicates the wording to be added. In fact we are only adding new wording (no deletions). Insertions and deletions are used to indicate changes from the previous proposed wording: N3672.
Insert a new subclause:
N.M.N [defns.direct-non-list-init]
dierect-non-list-initialization
A direct-initialization that is not list-initialization.
Insert a new paragraph.
X.Y Optional objects [optional]
X.Y.1 In general [optional.general]
This subclause describes class template
optional
that represents optional objects. An optional object for object types is an object that contains the storage for another object and manages the lifetime of this contained object, if any. The contained object may be initialized after the optional object has been initialized, and may be destroyed before the optional object has been destroyed. The initialization state of the contained object is tracked by the optional object.X.Y.2 Header <experimental/optional> synopsis [optional.synop]
namespace std { namespace experimental { // X.Y.4,optional
for object types template <class T> class optional; // X.Y.5, In-place construction struct in_place_t{}; constexpr in_place_t in_place{}; // X.Y.6, Disengaged state indicator struct nullopt_t{see below}; constexpr nullopt_t nullopt(unspecified); // X.Y.7, class bad_optional_access class bad_optional_access; // X.Y.8, Relational operators template <class T> constexpr bool operator==(const optional<T>&, const optional<T>&); template <class T> constexpr bool operator!=(const optional<T>&, const optional<T>&); template <class T> constexpr bool operator<(const optional<T>&, const optional<T>&); template <class T> constexpr bool operator>(const optional<T>&, const optional<T>&); template <class T> constexpr bool operator<=(const optional<T>&, const optional<T>&); template <class T> constexpr bool operator>=(const optional<T>&, const optional<T>&); // X.Y.9, Comparison withnullopt
template <class T> constexpr bool operator==(const optional<T>&, nullopt_t) noexcept; template <class T> constexpr bool operator==(nullopt_t, const optional<T>&) noexcept; template <class T> constexpr bool operator!=(const optional<T>&, nullopt_t) noexcept; template <class T> constexpr bool operator!=(nullopt_t, const optional<T>&) noexcept; template <class T> constexpr bool operator<(const optional<T>&, nullopt_t) noexcept; template <class T> constexpr bool operator<(nullopt_t, const optional<T>&) noexcept; template <class T> constexpr bool operator<=(const optional<T>&, nullopt_t) noexcept; template <class T> constexpr bool operator<=(nullopt_t, const optional<T>&) noexcept; template <class T> constexpr bool operator>(const optional<T>&, nullopt_t) noexcept; template <class T> constexpr bool operator>(nullopt_t, const optional<T>&) noexcept; template <class T> constexpr bool operator>=(const optional<T>&, nullopt_t) noexcept; template <class T> constexpr bool operator>=(nullopt_t, const optional<T>&) noexcept; // X.Y.10, Comparison with T template <class T> constexpr bool operator==(const optional<T>&, const T&); template <class T> constexpr bool operator==(const T&, const optional<T>&); template <class T> constexpr bool operator!=(const optional<T>&, const T&); template <class T> constexpr bool operator!=(const T&, const optional<T>&); template <class T> constexpr bool operator<(const optional<T>&, const T&); template <class T> constexpr bool operator<(const T&, const optional<T>&); template <class T> constexpr bool operator<=(const optional<T>&, const T&); template <class T> constexpr bool operator<=(const T&, const optional<T>&); template <class T> constexpr bool operator>(const optional<T>&, const T&); template <class T> constexpr bool operator>(const T&, const optional<T>&); template <class T> constexpr bool operator>=(const optional<T>&, const T&); template <class T> constexpr bool operator>=(const T&, const optional<T>&); // X.Y.11, Specialized algorithms template <class T> void swap(optional<T>&, optional<T>&) noexcept(see below); template <class T> constexpr optional<see below> make_optional(T&&); // X.Y.12, hash support template <class T> struct hash; template <class T> struct hash<optional<T>>; } // namespace experimental } // namespace stdA program that necessitates the instantiation of template
optional
for a reference type, or for possibly cv-qualified typesin_place_t
ornullopt_t
is ill-formed.X.Y.3 Definitions [optional.defs]
An instance of
optional<T>
is said to be disengaged ifit has been default constructed, constructed with or assigned with a value of typenullopt_t
, constructed with or assigned with a disengaged optional object of typeoptional<T>
- it default-initialized; or
- it is initialized with a value of type
nullopt_t
or with a disengaged optional object of typeoptional<T>
; or- a value of type
nullopt_t
or a disengaged optional object of typeoptional<T>
is assigned to it.An instance of
optional<T>
is said to be engaged if it is not disengaged.X.Y.4
optional
for object types [optional.object]namespace std { namespace experimental { template <class T> class optional { public: typedef T value_type; // X.Y.4.1, constructors constexpr optional() noexcept; constexpr optional(nullopt_t) noexcept; optional(const optional&); optional(optional&&) noexcept(see below); constexpr optional(const T&); constexpr optional(T&&); template <class... Args> constexpr explicit optional(in_place_t, Args&&...); template <class U, class... Args> constexpr explicit optional(in_place_t, initializer_list<U>, Args&&...); // X.Y.4.2, destructor ~optional(); // X.Y.4.3, assignment optional& operator=(nullopt_t) noexcept; optional& operator=(const optional&); optional& operator=(optional&&) noexcept(see below); template <class U> optional& operator=(U&&); template <class... Args> void emplace(Args&&...); template <class U, class... Args> void emplace(initializer_list<U>, Args&&...); // X.Y.4.4, swap void swap(optional&) noexcept(see below); // X.Y.4.5, observers constexpr T const* operator ->() const; T* operator ->(); constexpr T const& operator *() const; T& operator *(); constexpr explicit operator bool() const noexcept; constexpr T const& value() const; T& value(); template <class U> constexpr T value_or(U&&) const&; template <class U> T value_or(U&&) &&; private: bool init; // exposition only T* val; // exposition only }; } // namespace experimental } // namespace stdEngaged instances of
optional<T>
whereT
is of object type shall contain a value of typeT
within its own storage. This value is referred to as the contained value of the optional object. Implementations are not permitted to use additional storage, such as dynamic memory, to allocate its contained value. The contained value shall be allocated in a region of theoptional<T>
storage suitably aligned for the typeT
.Members
init
andval
are provided for exposition only. Implementations need not provide those members.init
indicates whether the optional object's contained value has been initialized (and not yet destroyed); wheninit
is trueval
points to(a possibly uninitialized)the contained value.
T
shall be an object type and shall satisfy the requirements ofDestructible
(Table 24).X.Y.4.1 Constructors [optional.object.ctor]
constexpr optional<T>::optional() noexcept;
constexpr optional<T>::optional(nullopt_t) noexcept;
- Postconditions:
*this
is disengaged.- Remarks:
No
contained value is initialized. For every object typeT
object referencedT
these constructors shall beconstexpr
constructors (7.1.5).
optional<T>::optional(const optional<T>& rhs);
- Requires:
is_copy_constructible<T>::value
istrue
.- Effects:
If
rhs
is engaged initializes the contained value as if direct-non-list-initializing an object of typeT
with the expression*rhs
.- Postconditions:
bool(rhs) == bool(*this)
.- Throws:
Any exception thrown by the selected constructor of
T
.
optional<T>::optional(optional<T> && rhs) noexcept(see below);
- Requires:
is_move_constructible<T>::value
istrue
.- Effects:
If
rhs
is engaged initializes the contained value as if direct-non-list-initializing an object of typeT
with the expressionstd::move(*rhs)
.bool(rhs)
is unchanged.- Postconditions:
bool(rhs) == bool(*this)
.- Throws:
Any exception thrown by the selected constructor of
T
.- Remarks:
The expression inside
noexcept
is equivalent to:is_nothrow_move_constructible<T>::value
constexpr optional<T>::optional(const T& v);
- Requires:
is_copy_constructible<T>::value
istrue
.- Effects:
Initializes the contained value as if direct-non-list-initializing an object of type
T
with the expressionv
.- Postconditions:
*this
is engaged.- Throws:
Any exception thrown by the selected constructor of
T
.- Remarks:
If
T
's selected constructor is aconstexpr
constructor, this constructor shall be aconstexpr
constructor.
constexpr optional<T>::optional(T&& v);
- Requires:
is_move_constructible<T>::value
istrue
.- Effects:
Initializes the contained value as if direct-non-list-initializing an object of type
T
with the expressionstd::move(v)
.- Postconditions:
*this
is engaged.- Throws:
Any exception thrown by the selected constructor of
T
.- Remarks:
If
T
's selected constructor is aconstexpr
constructor, this constructor shall be aconstexpr
constructor.
template <class... Args> constexpr explicit optional(in_place_t, Args&&... args);
- Requires:
is_constructible<T, Args&&...>::value
istrue
.- Effects:
Initializes the contained value as if
constructingdirect-non-list-initializing an object of typeT
with the argumentsstd::forward<Args>(args)...
.- Postconditions:
*this
is engaged.- Throws:
Any exception thrown by the selected constructor of
T
.- Remarks:
If
T
's constructor selected for the initialization is aconstexpr
constructor, this constructor shall be aconstexpr
constructor.
template <class U, class... Args>
constexpr explicit optional(in_place_t, initializer_list<U> il, Args&&... args);
- Requires:
is_constructible<T, initializer_list<U>&, Args&&...>::value
istrue
.- Effects:
Initializes the contained value as if
constructingdirect-non-list-initializing an object of typeT
with the argumentsil, std::forward<Args>(args)...
.- Postconditions:
*this
is engaged.- Throws:
Any exception thrown by the selected constructor of
T
.- Remarks:
The function shall not participate in overload resolution unless
is_constructible<T, initializer_list<U>&, Args&&...>::value
istrue
.- Remarks:
If
T
's constructor selected for the initialization is aconstexpr
constructor, this constructor shall be aconstexpr
constructor.X.Y.4.2 Destructor [optional.object.dtor]
optional<T>::~optional();
- Effects:
If
is_trivially_destructible<T>::value != true
and*this
is engaged, callsval->T::~T()
.- Remarks:
If
is_trivially_destructible<T>::value == true
then this destructor shall be a trivial destructor.X.Y.4.3 Assignment [optional.object.assign]
optional<T>& optional<T>::operator=(nullopt_t) noexcept;
- Effects:
If
*this
is engaged callsval->T::~T()
to destroy the contained value; otherwise no effect.- Returns:
*this
.- Postconditions:
*this
is disengaged.
optional<T>& optional<T>::operator=(const optional<T>& rhs);
- Requires:
is_copy_constructible<T>::value
istrue
andis_copy_assignable<T>::value
istrue
.- Effects:
- If
*this
is disengaged andrhs
is disengaged, no effect, otherwise- if
*this
is engaged andrhs
is disengaged, destroys the contained value by callingval->T::~T()
, otherwise- if
*this
is disengaged andrhs
is engaged, initializes the contained value as if direct-non-list-initializing an object of typeT
with*rhs
, otherwise- (if both
*this
andrhs
are engaged) assigns*rhs
to the contained value.- Returns:
*this
.- Postconditions:
bool(rhs) == bool(*this)
.- Exception Safety:
If any exception is thrown, the values of
init
andrhs.init
remain unchanged. If an exception is thrown during the call toT
's copy constructor, no effect. If an exception is thrown during the call toT
's copy assignment, the state of its contained value is as defined by the exception safety guarantee ofT
's copyconstructorassignment.
optional<T>& optional<T>::operator=(optional<T>&& rhs) noexcept(see below);
- Requires:
is_move_constructible<T>::value
istrue
andis_move_assignable<T>::value
istrue
.- Effects:
- If
*this
is disengaged andrhs
is disengaged, no effect, otherwise- if
*this
is engaged andrhs
is disengaged, destroys the contained value by callingval->T::~T()
, otherwise- if
*this
is disengaged andrhs
is engaged, initializes the contained value as if direct-non-list-initializing an object of typeT
withstd::move(*rhs)
, otherwise- (if both
*this
andrhs
are engaged) assignsstd::move(*rhs)
to the contained value.- Returns:
*this
.- Postconditions:
bool(rhs) == bool(*this)
.- Remarks:
The expression inside
noexcept
is equivalent to:is_nothrow_move_assignable<T>::value && is_nothrow_move_constructible<T>::value- Exception Safety:
If any exception is thrown, the values of
init
andrhs.init
remain unchanged. If an exception is thrown during the call toT
's move constructor, the state of*rhs.val
is determined by exception safety guarantee ofT
's move constructor. If an exception is thrown during the call toT
's move assignment, the state of*val
and*rhs.val
is determined by exception safety guarantee ofT
's move assignment.
template <class U> optional<T>& optional<T>::operator=(U&& v);
- Requires:
is_constructible<T, U>::value
istrue
andis_assignable<
isU, TT&, U>::valuetrue
.- Effects:
If
*this
is engaged assignsstd::forward<U>(v)
to the contained value; otherwise initializes the contained value as if direct-non-list-initializing object of typeT
withstd::forward<U>(v)
.- Returns:
*this
.- Postconditions:
*this
is engaged.- Exception Safety:
If any exception is thrown, value of
init
remains unchanged. If an exception is thrown during the call toT
's constructor, the state ofv
is determined by exception safety guarantee ofT
's constructor. If an exception is thrown during the call toT
's assignment, the state of*val
andv
is determined by exception safety guarantee ofT
's assignment.- Remarks:
The function shall not participate in overload resolution unless
is_same<typename
isremove_referencedecay<U>::type, T>::valuetrue
.[Note: The reason to provide such generic assignment and then constraining it so that effectively
T
==U
is to guarantee that assignment of the formo = {}
is unambiguous. —end note]
template <class... Args> void optional<T>::emplace(Args&&... args);
- Requires:
is_constructible<T, Args&&...>::value
istrue
.- Effects:
Calls
*this = nullopt
. Then initializes the contained value as if constructing an object of typeT
with the argumentsstd::forward<Args>(args)...
.- Postconditions:
*this
is engaged.- Throws:
Any exception thrown by the selected constructor of
T
.- Exception Safety:
If an exception is thrown during the call to
T
's constructor,*this
is disengaged, and the previous*val
(if any) has been destroyed.
template <class U, class... Args> void optional<T>::emplace(initializer_list<U> il, Args&&... args);
- Requires:
is_constructible<T, initializer_list<U>&, Args&&...>::value
istrue
.- Effects:
Calls
*this = nullopt
. Then initializes the contained value as if constructing an object of typeT
with the argumentsil, std::forward<Args>(args)...
.- Postconditions:
*this
is engaged.- Throws:
Any exception thrown by the selected constructor of
T
.- Exception Safety:
If an exception is thrown during the call to
T
's constructor,*this
is disengaged, and the previous*val
(if any) has been destroyed.- Remarks:
The function shall not participate in overload resolution unless
is_constructible<T, initializer_list<U>&, Args&&...>::value
istrue
.X.Y.4.4 Swap [optional.object.swap]
void optional<T>::swap(optional<T>& rhs) noexcept(see below);
- Requires:
LValues of type T shall be swappable andis_move_constructible<T>::value
istrue
.- Effects:
- If
*this
is disengaged andrhs
is disengaged, no effect, otherwise- if
*this
is engaged andrhs
is disengaged, initializes the contained value ofrhs
by direct-initialization withstd::move(*(*this))
, followed byval->T::~T(), swap(init, rhs.init)
, otherwise- if
*this
is disengaged andrhs
is engaged, initializes the contained value of*this
by direct-initialization withstd::move(*rhs)
, followed byrhs.val->T::~T(), swap(init, rhs.init)
, otherwise- (if both
*this
andrhs
are engaged) callsswap(*(*this), *rhs)
.- Throws:
Any exceptions that the expressions in the Effects clause throw.
- Remarks:
The expression inside
noexcept
is equivalent to:is_nothrow_move_constructible<T>::value && noexcept(swap(declval<T&>(), declval<T&>()))- Exception Safety:
If any exception is thrown, values of
init
andrhs.init
remain unchanged. If an exception is thrown during the call to functionswap
the state of*val
and*rhs.val
is determined by the exception safety guarantee ofswap
for lvalues ofT
. If an exception is thrown during the call toT
's move constructor, the state of*val
and*rhs.val
is determined by the exception safety guarantee ofT
's move constructor.X.Y.4.5 Observers [optional.object.observe]
constexpr T const* optional<T>::operator->() const;
T* optional<T>::operator->();
- Requires:
*this
is engaged.- Returns:
val
.- Throws:
Nothing.
- Remarks:
Unless
T
is a user-defined type with overloaded unaryoperator&
, the first function shall be aconstexpr
function.
constexpr T const& optional<T>::operator*() const;
T& optional<T>::operator*();
- Requires:
*this
is engaged.- Returns:
*val
.- Throws:
Nothing.
- Remarks:
The first function shall be a
constexpr
function.
constexpr explicit optional<T>::operator bool() noexcept;
- Returns:
init
.- Remarks:
tThis function shall be aconstexpr
function.
constexpr T const& optional<T>::value() const;
T& optional<T>::value();
- Returns:
*val
, ifbool(*this)
.- Throws:
bad_optional_access
if!*this
.- Remarks:
The first function shall be a
constexpr
function.
template <class U> constexpr T optional<T>::value_or(U&& v) const&;
- Requires:
is_copy_constructible<T>::value
istrue
andis_convertible<U&&, T>::value
istrue
.- Returns:
bool(*this) ? **this : static_cast<T>(std::forward<U>(v))
.- Throws:
Any exception thrown by the selected constructor of
T
.- Exception Safety:
If
init == true
and exception is thrown during the call toT
's constructor, the value ofinit
andv
remains unchanged and the state of*val
is determined by the exception safety guarantee of the selected constructor ofT
. Otherwise, when exception is thrown during the call toT
's constructor, the value of*this
remains unchanged and the state ofv
is determined by the exception safety guarantee of the selected constructor ofT
.- Remarks:
If the selected constructor ofIf both constructors ofT
is aconstexpr
constructorT
which could be selected areconstexpr
constructors, this function shall be aconstexpr
function.
template <class U> T optional<T>::value_or(U&& v) &&;
- Requires:
is_move_constructible<T>::value
istrue
andis_convertible<U&&, T>::value
istrue
.- Returns:
bool(*this) ? std::move(**this) : static_cast<T>(std::forward<U>(v))
.- Throws:
Any exception thrown by the selected constructor of
T
.- Exception Safety:
If
init == true
and exception is thrown during the call toT
's constructor, the value ofinit
andv
remains unchanged and the state of*val
is determined by the exception safety guarantee of theT
's constructor. Otherwise, when exception is thrown during the call toT
's constructor, the value of*this
remains unchanged and the state ofv
is determined by the exception safety guarantee of the selected constructor ofT
.X.Y.5 In-place construction [optional.inplace]
struct in_place_t{};
constexpr in_place_t in_place{};The struct
in_place_t
is an empty structure type used as a unique type to disambiguate constructor and function overloading. Specifically,optional<T>
has a constructor within_place_t
as the first argument followed by an argument pack; this indicates thatT
should be constructed in-place (as if by a call to placement new expression) with the forwarded argument pack as parameters.X.Y.6 Disengaged state indicator [optional.nullopt]
struct nullopt_t{see below};
constexpr nullopt_t nullopt(unspecified);The struct
nullopt_t
is an empty structure type used as a unique type to indicate a disengaged state foroptional
objects. In particular,optional<T>
has a constructor withnullopt_t
as single argument; this indicates that a disengaged optional object shall be constructed.Type
nullopt_t
shall not have a default constructor. It shall be a literal type. Constantnullopt
shall be initialized with an argument of literal type.X.Y.7 Class
bad_optional_access
[optional.bad_optional_access]namespace std { class bad_optional_access : public logic_error { public: explicit bad_optional_access(const string& what_arg); explicit bad_optional_access(const char* what_arg); }; }The class
bad_optional_access
defines the type of objects thrown as exceptions to report the situation where an attempt is made to access the value of a disengaged optional object.
bad_optional_access(const string& what_arg);
- Effects:
Constructs an object of class
bad_optional_access
.- Postcondition:
strcmp(what(), what_arg.c_str()) == 0
.
bad_optional_access(const char* what_arg);
- Effects:
Constructs an object of class
bad_optional_access
.- Postcondition:
strcmp(what(), what_arg) == 0
.X.Y.8 Relational operators [optional.relops]
template <class T> constexpr bool operator==(const optional<T>& x, const optional<T>& y);
- Requires:
T
shall meet the requirements ofEqualityComparable
.- Returns:
If
bool(x) != bool(y)
,false
; otherwise ifbool(x) == false
,true
; otherwise*x == *y
.- Remarks:
Instantiations of this function template for which
*x == *y
is a core constant expression, shall beconstexpr
functions.
template <class T> constexpr bool operator!=(const optional<T>& x, const optional<T>& y);
- Returns:
!(x == y)
.
template <class T> constexpr bool operator<(const optional<T>& x, const optional<T>& y);
- Requires:
Expression
less<T>{}(*x, *y)
*x < *y
shall be well-formed and its result shall be convertible tobool
.- Returns:
If
(!y)
,false
; otherwise, if(!x)
,true
; otherwiseless<T>{}(*x, *y)
*x < *y
.- Remarks:
Instantiations of this function template for which
expressionless<T>{}(*x, *y)
*x < *y
is a core constant expression, shall beconstexpr
functions.
template <class T> constexpr bool operator>(const optional<T>& x, const optional<T>& y);
- Returns:
(y < x)
.
template <class T> constexpr bool operator<=(const optional<T>& x, const optional<T>& y);
- Returns:
!(y < x)
.
template <class T> constexpr bool operator>=(const optional<T>& x, const optional<T>& y);
- Returns:
!(x < y)
.X.Y.9 Comparison with
nullopt
[optional.nullops]
template <class T> constexpr bool operator==(const optional<T>& x, nullopt_t) noexcept;
template <class T> constexpr bool operator==(nullopt_t, const optional<T>& x) noexcept;
- Returns:
(!x)
.
template <class T> constexpr bool operator!=(const optional<T>& x, nullopt_t) noexcept;
template <class T> constexpr bool operator!=(nullopt_t, const optional<T>& x) noexcept;
- Returns:
bool(x)
.
template <class T> constexpr bool operator<(const optional<T>& x, nullopt_t) noexcept;
- Returns:
false
.
template <class T> constexpr bool operator<(nullopt_t, const optional<T>& x) noexcept;
- Returns:
bool(x)
.
template <class T> constexpr bool operator<=(const optional<T>& x, nullopt_t) noexcept;
- Returns:
(!x)
.
template <class T> constexpr bool operator<=(nullopt_t, const optional<T>& x) noexcept;
- Returns:
true
.
template <class T> constexpr bool operator>(const optional<T>& x, nullopt_t) noexcept;
- Returns:
bool(x)
.
template <class T> constexpr bool operator>(nullopt_t, const optional<T>& x) noexcept;
- Returns:
false
.
template <class T> constexpr bool operator>=(const optional<T>& x, nullopt_t) noexcept;
- Returns:
true
.
template <class T> constexpr bool operator>=(nullopt_t, const optional<T>& x) noexcept;
- Returns:
(!x)
.X.Y.10 Comparison with
T
[optional.comp_with_t]
template <class T> constexpr bool operator==(const optional<T>& x, const T& v);
- Returns:
bool(x) ? *x == v : false
.
template <class T> constexpr bool operator==(const T& v, const optional<T>& x);
- Returns:
bool(x) ? v == *x : false
.
template <class T> constexpr bool operator!=(const optional<T>& x, const T& v);
- Returns:
bool(x) ? !(*x == v) : true
.
template <class T> constexpr bool operator!=(const T& v, const optional<T>& x);
- Returns:
bool(x) ? !(v == *x) : true
.
template <class T> constexpr bool operator<(const optional<T>& x, const T& v);
- Returns:
bool(x) ?
.less<T>{}(*x, v)*x < v : true
template <class T> constexpr bool operator<(const T& v, const optional<T>& x);
- Returns:
bool(x) ? v < *x : false
.
template <class T> constexpr bool operator>(const T& v, const optional<T>& x);
- Returns:
bool(x) ? *x < v : true
.
template <class T> constexpr bool operator>(const optional<T>& x, const T& v);
- Returns:
bool(x) ? v < *x : false
.
template <class T> constexpr bool operator>=(const optional<T>& x, const T& v);
- Returns:
!(x < v)
.
template <class T> constexpr bool operator>=(const T& v, const optional<T>& x);
- Returns:
!(v < x)
.
template <class T> constexpr bool operator<=(const optional<T>& x, const T& v);
- Returns:
!(x > v)
.
template <class T> constexpr bool operator<=(const T& v, const optional<T>& x);
- Returns:
!(v > x)
.X.Y.11 Specialized algorithms [optional.specalg]
template <class T> void swap(optional<T>& x, optional<T>& y) noexcept(noexcept(x.swap(y)));
- Effects:
calls
x.swap(y)
.
template <class T>
constexpr optional<typename decay<T>::type> make_optional(T&& v);
- Returns:
optional<typename decay<T>::type>(std::forward<T>(v))
.X.Y.12 Hash support [optional.hash]
template <class T> struct hash<optional<T>>;
- Requires:
the template specilaization
hash<T>
shall meet the requirements of class templatehash
(Z.X.Y). The template specilaizationhash<optional<T>>
shall meet the requirements of class templatehash
. For an objecto
of typeoptional<T>
, ifbool(o) == true
,hash<optional<T>>()(o)
shall evaluate to the same value ashash<T>()(*o)
; otherwise it evaluates to an unspecified value.
This proposal can be implemented as pure library extension, without any compiler magic support, in C++11. An almost full rerefence implementation of this proposal can be found at https://github.com/akrzemi1/Optional/. Below we demonstrate how one can implement optional
's constexpr
constructors to engaged and disengaged state as well as constexpr
operator*
for TriviallyDestructible
T
's.
namespace std { #if defined NDEBUG # define ASSERTED_EXPRESSION(CHECK, EXPR) (EXPR) #else # define ASSERTED_EXPRESSION(CHECK, EXPR) ((CHECK) ? (EXPR) : (fail(#CHECK, __FILE__, __LINE__), (EXPR))) inline void fail(const char* expr, const char* file, unsigned line) { /*...*/ } #endif struct dummy_t{}; template <class T> union optional_storage { static_assert( is_trivially_destructible<T>::value, "" ); dummy_t dummy_; T value_; constexpr optional_storage() // null-state ctor : dummy_{} {} constexpr optional_storage(T const& v) // value ctor : value_{v} {} ~optional_storage() = default; // trivial dtor }; template <class T> // requires: is_trivially_destructible<T>::value class optional { bool initialized_; optional_storage<T> storage_; public: constexpr optional(nullopt_t) : initialized_{false}, storage_{} {} constexpr optional(T const& v) : initialized_{true}, storage_{v} {} constexpr T const& operator*() { return ASSERTED_EXPRESSION(bool(*this), storage_.value_); } constexpr T const& value() { return *this ? storage_.value_ : (throw bad_optional_access(""), storage_.value_); } // ... }; } // namespace std
Many people from the Boost community, participated in the developement of the Boost.Optional library. Sebastian Redl suggested the usage of function emplace
.
Daniel Krügler provided numerous helpful suggestions, corrections and comments on this paper; in particular he suggested the addition of and reference implementation for "perfect initialization" operations.
Tony Van Eerd offered many useful suggestions and corrections to the proposal.
People in discussion group "ISO C++ Standard - Future Proposals" provided numerous insightful suggestions: Vladimir Batov (who described and supported the perfect forwarding constructor), Nevin Liber, Ville Voutilainen, Richard Smiths, Dave Abrahams, Chris Jefferson, Jeffrey Yasskin, Nikolay Ivchenkov, Matias Capeletto, Olaf van der Spek, Vincent Jacquet, Kazutoshi Satoda, Vicente J. Botet Escriba, Róbert Dávid, Vincent Jacquet, Luc Danton, Greg Marr, and many more.
Joe Gottman suggested the support for hashing some optional objects.
Nicol Bolas suggested to make operator->
conditionally constexpr
based on whether T::operator&
is overloaded.
noexcept
Prevents Library Validation" (N3248, http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3248.pdf)