Fixes for optional objects

ISO/IEC JTC1 SC22 WG21 N4078 2014-06-20

Fernando Cacciola, fernando.cacciola@gmail.com
Andrzej Krzemieński, akrzemi1@gmail.com
Ville Voutilainen, ville.voutilainen@gmail.com

Project: Programming Language C++, fundamentals-ts

Introduction

This document proposes a number of wording fixes, as suggested in Issaquah meeting, to optional objects proposed in N3793. In addition, this document includes the feature additions from N3982 and the review feedback from the LWG sessions in the Rapperswil 2014 meeting.

Table of contents

  1. Introduction
  2. Table of contents
  3. Proposed wording
  4. Acknowledgements

Proposed wording

The insertions and deletions in this section describe the changes to Fundamentals TS after applying the changes from N3793. In other words, the changes are relative to N3793.

Change [optional.synop]:
    // 5.4.5, observers
    constexpr T const* operator ->() const;
    constexpr T* operator ->();
    constexpr T const& operator *() const&;
    constexpr T& operator *() &;
    constexpr T operator *() &&;
    constexpr T operator *() const&&;
    constexpr explicit operator bool() const noexcept;
    constexpr T const& value() const&;
    constexpr T& value() &;
    constexpr T value() &&;
    constexpr T value() const&&;
    template <class U> constexpr T value_or(U&&) const&;
    template <class U> constexpr T value_or(U&&) &&;
  // 5.5, In-place construction
  struct in_place_t{};
  constexpr in_place_t in_place{};

  // 5.6, DisengagedNo-value state indicator
  struct nullopt_t{see below};
  constexpr nullopt_t nullopt(unspecified);
  
  // 5.7, class bad_optional_access
  class bad_optional_access;
  private:
    bool init; // exposition only
    T*   val;  // exposition only
  };
Replace [optional.object], para 1 and 2 with the following

Any instance of optional<T> at any given time either contains a value or does not contain a value. When an instance of optional<T> contains a value, it means that an object of type T, referred to as the optional object's contained value, is allocated within the storage 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 the optional<T> storage suitably aligned for the type T. When an object of type optional<T> is contextually converted to bool, the conversion returns true if the object contains a value; otherwise the conversion returns false.

Member val is provided for exposition only. When an optional<T> object contains a value, val points to the contained value.

Change [optional.object] subclauses as follows:

5.4.1 Constructors [optional.object.ctor]

constexpr optional() noexcept;
constexpr optional(nullopt_t) noexcept;

Postconditions:

*this is disengaged*this does not contain a value.

Remarks:

No contained value is initialized. For every object type T, these constructors shall be constexpr constructors (C++11 §7.1.5).

optional(const optional<T>& rhs);

Requires:

is_copy_constructible<T>::value is true.

Effects:

If rhs is engaged contains a value, initializes the contained value as if direct-non-list-initializing an object of type T with the expression *rhs.

Postconditions:

bool(rhs) == bool(*this).

Throws:

Any exception thrown by the selected constructor of T.

optional(optional<T> && rhs) noexcept(see below);

Requires:

is_move_constructible<T>::value is true.

Effects:

If rhs is engaged contains a value, initializes the contained value as if direct-non-list-initializing an object of type T with the expression std::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(const T& v);

Requires:

is_copy_constructible<T>::value is true.

Effects:

Initializes the contained value as if direct-non-list-initializing an object of type T with the expression v.

Postconditions:

*this is engaged contains a value.

Throws:

Any exception thrown by the selected constructor of T.

Remarks:

If T's selected constructor is a constexpr constructor, this constructor shall be a constexpr constructor.

constexpr optional(T&& v);

Requires:

is_move_constructible<T>::value is true.

Effects:

Initializes the contained value as if direct-non-list-initializing an object of type T with the expression std::move(v).

Postconditions:

*this is engaged contains a value.

Throws:

Any exception thrown by the selected constructor of T.

Remarks:

If T's selected constructor is a constexpr constructor, this constructor shall be a constexpr constructor.

template <class... Args> constexpr explicit optional(in_place_t, Args&&... args);

Requires:

is_constructible<T, Args&&...>::value is true.

Effects:

Initializes the contained value as if direct-non-list-initializing an object of type T with the arguments std::forward<Args>(args)....

Postconditions:

*this is engaged contains a value.

Throws:

Any exception thrown by the selected constructor of T.

Remarks:

If T's constructor selected for the initialization is a constexpr constructor, this constructor shall be a constexpr 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 is true.

Effects:

Initializes the contained value as if direct-non-list-initializing an object of type T with the arguments il, std::forward<Args>(args)....

Postconditions:

*this is engaged contains a value.

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 is true.

Remarks:

If T's constructor selected for the initialization is a constexpr constructor, this constructor shall be a constexpr constructor.

5.4.2 Destructor [optional.object.dtor]

~optional();

Effects:

If is_trivially_destructible<T>::value != true and *this is engaged contains a value, calls val->T::~T().

Remarks:

If is_trivially_destructible<T>::value == true then this destructor shall be a trivial destructor.

5.4.3 Assignment [optional.object.assign]

optional<T>& operator=(nullopt_t) noexcept;

Effects:

If *this is engaged contains a value, calls val->T::~T() to destroy the contained value; otherwise no effect.

Returns:

*this.

Postconditions:

*this is disengaged does not contain a value.

optional<T>& optional<T>::operator=(const optional<T>& rhs);

Requires:

is_copy_constructible<T>::value is true and is_copy_assignable<T>::value is true.

Effects:


*this contains a value*this does not contain a value
rhs contains a value assigns *rhs to the contained value initializes the contained value as if direct-non-list-initializing an object of type T with *rhs
rhs does not contain a value destroys the contained value by calling val->T::~T() no effect

  • If *this is disengaged and rhs is disengaged, no effect, otherwise
  • if *this is engaged and rhs is disengaged, destroys the contained value by calling val->T::~T(), otherwise
  • if *this is disengaged and rhs is engaged, initializes the contained value as if direct-non-list-initializing an object of type T with *rhs, otherwise
  • (if both *this and rhs are engaged) assigns *rhs to the contained value.

Returns:

*this.

Postconditions:

bool(rhs) == bool(*this).

Remarks:Exception Safety:

If any exception is thrown, the result of the expressionvalues bool(*this) of init and rhs.init remains unchanged. If an exception is thrown during the call to T's copy constructor, no effect. If an exception is thrown during the call to T's copy assignment, the state of its contained value is as defined by the exception safety guarantee of T's copy assignment.

optional<T>& optional<T>::operator=(optional<T>&& rhs) noexcept(see below);

Requires:

is_move_constructible<T>::value is true and is_move_assignable<T>::value is true.

Effects:


The result of the expression bool(rhs) remains unchanged.

*this contains a value*this does not contain a value
rhs contains a value assigns move(*rhs) to the contained value initializes the contained value as if direct-non-list-initializing an object of type T with move(*rhs)
rhs does not contain a value destroys the contained value by calling val->T::~T() no effect

  • If *this is disengaged and rhs is disengaged, no effect, otherwise
  • if *this is engaged and rhs is disengaged, destroys the contained value by calling val->T::~T(), otherwise
  • if *this is disengaged and rhs is engaged, initializes the contained value as if direct-non-list-initializing an object of type T with std::move(*rhs), otherwise
  • (if both *this and rhs are engaged) assigns std::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 result of the expressionvalues bool(*this) of init and rhs.init remains unchanged. If an exception is thrown during the call to T's move constructor, the state of *rhs.val is determined by theexception safety guarantee of T's move constructor. If an exception is thrown during the call to T's move assignment, the state of *val and *rhs.val is determined by the exception safety guarantee of T's move assignment.

template <class U> optional<T>& optional<T>::operator=(U&& v);

Requires:

is_constructible<T, U>::value is true and is_assignable<T&, U>::value is true.

Effects:

If *this is engaged contains a value, assigns std::forward<U>(v) to the contained value; otherwise initializes the contained value as if direct-non-list-initializing object of type T with std::forward<U>(v).

Returns:

*this.

Postconditions:

*this is engaged contains a value.

Remarks:Exception Safety:

If any exception is thrown, the result of the expressionvalue bool(*this)of init remains unchanged. If an exception is thrown during the call to T's constructor, the state of v is determined by the exception safety guarantee of T's constructor. If an exception is thrown during the call to T's assignment, the state of *val and v is determined by the exception safety guarantee of T's assignment.

Remarks:

The function shall not participate in overload resolution unless is_same<typename decay<U>::typedecay_t<U>, T>::value is true.

[Note: The reason to provide such generic assignment and then constraining it so that effectively T == U is to guarantee that assignment of the form o = {} is unambiguous. —end note]

template <class... Args> void optional<T>::emplace(Args&&... args);

Requires:

is_constructible<T, Args&&...>::value is true.

Effects:

Calls *this = nullopt. Then initializes the contained value as if constructingdirect-non-list-initializing an object of type T with the arguments std::forward<Args>(args)....

Postconditions:

*this is engaged contains a value.

Throws:

Any exception thrown by the selected constructor of T.

Remarks:Exception Safety:

If an exception is thrown during the call to T's constructor, *this is disengageddoes not contain a value, 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 is true.

Effects:

Calls *this = nullopt. Then initializes the contained value as if constructingdirect-non-list-initializing an object of type T with the arguments il, std::forward<Args>(args)....

Postconditions:

*this is engaged contains a value.

Throws:

Any exception thrown by the selected constructor of T.

Remarks:Exception Safety:

If an exception is thrown during the call to T's constructor, *this is disengageddoes not contain a value, 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 is true.

5.4.4 Swap [optional.object.swap]

void optional<T>::swap(optional<T>& rhs) noexcept(see below);

Requires:
LVvalues of type TT shall be swappable and is_move_constructible<T>::value is true.

Effects:


*this contains a value*this does not contain a value
rhs contains a value calls swap(*(*this), *rhs) initializes the contained value of *this as if direct-non-list-initializing an object of type T with the expression std::move(*rhs), followed by rhs.val->T::~T(); postcondition is that *this contains a value and rhs does not contain a value
rhs does not contain a value initializes the contained value of rhs as if direct-non-list-initializing an object of type T with the expression std::move(*(*this)), followed by val->T::~T(); postcondition is that *this does not contain a value and rhs contains a value no effect

  • If *this is disengaged and rhs is disengaged, no effect, otherwise
  • if *this is engaged and rhs is disengaged, initializes the contained value of rhs by direct-initialization with std::move(*(*this)), followed by val->T::~T(), swap(init, rhs.init), otherwise
  • if *this is disengaged and rhs is engaged, initializes the contained value of *this by direct-initialization with std::move(*rhs), followed by rhs.val->T::~T(), swap(init, rhs.init)
  • (if both *this and rhs are engaged) calls swap(*(*this), *rhs).

Throws:

Any exceptions that the expressions in the Effects elementclause 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, the results of the expressions values of init and rhs.initbool(*this) and bool(rhs) remain unchanged. If an exception is thrown during the call to function swap the state of *val and *rhs.val is determined by the exception safety guarantee of swap for lvalues of T. If an exception is thrown during the call to T's move constructor, the state of *val and *rhs.val is determined by the exception safety guarantee of T's move constructor.

5.4.5 Observers [optional.object.observe]

constexpr T const* operator->() const;
constexpr T* operator->();

Requires:

*this is engaged contains a value.

Returns:

val.

Throws:

Nothing.

Remarks:

Unless T is a user-defined type with overloaded unary operator&, the first functionthese functions shall be a constexpr functions.

constexpr T const& operator*() const&;
constexpr T& operator*() &;

Requires:

*this is engaged contains a value

Returns:

*val.

Throws:

Nothing.

Remarks:

The first functionThese functions shall be a constexpr functions.

constexpr T operator*() &&;
constexpr T operator*() const&&;

Requires:

*this contains a value

Effects:

equivalent to return std::move(*val);

Remarks:

if is_move_constructible<T>::value is false, the program is ill-formed.

constexpr explicit operator bool() noexcept;

Returns:

inittrue if and only if *this contains a value.

Remarks:

This function shall be a constexpr function.

constexpr T const& value() const&;
constexpr T& value() &;

Effects:

equivalent to return bool(*this) ? *val : throw bad_optional_access();

Returns:

*val, if bool(*this).

Throws:

bad_optional_access if !*this.

Remarks:

The first function shall be a constexpr function.

constexpr T value() &&;
constexpr T value() const&&

Effects:

equivalent to return bool(*this) ? std::move(*val) : throw bad_optional_access();

Remarks:

If is_move_constructible<T>::value is false, the program is ill-formed.

template <class U> constexpr T value_or(U&& v) const&;

Requires:

is_copy_constructible<T>::value is true and is_convertible<U&&, T>::value is true.

Effects:Returns:

equivalent to return bool(*this) ? *val : 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 to T's constructor, the value of init and v remains unchanged and the state of *val is determined by the exception safety guarantee of the selected constructor of T. Otherwise, when exception is thrown during the call to T's constructor, the value of *this remains unchanged and the state of v is determined by the exception safety guarantee of the selected constructor of T.

Remarks:

If both constructors of T which could be selected are constexpr constructors, this function shall be a constexpr function.

Remarks:

If is_copy_constructible<T>::value && is_convertible<U&&, T>::value is false, the program is ill-formed.

template <class U> constexpr T value_or(U&& v) &&;

Requires:

is_move_constructible<T>::value is true and is_convertible<U&&, T>::value is true.

Effects:Returns:

equivalent to return bool(*this) ? std::move(*val) : 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 to T's constructor, the value of init and v remains unchanged and the state of *val is determined by the exception safety guarantee of the T's constructor. Otherwise, when exception is thrown during the call to T's constructor, the value of *this remains unchanged and the state of v is determined by the exception safety guarantee of the selected constructor of T.

Remarks:

If is_move_constructible<T>::value && is_convertible<U&&, T>::value is false, the program is ill-formed.

5.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 with in_place_t as the first parameterargument followed by a parameteran argument pack; this indicates that T should be constructed in-place (as if by a call to a placement new expression) with the forwarded pack expansionargument pack as arguments for the initialization of Tparameters.

5.6 DisengagedNo-value 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 statethe state of not containing a value for optional objects. In particular, optional<T> has a constructor with nullopt_t as a single argument; this indicates that an disengaged optional object not containing a value shall be constructed.

Type nullopt_t shall not have a default constructor. It shall be a literal type. Constant nullopt shall be initialized with an argument of literal type.

5.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);
    bad_optional_access();
  };
}

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 an disengaged optional object that does not contain a value.

bad_optional_access();

Effects:

Constructs an object of class bad_optional_access.

Postcondition:

what() returns an implementation-defined NTBS.

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.

Change [optional.relops] as follows:

5.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 of EqualityComparable.

Returns:

If bool(x) != bool(y), false; otherwise if bool(x) == false, true; otherwise *x == *y.

Remarks:

InstantiationsSpecializations of this function template, for which *x == *y is a core constant expression, shall be constexpr 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 *x < *y shall be well-formed and its result shall be convertible to bool.

Returns:

If (!y), false; otherwise, if (!x), true; otherwise *x < *y.

Remarks:

InstantiationsSpecializations of this function template, for which expression *x < *y is a core constant expression, shall be constexpr functions.

Change [optional.nullops] as follows:

5.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).

Acknowledgements

Jonathan Wakely reviewed the proposal and offered useful suggestions.

Daniel Krügler provided numerous helpful suggestions, corrections and comments on this proposal; in particular he suggested the addition of and reference implementation for "perfect initialization" operations.

Many people from the Boost community, participated in the developement of the Boost.Optional library. Sebastian Redl suggested the usage of function emplace.

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.