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
::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
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
. 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
> * we rename the intended specialization xxx<T> is similar to optional . 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
than a variant ). 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
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 (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? Best, Vicente