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.
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?
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
> 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.
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. 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(...) {} Best, Vicente