[outcome] outcome without empty state?
Niall, What do you think about the following alternate interface of outcome types? 1. Remove option<T> altogether. 2. result<T> (and outcome<T>) does not have a separate empty state. 3. A default-constructed result<T> is initialized as if `result<T>{error_code_extended{}}`. 4. Function r.empty() is implemented as `r.has_error() && !error()`. 5. Framework requires that type E in `expected<T, E>` is default-constructible, and its default-constructed value represents no error condition (already the case for exception_ptr, error_code). The rationale behind this is my assumption that `option<T>` is not as useful as the other types. (Or can you give an example to the contrary?) That there was no initial business need for an empty state, and the examples that illustrate its usefulness are just exploring how we can make use of it now that it is there. (Or am I wrong here?) I noticed a number of people report negative feedback about this empty state. Getting rid of it would take result<T> closer to "either T or reason for failure". What do you think? Regards, &rzej;
Le 24/05/2017 à 15:15, Andrzej Krzemienski via Boost a écrit :
Niall, What do you think about the following alternate interface of outcome types?
1. Remove option<T> altogether.
2. result<T> (and outcome<T>) does not have a separate empty state. Yes please.
3. A default-constructed result<T> is initialized as if `result<T>{error_code_extended{}}`. It is curious, the original expected<E,T> default initialized to E{} :) as I copied from optional. We moved to expected<T,E> and a default construction as if T{} for two reason, to behave like T and to avoid this default construction of exception_ptr.
4. Function r.empty() is implemented as `r.has_error() && !error()`.
So you are requesting that error is explicitly convertible to bool. This could work for outcome and result as the errors provide it but not for expected<T,E>. I don't believe the library should make use of this bool conversion. Even the opposite. I will add a pre-condition that the Error value if convertible to bool is equal to false (this can be also cheched at run-time).
5. Framework requires that type E in `expected<T, E>` is default-constructible, and its default-constructed value represents no error condition (already the case for exception_ptr, error_code).
This is something that the library cannot check without adding more constraints. The interpretation that the user does of each one of the values of E is up to him. If the Error parameter is not an error but a status, maybe the user should consider the status_value<S, T> proposal, which is isomorphic to pair<S, optional <T>>. In this proposal, some of the status values mean succeed and others mean failure. The second is not empty if the status mean success. The default construction of exception_ptr is IMHO an accident. If the user uses values of Error that doesn't mean an error, you have a tri-state. IMO, the use error_code{} or exception_ptr{} with outcome or expected is an ERROR. At the end I'm not sure result<T> or expected<T> should have a default constructor. Note that we want variables initialized at the declaration and the default construction of these types is always artificial.
The rationale behind this is my assumption that `option<T>` is not as useful as the other types. (Or can you give an example to the contrary?) That there was no initial business need for an empty state, and the examples that illustrate its usefulness are just exploring how we can make use of it now that it is there. (Or am I wrong here?)
I noticed a number of people report negative feedback about this empty state. Getting rid of it would take result<T> closer to "either T or reason for failure".
What do you think?
I agree with the non-empty, but disagree with the possibility of Error to mean a Status. This make the user code more complex. Concerning the default construction, I'm inclined to think that in this cases the best is to don't initialize as a T or as E. Note that this doesn't mean to add an additional state. The result of default construction is just a partial initialized state that needs either to be assigned or destructed, as if we had do a move. Trying to use has_value on this state will be UB. The advantage is that IMO, is that we don't spend more time than we need. Not all types should have a well formed object as default value (See e.g. chrono::duration, Date). Best, Vicente
4. Function r.empty() is implemented as `r.has_error() && !error()`.
So you are requesting that error is explicitly convertible to bool. This could work for outcome and result as the errors provide it but not for expected<T,E>. I don't believe the library should make use of this bool conversion. Even the opposite. I will add a pre-condition that the Error value if convertible to bool is equal to false (this can be also cheched at run-time).
std::error_code does not have a constexpr constructor, and does not have a constexpr .value() nor explicit operator bool(). Very unfortunate. It also precludes doing a generic check that a default constructed E tests to boolean false, so you will need that generic check plus a special override for std::error_code and subclasses of std::error_code.
5. Framework requires that type E in `expected<T, E>` is default-constructible, and its default-constructed value represents no error condition (already the case for exception_ptr, error_code). This is something that the library cannot check without adding more constraints. The interpretation that the user does of each one of the values of E is up to him.
I would agree. A null error_code has the convention of "no error here", but as I've explained in previous discussion, because of the C++ standard requiring system_category with a code of 0 for default construction, it rules out anyone *portably* treating a null error_code as "no error here". Until the C++ standard gets fixed, we cannot assume this of the null error code any more than the Filesystem TS does (which it does, incidentally, and it should not).
If the user uses values of Error that doesn't mean an error, you have a tri-state. IMO, the use error_code{} or exception_ptr{} with outcome or expected is an ERROR.
I disagree. That is on the end user to decide, not us.
At the end I'm not sure result<T> or expected<T> should have a default constructor. Note that we want variables initialized at the declaration and the default construction of these types is always artificial.
I too felt this, which was one of my rationales for the formal empty state. Completely eliminating the default constructor altogether does seem attractive ... but it would be a usability pain for end users.
Concerning the default construction, I'm inclined to think that in this cases the best is to don't initialize as a T or as E. Note that this doesn't mean to add an additional state. The result of default construction is just a partial initialized state that needs either to be assigned or destructed, as if we had do a move. Trying to use has_value on this state will be UB.
Ah, more UB. Yay.
The advantage is that IMO, is that we don't spend more time than we need. Not all types should have a well formed object as default value (See e.g. chrono::duration, Date).
If the formal empty state is to be removed, then either you default construct T, or E, or you go the UB route. I still find the formal empty state the most attractive, but I recognise no one agrees with me. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
What about providing no default constructor? There are two valid choices so why surprise half the users? Yes, it makes it a bit harder to use in arrays, but how often would it need to be stored in arrays anyways. Jonathan On May 24, 2017 10:38 PM, "Niall Douglas via Boost" <boost@lists.boost.org> wrote:
4. Function r.empty() is implemented as `r.has_error() && !error()`.
So you are requesting that error is explicitly convertible to bool. This could work for outcome and result as the errors provide it but not for expected<T,E>. I don't believe the library should make use of this bool conversion. Even the opposite. I will add a pre-condition that the Error value if convertible to bool is equal to false (this can be also cheched at run-time).
std::error_code does not have a constexpr constructor, and does not have a constexpr .value() nor explicit operator bool(). Very unfortunate. It also precludes doing a generic check that a default constructed E tests to boolean false, so you will need that generic check plus a special override for std::error_code and subclasses of std::error_code.
5. Framework requires that type E in `expected<T, E>` is default-constructible, and its default-constructed value represents no error condition (already the case for exception_ptr, error_code). This is something that the library cannot check without adding more constraints. The interpretation that the user does of each one of the values of E is up to him.
I would agree.
A null error_code has the convention of "no error here", but as I've explained in previous discussion, because of the C++ standard requiring system_category with a code of 0 for default construction, it rules out anyone *portably* treating a null error_code as "no error here".
Until the C++ standard gets fixed, we cannot assume this of the null error code any more than the Filesystem TS does (which it does, incidentally, and it should not).
If the user uses values of Error that doesn't mean an error, you have a tri-state. IMO, the use error_code{} or exception_ptr{} with outcome or expected is an ERROR.
I disagree. That is on the end user to decide, not us.
At the end I'm not sure result<T> or expected<T> should have a default constructor. Note that we want variables initialized at the declaration and the default construction of these types is always artificial.
I too felt this, which was one of my rationales for the formal empty state. Completely eliminating the default constructor altogether does seem attractive ... but it would be a usability pain for end users.
Concerning the default construction, I'm inclined to think that in this cases the best is to don't initialize as a T or as E. Note that this doesn't mean to add an additional state. The result of default construction is just a partial initialized state that needs either to be assigned or destructed, as if we had do a move. Trying to use has_value on this state will be UB.
Ah, more UB. Yay.
The advantage is that IMO, is that we don't spend more time than we need. Not all types should have a well formed object as default value (See e.g. chrono::duration, Date).
If the formal empty state is to be removed, then either you default construct T, or E, or you go the UB route. I still find the formal empty state the most attractive, but I recognise no one agrees with me.
Niall
-- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/ mailman/listinfo.cgi/boost
On 25/05/2017 08:44, Jonathan Müller wrote:
What about providing no default constructor? There are two valid choices so why surprise half the users?
Yes, it makes it a bit harder to use in arrays, but how often would it need to be stored in arrays anyways.
Surprisingly often, if it ends up being used to represent the collection of results from methods executed in sequence or in parallel (although perhaps future<> is more suited to that task, since they're more likely to be asynchronous). Speaking of which, it seems like outcome<> is essentially just trying to be a not-asynchronous version of future<> that can elect to transport a std::error_code instead of a std::exception_ptr. So most likely its design decisions should be inspired mostly from that (and any pain points that people have experienced with that interface). Perhaps it even should be designed in such a way that std::future itself could be based on it?
Le 25/05/2017 à 01:24, Gavin Lambert via Boost a écrit :
On 25/05/2017 08:44, Jonathan Müller wrote:
What about providing no default constructor? There are two valid choices so why surprise half the users?
Yes, it makes it a bit harder to use in arrays, but how often would it need to be stored in arrays anyways.
Surprisingly often, if it ends up being used to represent the collection of results from methods executed in sequence or in parallel (although perhaps future<> is more suited to that task, since they're more likely to be asynchronous).
I need to store a collection of results even if the tasks are not executed concurrently. Not all application need the complexity of concurrency. Vicente
On 25/05/2017 22:08, Vicente J. Botet Escriba wrote:
Le 25/05/2017 à 01:24, Gavin Lambert a écrit :
On 25/05/2017 08:44, Jonathan Müller wrote:
What about providing no default constructor? There are two valid choices so why surprise half the users?
Yes, it makes it a bit harder to use in arrays, but how often would it need to be stored in arrays anyways.
Surprisingly often, if it ends up being used to represent the collection of results from methods executed in sequence or in parallel (although perhaps future<> is more suited to that task, since they're more likely to be asynchronous).
I need to store a collection of results even if the tasks are not executed concurrently. Not all application need the complexity of concurrency.
Which is why I included "in sequence".
Le 26/05/2017 à 00:58, Gavin Lambert via Boost a écrit :
On 25/05/2017 22:08, Vicente J. Botet Escriba wrote:
Le 25/05/2017 à 01:24, Gavin Lambert a écrit :
On 25/05/2017 08:44, Jonathan Müller wrote:
What about providing no default constructor? There are two valid choices so why surprise half the users?
Yes, it makes it a bit harder to use in arrays, but how often would it need to be stored in arrays anyways.
Surprisingly often, if it ends up being used to represent the collection of results from methods executed in sequence or in parallel (although perhaps future<> is more suited to that task, since they're more likely to be asynchronous).
I need to store a collection of results even if the tasks are not executed concurrently. Not all application need the complexity of concurrency.
Which is why I included "in sequence". My bad. I missed the word or mixed sequence and collection :(
Vicente
Le 24/05/2017 à 22:44, Jonathan Müller via Boost a écrit :
What about providing no default constructor? There are two valid choices so why surprise half the users?
Yes, it makes it a bit harder to use in arrays, but how often would it need to be stored in arrays anyways.
I need a default constructor even if uninitialized. What do you think an uninitialized default constructor? Vicente
On 26/05/2017 00:30, Peter Dimov wrote:
Vicente J. Botet Escriba wrote:
I need a default constructor even if uninitialized. What do you think an uninitialized default constructor?
Awful, of course. Gratuitous undefined behavior is never good.
+1 After some exchanges with Jonathan I believe now that we shouldn't have
Le 26/05/2017 à 00:52, Gavin Lambert via Boost a écrit : the uninitialized default constructor. My arguments were wrong and this makes the implementation less efficient and more complex. So either we have the status-quo or we delete it. I start to think that maybe the best would be to remove it :( Best, Vicente
2017-05-26 6:53 GMT+02:00 Vicente J. Botet Escriba via Boost < boost@lists.boost.org>:
Le 26/05/2017 à 00:52, Gavin Lambert via Boost a écrit :
On 26/05/2017 00:30, Peter Dimov wrote:
Vicente J. Botet Escriba wrote:
I need a default constructor even if uninitialized.
What do you think an uninitialized default constructor?
Awful, of course. Gratuitous undefined behavior is never good.
+1
After some exchanges with Jonathan I believe now that we shouldn't have the uninitialized default constructor. My arguments were wrong and this makes the implementation less efficient and more complex.
So either we have the status-quo or we delete it.
I start to think that maybe the best would be to remove it :(
Let me just recall her the suggestion from Peter made in the other thread. Mybe have two types: `expected<T, E>` does not have the empty state or default constructor -- it is used as function's return type `prepare_expected<T, E>` is either T or E or Empty -- you use it inside function to build expected<T, E>: ``` expected<T, E> f() { prepare_expected<T, E> ans; // populate (or not) ans return ans; // in this conversion you can do all sorts of checks } ``` When it comes to converting from `prepare_expected<T, E>` to `expected<T, E>`, you have plenty of choices: require manual conversion, narrow-contract to be checked, a defensive if and throw... I am just not sure if performance can be negatively impacted with this. Regards, &rzej;
2017-05-26 6:53 GMT+02:00 Vicente J. Botet Escriba via Boost < boost@lists.boost.org>:
Le 26/05/2017 à 00:52, Gavin Lambert via Boost a écrit :
On 26/05/2017 00:30, Peter Dimov wrote:
Vicente J. Botet Escriba wrote:
I need a default constructor even if uninitialized.
What do you think an uninitialized default constructor?
Awful, of course. Gratuitous undefined behavior is never good.
+1
After some exchanges with Jonathan I believe now that we shouldn't have the uninitialized default constructor. My arguments were wrong and this makes the implementation less efficient and more complex.
So either we have the status-quo or we delete it.
I start to think that maybe the best would be to remove it :(
Let me just recall her the suggestion from Peter made in the other thread. Mybe have two types: `expected<T, E>` does not have the empty state or default constructor -- it is used as function's return type `prepare_expected<T, E>` is either T or E or Empty -- you use it inside function to build expected<T, E>:
``` expected<T, E> f() { prepare_expected<T, E> ans; // populate (or not) ans return ans; // in this conversion you can do all sorts of checks } ``` The kind of code that declares a variable and then populate or not something can always be refactored to a smaller function that returns
Le 26/05/2017 à 14:54, Andrzej Krzemienski via Boost a écrit : the value to initialize the variable. See C++ guidelines. Declare as close as possible -> then initialize at the declarations -> then declare it const if not modified elsewhere. What is wrong by returning just expected when you know the result.
When it comes to converting from `prepare_expected<T, E>` to `expected<T, E>`, you have plenty of choices: require manual conversion, narrow-contract to be checked, a defensive if and throw... I am just not sure if performance can be negatively impacted with this.
I suspect that there will be a performance impact. Whether it is minor or not would depend on the context. Vicente
Let me just recall her the suggestion from Peter made in the other thread. Mybe have two types: `expected<T, E>` does not have the empty state or default constructor -- it is used as function's return type `prepare_expected<T, E>` is either T or E or Empty -- you use it inside function to build expected<T, E>:
``` expected<T, E> f() { prepare_expected<T, E> ans; // populate (or not) ans return ans; // in this conversion you can do all sorts of checks } ```
When it comes to converting from `prepare_expected<T, E>` to `expected<T, E>`, you have plenty of choices: require manual conversion, narrow-contract to be checked, a defensive if and throw... I am just not sure if performance can be negatively impacted with this.
I originally considered this design for Outcome's API. I ended up rejecting it because it's superfluous if you have correctly designed your expected/outcome/whatever in the first place. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
2017-05-26 17:59 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
Let me just recall her the suggestion from Peter made in the other thread. Mybe have two types: `expected<T, E>` does not have the empty state or default constructor -- it is used as function's return type `prepare_expected<T, E>` is either T or E or Empty -- you use it inside function to build expected<T, E>:
``` expected<T, E> f() { prepare_expected<T, E> ans; // populate (or not) ans return ans; // in this conversion you can do all sorts of checks } ```
When it comes to converting from `prepare_expected<T, E>` to `expected<T, E>`, you have plenty of choices: require manual conversion, narrow-contract to be checked, a defensive if and throw... I am just not sure if performance can be negatively impacted with this.
I originally considered this design for Outcome's API.
I ended up rejecting it because it's superfluous if you have correctly designed your expected/outcome/whatever in the first place.
What does "correctly" mean here?
When it comes to converting from `prepare_expected<T, E>` to `expected<T, E>`, you have plenty of choices: require manual conversion, narrow-contract to be checked, a defensive if and throw... I am just not sure if performance can be negatively impacted with this.
I originally considered this design for Outcome's API.
I ended up rejecting it because it's superfluous if you have correctly designed your expected/outcome/whatever in the first place.
What does "correctly" mean here?
Well, indeed :) Where I'm aiming, as you'll see in the summary design document I'll post shortly, is that the implicit conversion semantics will provide the ability to use a less representative variety to construct things, but return into a more representative variety. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
Niall Douglas wrote:
Where I'm aiming, as you'll see in the summary design document I'll post shortly, is that the implicit conversion semantics will provide the ability to use a less representative variety to construct things, but return into a more representative variety.
This holds in principle but I'm not sure that result<T> should convert into the hypothetical result_e<T>. From the discussion so far, I got the impression that we don't want to return empty results from functions; it seems to follow that result_e<T> would not be a return type, but the type of the local variable in the function that is initially empty but eventually acquires either a value or an error before being returned as result<T>.
Where I'm aiming, as you'll see in the summary design document I'll post shortly, is that the implicit conversion semantics will provide the ability to use a less representative variety to construct things, but return into a more representative variety.
This holds in principle but I'm not sure that result<T> should convert into the hypothetical result_e<T>. From the discussion so far, I got the impression that we don't want to return empty results from functions; it seems to follow that result_e<T> would not be a return type, but the type of the local variable in the function that is initially empty but eventually acquires either a value or an error before being returned as result<T>.
That certainly is one design choice. I definitely have functions in KernelTest and AFIO which would be returning a result_e<T> as the empty return is part of the function's public contract. I gave an example of such a function a few days ago. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
2017-05-27 2:12 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
Where I'm aiming, as you'll see in the summary design document I'll post shortly, is that the implicit conversion semantics will provide the ability to use a less representative variety to construct things, but return into a more representative variety.
This holds in principle but I'm not sure that result<T> should convert into the hypothetical result_e<T>. From the discussion so far, I got the impression that we don't want to return empty results from functions; it seems to follow that result_e<T> would not be a return type, but the type of the local variable in the function that is initially empty but eventually acquires either a value or an error before being returned as result<T>.
That certainly is one design choice. I definitely have functions in KernelTest and AFIO which would be returning a result_e<T> as the empty return is part of the function's public contract. I gave an example of such a function a few days ago.
Sorry, but the amount of threads to follow is getting difficult. Could you point again to the example? Regards, &rzej;
This holds in principle but I'm not sure that result<T> should convert into the hypothetical result_e<T>. From the discussion so far, I got the impression that we don't want to return empty results from functions; it seems to follow that result_e<T> would not be a return type, but the type of the local variable in the function that is initially empty but eventually acquires either a value or an error before being returned as result<T>.
That certainly is one design choice. I definitely have functions in KernelTest and AFIO which would be returning a result_e<T> as the empty return is part of the function's public contract. I gave an example of such a function a few days ago.
Sorry, but the amount of threads to follow is getting difficult. Could you point again to the example?
http://boost.2283326.n4.nabble.com/review-Review-of-Outcome-starts-Fri-19-Ma... Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
2017-05-27 16:14 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
This holds in principle but I'm not sure that result<T> should convert into the hypothetical result_e<T>. From the discussion so far, I got the impression that we don't want to return empty results from functions; it seems to follow that result_e<T> would not be a return type, but the type of the local variable in the function that is initially empty but eventually acquires either a value or an error before being returned as result<T>.
That certainly is one design choice. I definitely have functions in KernelTest and AFIO which would be returning a result_e<T> as the empty return is part of the function's public contract. I gave an example of such a function a few days ago.
Sorry, but the amount of threads to follow is getting difficult. Could you point again to the example?
http://boost.2283326.n4.nabble.com/review-Review-of- Outcome-starts-Fri-19-May-tp4694317p4694417.html
Ok, let me quote it here again:
```
// If this is empty, workspaces are identical result<stl1z::filesystem::path> workspaces_not_identical = compare_directories<false, false>(current_test_kernel.working_directory, model_workspace);
// Propagate any error if(workspaces_not_identical.has_error()) testret = error_code_extended(make_error_code(kerneltest_errc::filesystem_comparison_internal_failure),
workspaces_not_identical.get_error().message().c_str(),
workspaces_not_identical.get_error().value());
// Set error with extended message of the path which differs else if(workspaces_not_identical.has_value()) testret = error_code_extended(make_error_code(kerneltest_errc::filesystem_comparison_failed),
workspaces_not_identical.get().string().c_str()); ```
So compare_directories() walks two directory structures using the Filesystem TS and compares them for equivalence using fuzzy criteria. Here we use the empty state to indicate "directory trees are identical", an error state to indicate an error occurred, and a valued state to give the path of the first item which differed.
So, what I am seeing here (correct me if I am wrong), is that conceptually (performance considerations excluded) a return type that clearly reflects your intentions would be: ``` result<optional<filesystem::path>> ``` That is: * either a path representing the first difference, or * no path (but no error) meaning that there was no difference, or * information or error that prefvented you from giving the answer But if you used `result<optional<filesystem::path>>` it would be suboptimal: you have one discriminator for `result` and one for `optional`. So you applied a "hack": since, result's discriminator can handle one additional state fo free, allow it to also represent optional objects. This departs from the philosophy you described earlier, that "empty" state is the most abnormal of all states. But all-in-all the code is faster (I think), but what if in some other project you find it convenient to return "either a filesystem::path or file_desriptor": ``` result<variant<filesystem::path, file_descriptor>> ``` And again, this is suboptimal, as the result's discriminator could also handle two states of the variant's discriminator. Are you going to provide yet another "shades" for `result`: ``` result_variant<filesystem::path, file_descriptor> ``` This is also a useful optimization, but should library handle all possible optimizations for particular problems is user's code? I believe users should apply such improvements themselves, not via a generic tool. Regards, &rzej;
So compare_directories() walks two directory structures using the Filesystem TS and compares them for equivalence using fuzzy criteria. Here we use the empty state to indicate "directory trees are identical", an error state to indicate an error occurred, and a valued state to give the path of the first item which differed.
So, what I am seeing here (correct me if I am wrong), is that conceptually (performance considerations excluded) a return type that clearly reflects your intentions would be:
``` result<optional<filesystem::path>> ```
That is: * either a path representing the first difference, or * no path (but no error) meaning that there was no difference, or * information or error that prefvented you from giving the answer
Correct for the example code I gave above.
But if you used `result<optional<filesystem::path>>` it would be suboptimal: you have one discriminator for `result` and one for `optional`.
I don't like typing .value().value(), correct.
So you applied a "hack": since, result's discriminator can handle one additional state fo free, allow it to also represent optional objects. This departs from the philosophy you described earlier, that "empty" state is the most abnormal of all states.
It's not a hack. The object provides a formal empty state for the programmer to do with as they wish. So I made use of that feature as it was intended for the programmer to use. Empty state still has the strongest, most abnormal default actions, but if you don't write code to trigger those default actions, then it's just a third state carrying no payload.
But all-in-all the code is faster (I think), but what if in some other project you find it convenient to return "either a filesystem::path or file_desriptor":
``` result<variant<filesystem::path, file_descriptor>> ```
And again, this is suboptimal, as the result's discriminator could also handle two states of the variant's discriminator. Are you going to provide yet another "shades" for `result`:
``` result_variant<filesystem::path, file_descriptor> ```
This is also a useful optimization, but should library handle all possible optimizations for particular problems is user's code? I believe users should apply such improvements themselves, not via a generic tool.
The difference between the former and the latter is that internal to the implementation you're always going to have an empty state, at least under my implementation. So it's a case of choosing to expose it publicly (result<T>) or not (expected<T>) for programmers to use it directly, or not. In other words, it costs the implementation absolutely nothing to expose the empty state to the public API. So we can provide optional-type semantics at zero added cost. We could not do that for variant extensions like you described. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
3. A default-constructed result<T> is initialized as if `result<T>{error_code_extended{}}`.
I feel lots of gut feelings against that default. It seems a bit of a hack. Also, I remember Expected used to do this as a default, but it was undone. I would assume someone from WG21 gave a very good reason why. I will say that I cannot think of a good technical reason not to default construct to a null error_code. But just because you can doesn't mean you should. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
On 25/05/2017 07:54, Niall Douglas wrote:
3. A default-constructed result<T> is initialized as if `result<T>{error_code_extended{}}`.
I feel lots of gut feelings against that default. It seems a bit of a hack.
Also, I remember Expected used to do this as a default, but it was undone. I would assume someone from WG21 gave a very good reason why.
I will say that I cannot think of a good technical reason not to default construct to a null error_code. But just because you can doesn't mean you should.
Visions of "An error occurred: The operation succeeded" dialog boxes scroll through my brain. I like Peter Dimov's suggestion of defaulting to a non-null error code, especially where T is not void. This requires explicitly acknowledging success. (I'm not sure about the case where T is void. I'm initially inclined to want the same for consistency, but I suspect that would be too painful to actually use in practice.) Perhaps a suitable default would be std::errc::state_not_recoverable? That seems appropriate while being fairly rare as a regular error code. (Or of course since we're talking about std::error_code, a custom error category that isn't convertible to errc could be used for this instead.)
Le 25/05/2017 à 01:45, Gavin Lambert via Boost a écrit :
On 25/05/2017 07:54, Niall Douglas wrote:
3. A default-constructed result<T> is initialized as if `result<T>{error_code_extended{}}`.
I feel lots of gut feelings against that default. It seems a bit of a hack.
Also, I remember Expected used to do this as a default, but it was undone. I would assume someone from WG21 gave a very good reason why.
I will say that I cannot think of a good technical reason not to default construct to a null error_code. But just because you can doesn't mean you should.
Visions of "An error occurred: The operation succeeded" dialog boxes scroll through my brain. Yes, I don't want that.
I like Peter Dimov's suggestion of defaulting to a non-null error code, especially where T is not void. This requires explicitly acknowledging success. (I'm not sure about the case where T is void. I'm initially inclined to want the same for consistency, but I suspect that would be too painful to actually use in practice.)
If it is uninitialized on the default constructor it will force you as well to state explicitly success or failure. No default is good. If you want to state explicitly an error until something else is said later on initialize it explicitly to this error. More I think on the uninitialized default constructor, more I like it. No surprise. "Explicit is better than Implicit" Vicente
On May 25, 2017 12:13, "Vicente J. Botet Escriba via Boost" < boost@lists.boost.org> wrote: More I think on the uninitialized default constructor, more I like it. No surprise. "Explicit is better than Implicit" If you ignore the fact that now everything has a precondition. If there is no sensible default, don't provide a default constructor!
Le 25/05/2017 à 14:25, Jonathan Müller via Boost a écrit :
On May 25, 2017 12:13, "Vicente J. Botet Escriba via Boost" < boost@lists.boost.org> wrote:
More I think on the uninitialized default constructor, more I like it. No surprise. "Explicit is better than Implicit"
If you ignore the fact that now everything has a precondition. If there is no sensible default, don't provide a default constructor!
I could agree with you in some cases, but not in general. What is the default for chrono::duration? Does it means that we should remove the default constructor? For the expected<T,E> case I believe I would prefer to have an uninitialized constructor than don't having one. But I can understand other have different view on that. E.g. I would like to include expected on a c-like struct that is not initialized at all and that the user initializes field by field. Not having a default constructor will reduce the applicability of such a vocabulary type a lot. Vicente
participants (6)
-
Andrzej Krzemienski
-
Gavin Lambert
-
Jonathan Müller
-
Niall Douglas
-
Peter Dimov
-
Vicente J. Botet Escriba