From the front page, it looks like a implementation of expected, but only talks about the outcome template, it is very hard to find the documentation for
Hi Niall (and also probably Vicente)
I was following the discussion about expected/outcome/result very closely and
I can absolutely see the usefulness of such a library (I explicitly discarded
option here).
I am not sure if this post should count as a review, mainly because I disagree
with the fundamental design decisions (that includes expected as defined in
D0323R2) and therefor would cast my vote as in "not ready yet", I hope I can
convey my points below.
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
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:
An earlier edition of Outcome did implement future-promise. I think you yourself reviewed it then.
- expected being movable only - expected
::value() should always return by value and invalidate this.
As I mentioned in a reply to Peter yesterday, I am attracted to these semantics.
Second, I think Niall raised a valid about outcome being a framework for interoperability (completely orthogonal to the first point). However, I totally miss this from the proposed library, most pressing are non intrusive mechanisms. For that purpose I postulate, that a mechanism to transform between different unexpected results, that is: various error codes etc. However, for that to work, one would of course need a properly defined concept, for example, as Vicente suggested "EitherValue", and a mechanism to coerce one error type into another, maybe through ADL, or traits specialization or whatever.
Furthermore, I believe that .value/.error should really have a narrow contract, that is that it is UB to call those functions if the respective types are not held (easy to catch this in a debug build...). Why? Probably just a micro optimization, but consider the canonical usage: auto r = some_function_returning_expected(...); if (r.has_value()) // Do something with the value else // An error occurred, PANIC In both branches, we know exactly what's in there ... so why check again when getting the state out?
If you've already checked by hand, the compiler will elide the internal check entirely.
I don't get the "reinterpret_cast" argument. I am one of the persons that believe that UB is a necessary evil for some optimizations...
I am not keen on sprinkling reinterpret_cast everywhere. But it appears
When I open its "landing page", I get no idea about the following: - What is the purpose of the library? - Why/when should I use it?
It quite literally tells you both on the landing page. https://ned14.github.io/boost.outcome/index.html. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
On 05/25/2017 01:01 AM, Niall Douglas via Boost wrote:
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:
An earlier edition of Outcome did implement future-promise. I think you yourself reviewed it then.
We reviewed AFIO, which included outcome. Back then Outcome did indeed try to implement future-like semantics with semantic/interface changes which were discussed and found to be inappropriate. This is a different topic since you decided to define different semantics, again with lots of food for discussion.
- expected being movable only - expected
::value() should always return by value and invalidate this. As I mentioned in a reply to Peter yesterday, I am attracted to these semantics.
There are a lot of unanswered questions coming along with that design decision...
Second, I think Niall raised a valid about outcome being a framework for interoperability (completely orthogonal to the first point). However, I totally miss this from the proposed library, most pressing are non intrusive mechanisms. For that purpose I postulate, that a mechanism to transform between different unexpected results, that is: various error codes etc. However, for that to work, one would of course need a properly defined concept, for example, as Vicente suggested "EitherValue", and a mechanism to coerce one error type into another, maybe through ADL, or traits specialization or whatever.
already provides equivalence testing between std::error_code's.
I am not talking about equivalence testing or the ability for ones own categories. I am talking about conversions between two different error types.
Furthermore, I believe that .value/.error should really have a narrow contract, that is that it is UB to call those functions if the respective types are not held (easy to catch this in a debug build...). Why? Probably just a micro optimization, but consider the canonical usage: auto r = some_function_returning_expected(...); if (r.has_value()) // Do something with the value else // An error occurred, PANIC In both branches, we know exactly what's in there ... so why check again when getting the state out?
If you've already checked by hand, the compiler will elide the internal check entirely.
That's quite a claim to make here. When looking at vector<T>::at, this is certainly not the case, unless compilers made significant improvements over the last few years.
When I open its "landing page", I get no idea about the following: - What is the purpose of the library? - Why/when should I use it?
It quite literally tells you both on the landing page. https://ned14.github.io/boost.outcome/index.html.
From an empirical study of a non representative group, it seems it is not as literal as you would like it to be. One thing is that the points I mentioned are not visually distinguishable at first sight, there are three paragraphs with lots of prose. The first paragraph reads: "This is the Outcome library. It is a C++ 14 library intended to aid ultra-lightweight error handling in large C++ codebases, providing a more expressive and type safe alternative to integer error codes or enums." Apart from the trigger word "ultra-lightweight", what the library actually does is at the very end in a long sentence. The second: "Unlike alternative implementations, it works perfectly with exceptions and RTTI disabled and is thus suitable for low-latency/games/finance/SG14 users. One could view Outcome as a minimum overhead universal outcome transport mechanism for C++, hence being named "Outcome"." This sentence starts with "Unlike alternative implementations", I don't even know what to expect from this library, at this point, I really don't care about the alternatives yet, speaking of which, which alternatives? The key feature you wanted to highlight here is probably "works with exceptions and RTTI disabled". The next two paragraphs follow the same scheme. So in a nutshell: The Outcome library implements a set of types to be used for lightweight error transport. The main features include: - Type safe alternative to integer or enum based error codes - Works with exceptions and RTTI disabled - Suitable for real-time and low latency applications - Efficient implementation - Rich set of different types to support different use cases And then you can go on with everything else you want to say, possibly with links pointing into the documentation on where potential users are able to find more about the various topics you just advertised. (Just a braindump of mine, not claiming this is the ultimate solution.)
Niall
-- Thomas Heller Friedrich-Alexander-Universität Erlangen-Nürnberg Department Informatik - Lehrstuhl Rechnerarchitektur Martensstr. 3 91058 Erlangen Tel.: 09131/85-27018 Fax: 09131/85-27912 Email: thomas.heller@cs.fau.de
If you've already checked by hand, the compiler will elide the internal check entirely.
That's quite a claim to make here. When looking at vector<T>::at, this is certainly not the case, unless compilers made significant improvements over the last few years.
I have spent a lot of time tuning the implementation of Outcome to be optimiser friendly. I have also been looking at the disassembly of these objects in use in real world code for over a year. Unless you do a system call, a call to an extern function, or an atomic operation, the optimiser does a really great job at converting sequences of (with the if() hidden inside the outcome implementation): if(has_value) something1(value()); else something2(error()); if(has_value) something3(value()); else something4(error()); if(has_value) something5(value()); else something6(error()); ... into: if(has_value) { something1(value()); something3(value()); something5(value()); } else { something2(error()); something4(error()); something6(error()); } So, I repeat my claim about eliding the internal check. The optimiser is clever, if you don't give it cause not to eliminate code, it'll eliminate code very effectively, especially clang 4.0+ and GCC 6.0+. Besides, the checking of current state is not the expensive part, it's usually free on an out-of-order CPU as compare and branch usually uses non-busy execution ports whilst the CPU is stalled on memory. It's any branch misprediction which costs real time, typically 20 cycles or so on recent Intel chips. As a general rule, jumps to addresses later than current are predicted to not happen, so I intentionally placed the empty state value before valued, errored and excepted. That causes branches to equally mispredict for any of the valued, errored and excepted states, thus creating predictable execution times by creating a 20 cycle stall for all states but empty for when the state of an outcome is not deduced by the compiler. So I have paid attention to this stuff. My choices may be disagreeable to some or many, but it's not like they weren't thought through.
When I open its "landing page", I get no idea about the following: - What is the purpose of the library? - Why/when should I use it?
It quite literally tells you both on the landing page. https://ned14.github.io/boost.outcome/index.html.
From an empirical study of a non representative group, it seems it is not as literal as you would like it to be. One thing is that the points I mentioned are not visually distinguishable at first sight, there are three paragraphs with lots of prose.
The first paragraph reads: "This is the Outcome library. It is a C++ 14 library intended to aid ultra-lightweight error handling in large C++ codebases, providing a more expressive and type safe alternative to integer error codes or enums."
Apart from the trigger word "ultra-lightweight", what the library actually does is at the very end in a long sentence.
The second: "Unlike alternative implementations, it works perfectly with exceptions and RTTI disabled and is thus suitable for low-latency/games/finance/SG14 users. One could view Outcome as a minimum overhead universal outcome transport mechanism for C++, hence being named "Outcome"."
This sentence starts with "Unlike alternative implementations", I don't even know what to expect from this library, at this point, I really don't care about the alternatives yet, speaking of which, which alternatives? The key feature you wanted to highlight here is probably "works with exceptions and RTTI disabled".
The next two paragraphs follow the same scheme.
So in a nutshell:
The Outcome library implements a set of types to be used for lightweight error transport. The main features include: - Type safe alternative to integer or enum based error codes - Works with exceptions and RTTI disabled - Suitable for real-time and low latency applications - Efficient implementation - Rich set of different types to support different use cases
And then you can go on with everything else you want to say, possibly with links pointing into the documentation on where potential users are able to find more about the various topics you just advertised. (Just a braindump of mine, not claiming this is the ultimate solution.)
I used to have bullet points, but a previous round of Reddit and Boost feedback reduced the landing page to what is currently there on the basis of less information is more. You may have missed that Andrzej has already volunteered to restructure the landing page and intro into more bitesize chunks. You can track that issue at https://github.com/ned14/boost.outcome/issues/41. I have appended your notes to that issue. Thank you for the feedback. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
Le 24/05/2017 à 21:44, Thomas Heller via Boost a écrit :
Hi Niall (and also probably Vicente) I believe that this should be discussed probably in the std-proposal ML. Anyway as we are here I was following the discussion about expected/outcome/result very closely and I can absolutely see the usefulness of such a library (I explicitly discarded option here).
I am not sure if this post should count as a review, mainly because I disagree with the fundamental design decisions (that includes expected as defined in D0323R2) and therefor would cast my vote as in "not ready yet", I hope I can convey my points below. Wow, first notice I have of this disagreement. It is better later than never.
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. expected could be seen as the ready storage of a future. future::get block until the future is ready and then returns by reference :)
I want to ask you, what would be the liabilities of an expected that is copyable? We don't have a problem returning be reference, why would we like to return by value? 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?
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. 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).
Second, I think Niall raised a valid about outcome being a framework for interoperability (completely orthogonal to the first point). However, I totally miss this from the proposed library, most pressing are non intrusive mechanisms. For that purpose I postulate, that a mechanism to transform between different unexpected results, that is: various error codes etc. However, for that to work, one would of course need a properly defined concept, for example, as Vicente suggested "EitherValue", and a mechanism to coerce one error type into another, maybe through ADL, or traits specialization or whatever.
With that in place, one could simply define the different EitherValue types, there is no need that everything needs to be in the form of "basic_XXX". For the library under review, this would be perfectly sufficient: template
class expected; template <typename T> using result = expected ; template <typename T> using outcome = expected >; We will need to have a specialization of expected > as we have an index for variant. The revision 2 of the expected proposal talks of a expected . That is, given that we have either a value or unexpected, we can convert expected
to expected if T is convertible to U and E1 "coercable" (with whichever mechanism) to E2. I have added this conversion constructor recently to the expected
There will be such a proposal as a generalization of Nullable based on what I named PossiblyValued.. https://github.com/viboes/std-make/blob/master/doc/proposal/nullable/D0196R3... proposal as the result of my understanding of the need of Outcome and I hope we will discuss it in Toronto.
If we then have a generic mechanism to get from a (possibly user defined "E") to an exception, I completely miss the point of the outcome template.
And why not to throw E?
Furthermore, I believe that .value/.error should really have a narrow contract, that is that it is UB to call those functions if the respective types are not held (easy to catch this in a debug build...). Why? Probably just a micro optimization, but consider the canonical usage: auto r = some_function_returning_expected(...); if (r.has_value()) // Do something with the value else // An error occurred, PANIC In both branches, we know exactly what's in there ... so why check again when getting the state out? I don't get the "reinterpret_cast" argument. I am one of the persons that believe that UB is a necessary evil for some optimizations...
We agree here.
All optimizations can then easily be put as implementation details and the generic expected
will probably suffice for most use cases, for everything else, we can implement special types which conform to our concepts and implement the error conversion mechanisms. This will most likely also work with different APIs/ABIs.
The main problem is that we don't have here the generic interface and it is for this reason we are discussing on the details of a concrete class. The original expected proposal has fmap, bind, catch_error functions. We have removed them form expected since the last revision, but we need now to have a generic interface for those functions. For me expected should have the minimum, everything else should be associated to a specific concept, as Nullable, PossiblyValued, MonadError, SumType. https://github.com/viboes/std-make/blob/master/doc/proposal/monads/Monads.md https://github.com/viboes/std-make/tree/master/include/experimental/fundamen... https://github.com/viboes/std-make/tree/master/include/experimental/fundamen... https://github.com/viboes/std-make/tree/master/include/experimental/fundamen... Vicente
On 05/25/2017 07:28 AM, Vicente J. Botet Escriba wrote:
Le 24/05/2017 à 21:44, Thomas Heller via Boost a écrit :
Hi Niall (and also probably Vicente) I believe that this should be discussed probably in the std-proposal ML. Anyway as we are here
Sure, whatever you think is best :)
I was following the discussion about expected/outcome/result very closely and I can absolutely see the usefulness of such a library (I explicitly discarded option here).
I am not sure if this post should count as a review, mainly because I disagree with the fundamental design decisions (that includes expected as defined in D0323R2) and therefor would cast my vote as in "not ready yet", I hope I can convey my points below. Wow, first notice I have of this disagreement. It is better later than never.
Still not sure if everything I said really makes sense, I just felt this line of thought might be interesting to the general discussion.
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
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
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.
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.
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.
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 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...
Second, I think Niall raised a valid about outcome being a framework for interoperability (completely orthogonal to the first point). However, I totally miss this from the proposed library, most pressing are non intrusive mechanisms. For that purpose I postulate, that a mechanism to transform between different unexpected results, that is: various error codes etc. However, for that to work, one would of course need a properly defined concept, for example, as Vicente suggested "EitherValue", and a mechanism to coerce one error type into another, maybe through ADL, or traits specialization or whatever.
There will be such a proposal as a generalization of Nullable based on what I named PossiblyValued.. https://github.com/viboes/std-make/blob/master/doc/proposal/nullable/D0196R3...
That's a good starting point!
With that in place, one could simply define the different EitherValue types, there is no need that everything needs to be in the form of "basic_XXX". For the library under review, this would be perfectly sufficient: template
class expected; template <typename T> using result = expected ; template <typename T> using outcome = expected >; We will need to have a specialization of expected > as we have an index for variant. The revision 2 of the expected proposal talks of a expected . That is, given that we have either a value or unexpected, we can convert expected
to expected if T is convertible to U and E1 "coercable" (with whichever mechanism) to E2. I have added this conversion constructor recently to the expected proposal as the result of my understanding of the need of Outcome and I hope we will discuss it in Toronto.
Cool!
If we then have a generic mechanism to get from a (possibly user defined "E") to an exception, I completely miss the point of the outcome template.
And why not to throw E?
throw E; is certainly a nice default, but it really should be customisable. For example, for std::exception_ptr, you probably want to throw the underlying exception? What if you have exceptions disabled, do you want to give users the chance to implement whatever they want?
All optimizations can then easily be put as implementation details and the generic expected
will probably suffice for most use cases, for everything else, we can implement special types which conform to our concepts and implement the error conversion mechanisms. This will most likely also work with different APIs/ABIs. The main problem is that we don't have here the generic interface and it is for this reason we are discussing on the details of a concrete class. The original expected proposal has fmap, bind, catch_error functions. We have removed them form expected since the last revision, but we need now to have a generic interface for those functions.
Those should be the next step once we have the underlying concepts ready. I was made aware of a very interesting experiment recently ... mainly to use the mechanisms defined in the coroutine TS for optional. Ignoring that co_await sounds awkward when dealing with PossiblyValued objects, it almost gives you everything you need to handle those objects with the nice syntax people usually refer to as "monadic".
For me expected should have the minimum, everything else should be associated to a specific concept, as Nullable, PossiblyValued, MonadError, SumType.
https://github.com/viboes/std-make/blob/master/doc/proposal/monads/Monads.md
https://github.com/viboes/std-make/tree/master/include/experimental/fundamen...
https://github.com/viboes/std-make/tree/master/include/experimental/fundamen...
https://github.com/viboes/std-make/tree/master/include/experimental/fundamen...
I guess we are not fundamentally disagreeing. I would really like to see "monad" to vanish again from our vocabulary though, while it is certainly a nice underlying theory, C++ just misses too much to ever really define Monads properly without making fun itself (Type Categories?, Concepts are not really the same...).
Vicente
-- Thomas Heller Friedrich-Alexander-Universität Erlangen-Nürnberg Department Informatik - Lehrstuhl Rechnerarchitektur Martensstr. 3 91058 Erlangen Tel.: 09131/85-27018 Fax: 09131/85-27912 Email: thomas.heller@fau.de
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
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.
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.
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
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.
Second, I think Niall raised a valid about outcome being a framework for interoperability (completely orthogonal to the first point). However, I totally miss this from the proposed library, most pressing are non intrusive mechanisms. For that purpose I postulate, that a mechanism to transform between different unexpected results, that is: various error codes etc. However, for that to work, one would of course need a properly defined concept, for example, as Vicente suggested "EitherValue", and a mechanism to coerce one error type into another, maybe through ADL, or traits specialization or whatever.
There will be such a proposal as a generalization of Nullable based on what I named PossiblyValued.. https://github.com/viboes/std-make/blob/master/doc/proposal/nullable/D0196R3...
That's a good starting point!
Comments welcome (privately or on the std--proposal ML)
With that in place, one could simply define the different EitherValue types, there is no need that everything needs to be in the form of "basic_XXX". For the library under review, this would be perfectly sufficient: template
class expected; template <typename T> using result = expected ; template <typename T> using outcome = expected >; We will need to have a specialization of expected > as we have an index for variant. The revision 2 of the expected proposal talks of a expected . That is, given that we have either a value or unexpected, we can convert expected
to expected if T is convertible to U and E1 "coercable" (with whichever mechanism) to E2. I have added this conversion constructor recently to the expected proposal as the result of my understanding of the need of Outcome and I hope we will discuss it in Toronto. Cool!
:)
If we then have a generic mechanism to get from a (possibly user defined "E") to an exception, I completely miss the point of the outcome template.
And why not to throw E?
throw E; is certainly a nice default, but it really should be customisable. For example, for std::exception_ptr, you probably want to throw the underlying exception?
I've a problem by fixing some hardcoded customization as for exception_ptr and error_code as some are suggesting, and don't allowing the user to customize their errors. Maybe this is not the best, but it could be what we are able to agree on. My expected implementation allows the user to customize the exception to throw. I did it this way because we need it for exception_ptr and people wanted to have other exceptions than bad_expected_access<E>.
What if you have exceptions disabled, do you want to give users the chance to implement whatever they want? For a Boost library I will follow what Boost.Exception proposes already.
All optimizations can then easily be put as implementation details and the generic expected
will probably suffice for most use cases, for everything else, we can implement special types which conform to our concepts and implement the error conversion mechanisms. This will most likely also work with different APIs/ABIs. The main problem is that we don't have here the generic interface and it is for this reason we are discussing on the details of a concrete class. The original expected proposal has fmap, bind, catch_error functions. We have removed them form expected since the last revision, but we need now to have a generic interface for those functions.
Those should be the next step once we have the underlying concepts ready. I was made aware of a very interesting experiment recently ... mainly to use the mechanisms defined in the coroutine TS for optional. I'm aware also of a customizing expected
with the coroutine TS. I can share a POC I got ( I don't remember from how) if people are interested in. Ignoring that co_await sounds awkward when dealing with PossiblyValued objects, it almost gives you everything you need to handle those objects with the nice syntax people usually refer to as "monadic". Right. This is something we should have if expected and coroutine TS are accepted. Maybe we could add *try* for PossibleValued types. For me expected should have the minimum, everything else should be associated to a specific concept, as Nullable, PossiblyValued, MonadError, SumType.
https://github.com/viboes/std-make/blob/master/doc/proposal/monads/Monads.md
https://github.com/viboes/std-make/tree/master/include/experimental/fundamen...
https://github.com/viboes/std-make/tree/master/include/experimental/fundamen...
https://github.com/viboes/std-make/tree/master/include/experimental/fundamen...
I guess we are not fundamentally disagreeing. I would really like to see "monad" to vanish again from our vocabulary though, while it is certainly a nice underlying theory, C++ just misses too much to ever really define Monads properly without making fun itself (Type Categories?, Concepts are not really the same...). Agreed. We can not define type classes with concepts as there is some times a universal quantification. Anyway, we are able to provide a monadic interface in C++. We have it already in Boost.Hana.
Thanks for your the feedback, Vicente
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 ;) 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.
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.
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...
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
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
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
2017-05-26 9:03 GMT+02:00 Vicente J. Botet Escriba via Boost < boost@lists.boost.org>:
Le 26/05/2017 à 08:22, Thomas Heller a écrit :
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 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.
BTW, is the scope of use cases we want to cover with `expected` outlined clearly? People arrive at different optimum solutions when they start with different goals in mind.
2017-05-26 9:03 GMT+02:00 Vicente J. Botet Escriba via Boost < boost@lists.boost.org>:
Le 26/05/2017 à 08:22, Thomas Heller a écrit :
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 the future becomes ready we have expected .
`optional
Le 26/05/2017 à 23:23, Andrzej Krzemienski via Boost a écrit :
2017-05-26 9:03 GMT+02:00 Vicente J. Botet Escriba via Boost < boost@lists.boost.org>:
Le 26/05/2017 à 08:22, Thomas Heller a écrit :
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 the future becomes ready we have expected . `optional
>` is still not enough for the `future<T>`, because in `future` `T` is allowed to be an lvalue reference. :)
Right :) It cannot be neither std::variant
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.
Everyone else hated this idea, but what do you think of: - T& .value() returns a reference to the value, non-destructive. - T .get() returns the value by value, destructive. I personally like this plan, but no one agrees with me. I do agree that the availability of copy construction muddies clarity of usage very significantly - a separate move only type implementing .get() destructively from one implementing .value() non-destructively looks much cleaner. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
AMDG On 05/26/2017 07:43 AM, Niall Douglas via Boost wrote:
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.
Everyone else hated this idea, but what do you think of:
- T& .value() returns a reference to the value, non-destructive. - T .get() returns the value by value, destructive.
I hate it too. Who (besides you, the author) will be able to remember which one is which. In Christ, Steven Watanabe
On Sun, May 28, 2017 at 10:40 AM, Steven Watanabe via Boost
Everyone else hated this idea, but what do you think of:
- T& .value() returns a reference to the value, non-destructive. - T .get() returns the value by value, destructive.
I have a class with similar behavior, I named the function which returns the value destructively T::release() to make it clear at call sites. Example: template<class T> class Holder { T t_; public: T release() { return std::move(t_); } }; Thanks
- T& .value() returns a reference to the value, non-destructive. - T .get() returns the value by value, destructive.
I have a class with similar behavior, I named the function which returns the value destructively T::release() to make it clear at call sites.
Example:
template<class T> class Holder { T t_; public: T release() { return std::move(t_); } };
Meh, .release() makes me think of smart pointers. Though afio::file_handle does have a .release() to detach the native handle. I'll ponder it. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
participants (6)
-
Andrzej Krzemienski
-
Niall Douglas
-
Steven Watanabe
-
Thomas Heller
-
Vicente J. Botet Escriba
-
Vinnie Falco