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())
> 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
> 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
Do you want optional to take care of this possible future?