Boost logo

Boost :

Subject: Re: [boost] [outcome] High level summary of review feedback accepted so far
From: Niall Douglas (s_sourceforge_at_[hidden])
Date: 2017-05-27 14:13:02

>> - outcome<T> and result<T> will have their empty state removed, and all
>> observers gain narrow contracts. Default construction is disabled.
>> Example:
>> ```
>> result<Foo> v(make_errored_result(std::errc::invalid_argument));
>> assert(v.has_error()); // true
>> assert(!v.has_value()); // true
>> // As if Foo(reinterpret_cast<Foo &&>
>> // (error_code_extended(std::errc::invalid_argument));
>> Foo f(std::move(v.value()));
>> assert(v.has_error()); // still true
>> assert(!v.has_value()); // still true
>> ```
> Just a question I could do for the existing library also. What has_error
> mean for outcome, result? is that it has an EC or that it has no value?

.has_error() is true if and only if the current state is an
error_code_extended instance.

> And now that we don't have empty, what is the sens of has_error for

There will be no longer an empty state in result<T>, but for generic
programming I'll be retaining a .has_error() so end users can more
easily swap a result<T> for a result_e<T>.

> Maybe, outcome should have a get_state function that returns an enum so
> that the user can do a switch.

I had previously a .visit(callable) for that.

>> (NOTE: expected<T, E> will track whatever LEWG Expected does, but it
>> differs from what will become result<T> by having a wide contract on
>> .value() and narrow contracts on operator*(), .error(), operator->().
>> result<T> will have narrow contracts on everything, it is basically a
>> thin wrap of std::variant<T, error_code_extended> except with strong
>> never empty warranty)
> What will be the differences between result<T> and expected<T,
> error_code_extended>?
> The wide contracts for the observers? Can not we provide wide and narrow
> contracts or don't reuse the same name with different meaning?

The description above quite literally tells you the differences.

> If we had a expected<T, E1, .., En> what will be the differences between
> outcome<T> and expected<T, error_code_extended, exception_ptr>?

I would assume an expected<T, E1, .., En> could only return a
std::variant<E1, ..., En> from its .error(). I can't think what else it
could do.

outcome<T> continues to provide:
- T& .value()
- error_code_extended .error()
- std::exception_ptr .exception()

i.e. hard coded.

>> - New typedefs outcome_e<T> and result_e<T> are identical to outcome<T>
>> and result<T> except for adding a formal empty state. Observer contract
>> slightly widens, an attempt to use an empty object throws a
>> bad_outcome_access exception. Implicit conversion from non-empty-capable
>> varieties is permitted to empty-capable varieties, but not the other way
>> round. Default construction is to **empty**.
> Okay this corresponds to what others are naming optional_outcome,
> optional_result.

I'm not wedded to result_e<T> etc. Ok, I'll change result_e<T> and
outcome_e<T> to optional_result<T> and optional_outcome<T>. Done at

> If we had a optional_expected<T, E1, .., En>
> what will be the differences between result_e<T> and
> optional_expected<T, error_code_extended>?
> what will be the differences between outcome_e<T> and
> optional_expected<T, error_code_extended, exception_ptr>?

I am not sure what semantics you have chosen for optional_expected<>.

optional_outcome<T> is exactly like outcome<T> but with an added empty
state and a default constructor which constructs to empty. Otherwise

>> - New typedefs checked_outcome<T>/checked_result<T>,
>> checked_outcome_e<T>/checked_result_e<T> are added. These mirror the
>> editions just described, but checks and default actions occur on all
>> observer usage so hidden reinterpret_cast<> never occurs. Implicit
>> conversion from non-checked varieties is permitted to checked varieties,
>> but not the other way round.
>> Examples:
>> ```
>> // Note result<T> implicitly converts to checked_result<T>, but not
>> // the other way round. So we can use same make_errored_result().
>> checked_result<Foo> v(make_errored_result(std::errc::invalid_argument));
>> assert(v.has_error()); // true
>> assert(!v.has_value()); // true
>> // .value() throws std::system_error(
>> // std::make_error_code(std::errc::invalid_argument));
>> Foo f(std::move(v.value()));
>> ```
>> ```
>> checked_result<Foo> v(make_valued_result(Foo()));
>> assert(!v.has_error()); // true
>> assert(v.has_value()); // true
>> // .error() returns a default constructed (null) error_code_extended
>> // when result is valued to indicate "no error here"
>> error_code_extended ec(std::move(v.error()));
>> assert(!ec); // true
>> ```
> I will need more rationale about the need of this classes and why we
> need to do an action while observing them. Please, could you elaborate?

checked_optional_outcome<T> equals exactly the outcome<T> in the
presented library.

checked_optional_result<T> equals exactly the result<T> in the presented

All the discussion of the presented library to date applies to the
checked_*() functions.

>> Still to be decided:
>> ====================
>> - Should *_e() varieties provide convenience .get(), .get_error(),
>> .get_exception() which returns T, error_code_extended and
>> std::exception_ptr by value moved from internal state, resetting state
>> afterwards to empty? These would mirror future.get()'s single shot
>> observers.
> We need a valid use case to introduce them and even more as member
> functions.
> In any case, these functions can be defined on to of the provided
> interface, and could be non-member functions, isn't it?

There is an argument to make all the observers of Expected or Outcome
free functions. It certainly would fit how std::begin() etc work. So:

expected<T, E> foo(T());

if(has_value(foo)) ...

T x(std::move(value(foo)));

T y(std::move(get(foo)));

... and so on.


ned Productions Limited Consulting

Boost list run by bdawes at, gregod at, cpdaniel at, john at