Boost logo

Boost :

Subject: Re: [boost] [outcome] How to drop the formal empty state
From: Vicente J. Botet Escriba (vicente.botet_at_[hidden])
Date: 2017-05-26 14:02:04

Le 26/05/2017 à 08:41, Gavin Lambert via Boost a écrit :
> On 26/05/2017 17:33, Vicente J. Botet Escriba wrote:
>> std::experimentall:expected<T,E>::error, doesn't throws. I don't see
>> a use case where we want to retrieve an error without checking
>> before. Maybe you have a case.
> Perhaps unit tests, where you're expecting an error but the code
> unexpectedly succeeds.
Okay. We could have a wide_error function for this purposes.

> Also as in the case above, when you forget that an empty state exists:
I will not take this in consideration for my purposes (expected
proposal) as I have no empty state.
> result<T> r = something();
> if (r.has_value())
> {
> do_something(r.value());
> }
> else
> {
> log(r.error()); // oops, r might be empty
> }
> I dislike gratuitous UB, and Niall assures us that optimisers will
> discard a double check so it should be reasonably cheap.
Do we need a probe?
>> Le 26/05/2017 à 01:36, Gavin Lambert a écrit :
>>> I don't like the idea of a default-constructed T because T is not
>>> always default-constructible, and this makes it inconsistently
>>> behaved for different T and makes it harder to use uniformly in
>>> containers, especially in generic code.
> For the record, not having a default constructor at all also makes it
> harder to use in containers, so I don't like that either. Though it's
> a weaker dislike than my dislike of a default-constructed T or E.
If you want empty we need a good implementation of
optional<expected<T,E>> ;-) outcome? But please follows this model.

>>> I don't like the idea of a default-constructed E because by
>>> convention (even if not quite in fact as Niall has pointed out --
>>> though I've yet to see a platform where a 0 error code *didn't* mean
>>> success, other than cases where the formal type is int but is
>>> actually used as bool) the default-constructed error_code means "no
>>> error", and this is heavily reinforced by its operator bool semantics.
>>> I do like the idea of a non-default-constructed error code, because
>>> failure to initialise the result does seem like an error to me.
>>> Niall points out that this is harder to detect and treat specially
>>> in code but I don't agree with that; as long as a suitably unique
>>> error code is used then a simple assert in the error path would pick
>>> it up, no problem.
>>> If the consensus is that an initial non-default error code is not
>>> satisfactory, then a formal empty state seems to me like the least
>>> worst alternative. I just know that it's going to bite someone at
>>> some point.
>> If we don't provide a default constructor for expected<T> we could be
>> forced to use optional<expected<T>>.
>> This allows to don't pay for this empty state when we don't need it.
>> The problem is that we are paying more than needed when we need it.
>> We have two options:
>> * we specialize optional<expected<T,E>>
>> * we rename the intended specialization xxx<T> is similar to
>> optional<expected<T>>. xxx could be outcome::result or optional_expected
> I'm not entirely sure how this relates to what I was saying.
> At least in terms of storage, the current implementation of empty
> state is presumably free (it should be no more expensive to internally
> store a variant<none_t, T, E> than a variant<T, E>). And it's
> currently required to exist due to exception guarantees (and possible
> noexcept(false) move constructors).
In terms of storage you are right, but not in terms of possible values.
This is why I'm using optional here to state clearly that we have an
additional value.
> I don't think that T should be restricted to noexcept(true)-movable
> types only, as this prevents using it with C++03 non-POD types (that
> have a copy constructor but lack a move constructor), which are still
> likely to be widespread in codebases (although perhaps less common as
> return values).
  Do you have an example of a C++03 error type that will throw?
> Given that, from the sounds of it an empty state does need to exist in
> the implementation. Where it sounds like Niall and you differ is
> whether that state should be exposed to the user. I think if it's
> there anyway then it probably should be, since this enables useful
> behaviour (such as storing in containers and using that state as
> "method not called yet", implying that the empty state should be the
> default-constructed state).
> If it turns out that the empty state is not needed by the
> implementation, then a non-default-constructed-E seems like a better
> default value, at least for Outcome where E is a known type. (It's a
> bit harder for Expected.)
> Another consideration is that regardless of default construction or
> not is that you need to decide what an expected<T, E> will contain if
> someone moves-from it (directly). Is it now formal-empty or does it
> now contain a moved-from-T or moved-from-E? Or does it contain a
> moved-from-variant<none_t, T, E> (if that's different)?
This is already defined in the proposal. What do you expect to have?
> The return type of value() plays a role here as well. If it returns
> by value, then you can probably pick whatever you like. If it returns
> by reference, then the caller can now move-from the internal T and
> ensure it will be in the has-a-moved-from-T state, not the empty
> state. (Which may or may not be desirable, but implies that
> moved-from is not the same as empty, which might surprise users of
> smart pointers.)
If you want something that can be empty, model it from optional<T>. Al
the functions will follow. Maybe you don't agree with the std::optional
interface and then my previous advise will not apply. Is this your case?
> (Returning by reference also disallows possible future storage
> optimisations from nested variant merging, as mentioned in another
> thread.)
Do you want optional to take care of this possible future?


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