Doc. no.:WG21/N3982
Date:2014-04-03
Author:Andrzej Krzemieński
Contact:[email protected]
Addresses:fundamentals-ts

Rvalue reference overloads for optional

This document proposes an addition of missing rvalue reference overloads for optional observer functions.

Table of contents

Motivation

Consider the following usage of optional. A converting function that converts from type T to some other type U:

template <typename U, typename T>
optional<U> convert(const T& v);

Such a conversion may fail because certain values of type T cannot be represented by any value of type U. For instance converting a string to int may fail. One user can consider such failure an exceptional condition that qualifies for a throw. Other user may just want to use a default value instead. Some other user may want to perform some fallback action, or skip some part of the code. Finally, a different user can just be sure that the conversion will not fail (e.g. when converting an int to a string), and may want to skip a time-consuming check. optional can satisfy all these expectations:

int i;	

auto oi = convert<int>(str);   
if (oi) i = *oi;                    // (1) check and skip logic
i = convert<int>(str).value_or(-1); // (2) use default
i = convert<int>(str).value();      // (3) throw if empty
i = *convert<int>("1");             // (4) unchecked

Now, suppose that, rather than to type int, we are converting to a type that is MoveConstructible but not CopyConstructible. Case (1), with manual check, does not work (unless we put an explicit move). But that is understandable: we want to perform two reads from the optional object: one to check if it contains the value, the other to read the value. For that we need a named object; it cannot be a temporary, so no implicit moving is possible. Case (2) will work fine. This is because function value_or has two overloads: for *this being an lvalue reference to const and rvalue reference. Cases (3) and (4) will not work although they could have. optional is just missing the rvalue reference overloads for these functions. This is what this proposal intends to fix: add rvalue reference overloads for member functions value and operator* (not for operator->, because it appears not to be implementable). I consider it a bug fix rather than a new feature, primarily because such an overload already exists and has been approved for function value_or. This is why I propose to fix it still in the first version of Fundamentals TS.

Implementability

The proposed changes are not implementable in C++11. This is because in C++11 constexpr member functions automatically become const, and one cannot implement a member function that is both constexpr and has an rvalue reference qualifier. C++14 fixed it though, and a reference implementation of the proposed fix to optional in C++14 (tested in Clang 3.4) can be accessed at https://github.com/akrzemi1/Optional. For consistency, this proposal makes all the observer functions constexpr.

Proposed wording

The insertions and deletions in this section describe the changes to Fundamentals TS, assuming N3966 has been incorporated.

In optional synopsis [optional.synop], change the declaration of observers as follows:

    // 5.4.5, observers
    constexpr T const* operator ->() const;
    constexpr T* operator ->();
    constexpr T const& operator *() const&;
    constexpr T& operator *() &;
    constexpr T&& operator *() &&;
    constexpr explicit operator bool() const noexcept;
    constexpr T const& value() const&;
    constexpr T& value() &;
    constexpr T&& value() &&;
    template <class U> constexpr T value_or(U&&) const&;
    template <class U> constexpr T value_or(U&&) &&;
Change [optional.object.observe] as follows:
5.4.5 Observers [optional.object.observe]

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

Requires:

bool(*this).

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:

bool(*this).

Returns:

*val.

Throws:

Nothing.

Remarks:

The first functionThese functions shall be a constexpr functions.

constexpr T&& operator*() &&

Requires:

bool(*this).

Returns:

std::move(*val).

Throws:

Nothing.

Remarks:

This function shall be a constexpr function.

constexpr explicit operator bool() noexcept;

Returns:

true if and only if *this contains a value.

Remarks:

This function shall be a constexpr function.

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

Returns:

*val, if bool(*this).

Throws:

bad_optional_access if !*this.

Remarks:

The first functionThese functions shall be a constexpr functions.

constexpr T && value() &&;

Returns:

std::move(*val), if bool(*this).

Throws:

bad_optional_access if !*this.

Remarks:

This function shall be a constexpr function.

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.

Returns:

bool(*this) ? **this : static_cast<T>(std::forward<U>(v)).

Throws:

Any exception thrown by the selected constructor of T.

Exception Safety:

If bool(*this) and exception is thrown during the call to T's constructor, the value of bool(*this) and v remains unchanged and the state of *val is determined by the exception safety guarantee of the selected constructor of T. Otherwise, when an 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 any of the constructors of T which could be selected is a constexpr constructor, this function shall be a constexpr function.

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.

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 bool(*this) and exception is thrown during the call to T's constructor, the value of bool(*this) and v remains unchanged and the state of *val is determined by the exception safety guarantee of the T's constructor. Otherwise, when an 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 any of the constructors of T which could be selected is a constexpr constructor, this function shall be a constexpr function.

Acknowledgements

Daniel Krügler and Tomasz Kamiński reviewed the proposal and suggested fixes.