Boost logo

Boost :

Subject: Re: [boost] [gsoc-2013] Boost.Expected
From: Pierre T. (ptalbot_at_[hidden])
Date: 2013-04-28 14:17:33


On 04/28/2013 07:30 PM, Vicente J. Botet Escriba wrote:
> Le 28/04/13 18:27, Pierre T. a écrit :
>> On 04/28/2013 05:20 PM, Vicente J. Botet Escriba wrote:
>>> Le 28/04/13 16:22, Pierre T. a écrit :
>>>> On 04/27/2013 03:30 PM, Vicente J. Botet Escriba wrote:
>>>>> Le 27/04/13 09:39, Pierre T. a écrit :
>>>>>> On 04/26/2013 08:17 PM, Vicente J. Botet Escriba wrote:
>>>>>>> * Default Constructor or constructor from nullexpect
>>>>>>> What is the advantage of having a expected instance that doesn't
>>>>>>> have
>>>>>>> neither a value nor an exception?
>>>>>>> How would the user manages with this possibility?
>>>>>>> Are you looking to make expect movable?
>>>>>>>
>>>>>> Basically, I noticed that classes without default constructor (or
>>>>>> default state) are burdensome to use. Indeed, you cannot store an
>>>>>> expected in a class as a member if not initialized in the
>>>>>> constructor. Or doing something like:
>>>>>>
>>>>>> expected<int> e;
>>>>>> if(…)
>>>>>> else(…)
>>>>>> return e;
>>>>>>
>>>>>> Through, I removed the default constructor because I found it
>>>>>> unclear. I use a nullexcept because it was a good idea in
>>>>>> Boost.Optional with nullopt.
>>>>> Yes it was one. But the definition of optional is there to allow a
>>>>> state that means value not present. expected<> is designed to have
>>>>> a value or an exception.
>>>>> Could you answer to the question
>>>>> How would the user manages with this possibility?
>>>> By testing == nullexcept (operator== not in the proposal, sorry),
>>>> however you are right, expected must contains an exception or a
>>>> value. On the other hand, it's nice to provide a default
>>>> constructor, so an idea could be to add a method
>>>> "unitialized_error()" in the trait class.
>>> Why do you need a default constructor.
>> Because it can be useful for the following situation:
>>
>> expected<T> func(bool b)
>> {
>> expected<T> e;
>> if(b)
>> {
>> e = f();
>> // use e
>> }
>> else
>> {
>> e = g();
>> // use e
>> }
>> return e;
>> }
>>
>> Some programmers prefer to have only one return statement instead of
>> having:
>>
>> expected<T> func(bool b)
>> {
>> if(b)
>> {
>> expected<T> e = f();
>> // use e
>> return e;
>> }
>> else
>> {
>> expected<T> e = g();
>> // use e
>> return e;
>> }
>> }
>>
> Humm, I don't know if this is enough.
I'll drop the default constructor for the moment. When an implementation
will be available, feedback from users will help to find relevant use-cases.
>>>>>>
>>>>>> Finally, I'm not sure to understand how it's related to the
>>>>>> movable ability of Boost.Expected.
>>>>> I let you try to define move move semantics for expected and you
>>>>> will see why this is related.
>>> After thinking more on this the moved object could present its
>>> exception_ptr if it has one, so move semantics doesn't force to have
>>> an uninitialized expected<>
>> Not sure to get it,
>>
>> expected(expected&& e) // nothing in the initializer list because we
>> don't know if we must move T or Error.
>> {
>> if(has_value)
>> // move value
>> else
>> // move error
>> }
>>
>> In case T or Error haven't a default constructor, the previous code
>> can't work, right ?
> You would need to move construct using placement new
>
> new (&value_) T(forward<T>(val)) ;
>
> BTW, exception_ptr move is equivalent to a copy, isn't it?
>
It is, but expected may store any Error types and not only exception.
Adding a trait method "default_error" would help if the Error hasn't a
default constructor.
>> Could we add a dummy field in the union such as:
>>
>> union{ T value, Error error, bool internal_uninitialized};
> I don't think this is a good ideea.
>>
>> But I'm not sure it's what you meant.
>>>>>>
>>>>>>> *Ah I think I see now what was the initial intention. I suspect
>>>>>>> that you mean that as the result of e.then(f) would transport the
>>>>>>> exception stored on e if e is not valid, then the exception would
>>>>>>> be relayed until there is a call to visit_error and no call to
>>>>>>> any function will be done.
>>> Yes this is quite similar if we had exceptions and we had a try/catch
>>> block
>>>
>>> f0().then(f1).then(f2).then(f3).visit_error(error_visitor);
>>>
>>> try {
>>> f3(f2(f1(f0())));
>>> } catch(...) {
>>> error_visitor();
>>> }
>>>
>>> It would be great if we could have the equivalent for
>>> try {
>>> h(f(), g());
>>> } catch(...) {
>>> error_visitor();
>>> }
>>>
>>> when_all(f(), g()).then(h).visit_error(error_visitor);
>>>
>>> when_all could return a expected<tuple<T1, T2>> or something more
>>> specific so that then would extract the members of the tuple to call
>>> h with two parameters.
>>>
>>> This start to look more and more like the proposal for futures
>>> (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3558.pdf)
>>>
>> Sorry for the poor explanations about it, but you guessed correctly. I
>> read the futures proposal and the ideas in there are really
>> interesting. However because expected is not related to the time, I
>> think we could just do the following:
>>
>> e.then(f, g).then(h).inspect(error_inspector)
>>
>> where "then" takes an unlimited number of functions. In this case it
>> would return a tuple-wrapper class specific to the expected class (in
>> case users would like to store tuple inside expected).
> Humm, in the example I gave above f an g are not continuations but
> expected<> producers.
> Anyway, the variadic version of then could have its uses. I like it, but
> it is orthogonal to the when_all feature.
>
> What do you think about letting the continuation returning an
> expected<>? It has the advantage to allowing the continuation to
> transport exceptions also without throwing.
>
I forgot to claim the change but in my "then" version, the function
passed to "then" must return an expected (or void). So all functions are
expected producers. It's strange to return something else because it
would always be a good value into an expected. The "then" chaining could
not return error cases.

I don't think there are much differences between when_all and then(f,g).

However, I propose to let this feature out of the GSoC proposal. But of
course, we wouldn't let it out of the summer, it's just because time is
lacking and adding features in a hurry could break the proposal consistency.

>> About the "inspect" method, I try to find another name instead of
>> visit_error because it makes me think to the pattern visitor which is
>> too specific to hierarchy classes. I found that inspect was nice
>> because it's like in a factory where the chief "inspects" that
>> everything has been alright.
>>
> The name should work independently of the name of the 'then()' function.
Oh nice remark.
>
> on_exception? if_exception? catch_all? capture?
>
> e.catch_all(eh);
>
> And maybe catching/capturing an exception type at a time.
>
> e.catch_one<E1>(e1).catch_one<E2>(e2).catch_all<EA>(eh);
>
> e.capture<E1>(e1).capture<E2>(e2).capture<all>(eh);
>
>
> But a C++ try/catch is not worst, or is it?
>
> try { v=e.get(); } catch(E1 & ex) { e1(ex); } catch(E2 & ex) { e2(ex); }
> catch(...) {}
>
I would say "on_error", so it would be a generic name for error type
that aren't exception.

Thanks again,
Pierre T.


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk