Le 26/05/2017 à 12:22, Thomas Heller a écrit :
On 05/26/2017 09:03 AM, Vicente J. Botet Escriba wrote:
On 05/25/2017 07:28 AM, Vicente J. Botet Escriba wrote:
Le 24/05/2017 à 21:44, Thomas Heller via Boost a écrit :
First of all, I don't agree with the strong (conceptual) relationship between optional (be it boost:: or std::experimental) in such a way that expected is a generalization of it. From what I understand, the purpose of expected/outcome/ result is to be used as a mechanism to return the result of a function. As such it should also expose the semantics of it. Fortunately, we already have a (asynchronous) return object standardized (std::future). And this is my basic disagreement here: Why not model expected in the lines of future? With the main points being: - expected being movable only - expected
::value() should always return by value and invalidate this. - (I would really prefer the color .get though ;)) So the question to Vicente/Niall is: what is the motivation to make it "optional-ish"? Do we have use cases which make this design undesirable? expected is a generalization of optional as it is synchronous and could return more information about why the the value is not there.
Right, I can see this argument now, let me try to rephrase it a little bit (correct me if I got something wrong please): We want to be able to have a mechanism to return (or store) a value which might not be there, and we need to know why it is not there. The class that's currently available which almost does what we want is optional, in fact, it is already used in such situations, so what we miss is the possible error. So here we are, and then we naturally end up with something like variant
. Makes perfect sense. My line of thought was mostly influenced by the property of being solely used as a return object. And well, we already have the asynchronous return objects, so why not go with something a synchronous return object which represents a similar interface/semantics.
With that being said, I am still not sure if the result of both ways to look at it should converged into the same result.
expected could be seen as the ready storage of a future. future::get block until the future is ready and then returns by reference :)
Except not quite ;) excepted indeed sounds like the perfect fit for the value store in the share state. The only problem here, is that it really requires some kind of empty (or uninitialized) since the value is only computed some time in the future (this is motivation #1 for the proposed default constructed semantics), having either a default constructed T or E doesn't make sense in that scenario. So it is more like a variant
. From a high level I will see it as optional >. We optional because we have in addition the not-yet-ready state. Once Le 26/05/2017 à 08:22, Thomas Heller a écrit : the future becomes ready we have expected
. I understand that the implementation optional > could be variant . The implementation of expected could store as well variant , but this makes difficult to extract the ready value as variant is not convertible from variant . So there are two possibilities for future::then * status-quo pass the future as parameter of the continuation * make it possible to cast variant from variant and pass expected to the continuation I proposed other future functions (next and catch_exception) long time ago to manage with the success and failure branches, so that we don't add any constraint in the way the contents is stored.
Resuming, you are right expected
will not help as a value type representing the ready state without major changes. Ugh, I don't want to go this path ;) Which one? Mind you, a ready future (which you have present in a continuation) already fulfills (or should) all the PossiblyValued properties. If it gets ammended with a has_value/has_error/get_exception function, why introduce another type? This safes you all the hassle you have with the different possible conversion paths. Humm, the problem are the pre-conditions. Defining has_value/has_error/get_exception on a Future will need to check if it is ready :( I know that depending on the implementation this could be an additional check or not, but at the high level we add an additional condition. If you pass a type expected
that already satisfy the ready condition, there are no need for such pre-condition. The type system help you. If you pass a future to the continuation you can not reuse the continuation function as it is valid only in this context where the future is ready.
Is like if you pass a pointer to a function when the function is waiting for the pointee and it has a sense only when there is a pointee and not when the pointer is null. As if the for_each algorithm requested a function that takes the iterator instead of the value_type. The argument of passing a future instead has been that the compiler will optimize all this or that this are micro optimization as concurrency time is at another level. I believe however that the type system should be used for this purposes. Maybe I'm wrong and I've missed something important the current future::then can provide and that other alternatives cannot. Please, note that I don't have a complete solution without as I said major/controversial changes.
The weak point in my proposed interface would indeed be the destructive .value() functions, since a shared state needs to be able to obtain it's value multiple times (in the case of shared_future).
I want to ask you, what would be the liabilities of an expected that is copyable?
That's a good question. When initially thinking about it, I was very deep into the semantics of asynchronous return objects, which use a common shared state which presents an entirely different problem. So I was always thinking in terms of: Does a copy share the result, like shared_future, or does it have to be unique? I guess this doesn't apply to expected and keeping the value semantics of the underlying types makes most sense. I guess there is nothing wrong with expected being copyable if T and E are copyable.
Glad to see we agree.
We don't have a problem returning be reference, why would we like to return by value?
Mainly to represent the fact that we have a return value from a function. There is always one return, if you want to alias it, bind it to a named variable.
I see this as tuples and structured binding. We return a tuple/expected and we are able to get the contents by reference. No need to copy while you want to access to the elements.
Indeed, no need to copy. Assuming Movable, this would work. I concur that nowadays you don't necessarily need Movable to return from functions... In the end, I am fine with either choice.
Great.
Why do you prefer get? what do you get with get? How will you name the function that give you access to the value of a PossiblyValued type?
It's really just a different color. The preference to get is coming from my mental model of mine with expected being more like a asynchronous return object (future calls it get as well). There are other types (with similar purpose) using the name get for their accessor: shared_ptr, unique_ptr, tuple and variant.
+ tuple
These get functions are not uniform. Some can throw some not. Some return a pointer some return a reference. Some are narrow some are wide. Should we use a different name for each flavor? I believe we should, but it is too late.
If you had to name a function that takes a unique_ptr and returns the pointee value or throw if nullptr, how would you name it. I'll name it value() even if value is not a name.
Note that future::get does two things: wait until ready and then returns a reference to the success value or throw. In some way future::get could be named future::wait_value. Too late also.
I really don't want to get into naming discussions, I'll take whatever our wizards in the ivory towers decide...
No problem. I didn't started the naming issue. Anyway, future could provide a value() function that requires the future to be ready and that throws if there is no value ;-)
By having these constraints, expected of course needs to have an uninitialized state. As such we'd have the three observers: valid(): true when has_value() || has_error(), false otherwise (for example default constructed, invalidated), has_value() and has_error().
Sorry but, not. We don't need such state. This is something future needs, but not expected.
This state is the logical conclusion coming from the destructive value function. As a result, one could just as well reuse it for default construction. If there is consensus that value returns by reference, I would go for result not being default constructible and have never-empty guarantees.
I don't follows you here. How the fact that expected
could be default constructible and returning by value or by reference are related. Assuming this function: typename std::decay<T>::type expected
::value() with the semantics that it invalidates this, you need some way to represent the "invalid" state. This invalid state could also be reused when default constructing...
Oh I see it now. However I don't see yet the benefice of invalidate the value. Why do you want to invalidate the stored value?
I we decided to default construct to an uninitialized state, I wouldn't support to show to the user this state via any observable function, but via UB (as for chrono::duration).
That's fine for me as well...
I've changed my mind since I wrote this as my arguments were wrong. Either expected doesn't provide a default constructor or if it does we need to default it to T.
I don't like the default to T{}. It seems arbitrary. I would rather see the default ctor vanish altogether and have the user go the extra mile when he really needs something like: expected
exp(/* the user decides what to do here, be it T{}, or make_unexpected<E>(....);*/); { // produce expected result here. } return exp;
Lastly I'm also for removing the default constructor. Just to note that the current proposal default to T{} and some one needs to make a contre-proposal ;-) Vicente