Czipperz
Czipperz

Reputation: 3336

Inferring return types with `-> decltype`

When using the C++11 arrow decltype statement, why does boost::optional::operator*() behave differently than boost::optional::get()?

Here's my code: (this works)

template<typename Fun, typename... Xs>
auto repeat_remove_optional(Fun f, std::string prompt, Xs&&... xs)
    -> decltype(f(prompt, xs...).get())
{
    auto x = f(prompt, xs...);
    if (x) return *x;
    prompt += "(Required!) ";
    while (true) {
        x = f(prompt, xs...);
        if (x) return *x;
    }
}

Use case is on some functions that prompt user with string and return boost::none if they enter escape during the input.

Using -> decltype(*f(prompt, xs...)) won't compile, saying that rvalue reference to type 'bool' cannot bind to lvalue of type 'bool': if (x) return *x; (at both return statements there is this error).

In other places in my code, the two functions behave identically. Why does this change here?

Usage:

boost::optional<bool> prompt_yes_no(const std::string& message);
bool continue = repeat_remove_optional(prompt_yes_no,
                                       "Are you sure you want to continue?"

Upvotes: 0

Views: 337

Answers (1)

mpark
mpark

Reputation: 7904

operator* has equivalent semantics with value, but not with get.

According to the documentation for boost::optional, the signatures are:

T const& get() const;
T&       get();

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

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

Under the assumption that f(prompt, xs...) returns some boost::optional<T>, decltype(*f(prompt, xs...)) is T&& whereas decltype(f(prompt, xs...).get()) is T&.

auto x = f(prompt, xs...);  // `x` has type `boost::optional<T>`.
if (x) {
  return *x;  // `*x` has type `T&`.
}

Substitute bool for T and we've got your error of rvalue reference to type 'bool' cannot bind to lvalue of type 'bool'

In this case, return *std::move(x) would keep the expressions equivalent.

NOTE: return std::move(*x) would also work, but the subtle semantic difference is that you would be invoking T& operator *() & then performing std::move on the resulting T& as opposed to performing std::move on boost::optional<T>& then invoking T&& operator *() &&. The latter is more accurate, since the expression *f(prompt, xs...) invokes T&& operator*() &&.

UPDATE (Thanks Praetorian for the comment): With the return type as specified, you're now returning a T&& into the contained value of x which is on the function stack. It would be best to decay the resulting type also: std::decay_t<decltype(*f(prompt, xs...))>.

Upvotes: 4

Related Questions