Error handling libraries and proposals

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
47 messages Options
123
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Error handling libraries and proposals

Boost - Dev mailing list
Last week it was brought to my attention that there are currently 5
different libraries and proposals for dealing with the problem of reporting
errors from noexcept functions, so I thought it would be useful to update
the Noexcept documentation with a short section comparing all the different
designs: https://zajo.github.io/boost-noexcept/#alternatives.

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Error handling libraries and proposals

Boost - Dev mailing list
On 10/07/2017 18:07, Emil Dotchevski via Boost wrote:
> Last week it was brought to my attention that there are currently 5
> different libraries and proposals for dealing with the problem of reporting
> errors from noexcept functions, so I thought it would be useful to update
> the Noexcept documentation with a short section comparing all the different
> designs: https://zajo.github.io/boost-noexcept/#alternatives.

Some notes on Outcome v2 related to your now out of date comparison:

* result and outcome can now be error type customised, same as all the
other libraries. For example, outcome<void, void, void> is legal, though
unusable.

* result and outcome are struct-like, some of the others are variant-like.

* I have compiled out by default an implementation of P0252 as well, but
it can be reenabled if I decide it's wise later.

Your table would be better if it listed the items in the headers rather
than by number, but for Outcome v2 the new values are:

yes, yes, yes, yes, no, yes, yes, yes.

(v2 result is specifically designed to be C struct layout compatible)

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
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Error handling libraries and proposals

Boost - Dev mailing list
On Mon, Jul 10, 2017 at 10:44 AM, Niall Douglas via Boost <
[hidden email]> wrote:

> On 10/07/2017 18:07, Emil Dotchevski via Boost wrote:
> > Last week it was brought to my attention that there are currently 5
> > different libraries and proposals for dealing with the problem of
> reporting
> > errors from noexcept functions, so I thought it would be useful to update
> > the Noexcept documentation with a short section comparing all the
> different
> > designs: https://zajo.github.io/boost-noexcept/#alternatives.
>
> Some notes on Outcome v2 related to your now out of date comparison:
>

Thank you for letting me know that the Outcome design has changed, again. :)


> * result and outcome can now be error type customised, same as all the
> other libraries.


Not all the other libraries. The Noexcept design does not force you specify
the possible error types for each function because, as we have learned from
the exception specifications fiasco, in non-trivial cases (usually in
generic library code) it is impossible to know what errors may be returned
by lower level functions. If the error handling library forces you to list
them anyway, in such use cases the two choices you're left with is to
translate (which is prone to errors and usually imperfect), or some form of
catch-all, e.g. unique_ptr<void>.

The reality is that not everyone is or will be using only std::error_code;
ideally, functions should be able to forward errors from lower level code
intact.


> For example, outcome<void, void, void> is legal, though
> unusable.
>

What would be the meaning of outcome<void,void>?

>
> , but for Outcome v2 the new values are:
>
> yes, yes, yes, yes, no, yes, yes, yes.
>

To me, your note that Outcome is struct-like rather than variant-like means
that it does not have strict value-or-error semantics, so the last one
would be "no". Also, could you expand on how Outcome can propagate errors
from a C-style callback or across language boundaries? I'm referring to use
cases like these: https://zajo.github.io/boost-noexcept/#example_lua

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Error handling libraries and proposals

Boost - Dev mailing list
> Thank you for letting me know that the Outcome design has changed, again. :)

It's only changed as per peer review feedback. If you trawl all 800+
emails and drag out a consensus view of the community, you end up with
highly inoffensive Outcome v2. Well, apart from copying std::variant's
constructor design, that's on me, but I think cloning the C++ standard
is an okay design choice.

>     For example, outcome<void, void, void> is legal, though
>     unusable.
>
> What would be the meaning of outcome<void,void>?

outcome<void, void> would equal outcome<void, void, std::exception_ptr>.

Such an object can store a void or an exception_ptr. Or rather, have the
state of a void or an exception_ptr.

>     , but for Outcome v2 the new values are:
>
>     yes, yes, yes, yes, no, yes, yes, yes.
>
>
> To me, your note that Outcome is struct-like rather than variant-like
> means that it does not have strict value-or-error semantics, so the last
> one would be "no".

Unless you enable the positive status support manually, a strict
enforcement of value or error is made. There is no longer an empty
state, so you literally must choose either valued, or errored. Anything
else won't compile.

> Also, could you expand on how Outcome can propagate
> errors from a C-style callback or across language boundaries? I'm
> referring to use cases like these:
> https://zajo.github.io/boost-noexcept/#example_lua

I specifically personally need result to be C layout compatible so AFIO
can have C SWIG bindings. Outcome v1 was C layout compatible too, but v2
is even more so because it's just a struct of the form:

struct
{
  T value;
  unsigned flags;
  EC error;
};

... and bit 0 of the unsigned means a T is present, and bit 2 means an
EC is present.

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
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Error handling libraries and proposals

Boost - Dev mailing list
On Mon, Jul 10, 2017 at 1:36 PM, Niall Douglas via Boost <
[hidden email]> wrote:

> outcome<void, void> would equal outcome<void, void, std::exception_ptr>.
>
> Such an object can store a void or an exception_ptr. Or rather, have the
> state of a void or an exception_ptr.
>

So, if I understand correctly, the second void means exception_ptr? Why
isn't it outcome<void,exception_ptr>? What are the semantic differences
between e.g. expected<int,exception_ptr> and outcome<int,void>?


> >     , but for Outcome v2 the new values are:
> >
> >     yes, yes, yes, yes, no, yes, yes, yes.
> >
> >
> > To me, your note that Outcome is struct-like rather than variant-like
> > means that it does not have strict value-or-error semantics, so the last
> > one would be "no".
>
> Unless you enable the positive status support manually, a strict
> enforcement of value or error is made.


Ah, so this means that it is optional, like in Noexcept. I'll reflect that
in the comparison table.


> > Also, could you expand on how Outcome can propagate
> > errors from a C-style callback or across language boundaries? I'm
> > referring to use cases like these:
> > https://zajo.github.io/boost-noexcept/#example_lua
>
> I specifically personally need result to be C layout compatible so AFIO
> can have C SWIG bindings. Outcome v1 was C layout compatible too, but v2
> is even more so because it's just a struct of the form:
>
> struct
> {
>   T value;
>   unsigned flags;
>   EC error;
> };
>
> ... and bit 0 of the unsigned means a T is present, and bit 2 means an
> EC is present.
>

It seems that here you are making the case I've been making, that in
general std::error_code is not sufficient, that an error-handling library
must be able to propagate errors of user-defined types, and not only
through an exception_ptr. I also think it is a good idea to be
C-compatible, but from the above it isn't clear to me how that works. Could
you please post a complete example, specifically how can outcome<T>
propagate errors from a C (not C++) API?

That said, if you look at the example I linked, you'll see that it is about
more than just being C-layout compatible, but also being able to propagate
an error from a third-party C-style callback which does not return your
struct. In case I'm missing something, could you post the outcome<T>
version of the example I linked?

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Error handling libraries and proposals

Boost - Dev mailing list
On 10/07/2017 22:06, Emil Dotchevski via Boost wrote:

> On Mon, Jul 10, 2017 at 1:36 PM, Niall Douglas via Boost <
> [hidden email]> wrote:
>
>> outcome<void, void> would equal outcome<void, void, std::exception_ptr>.
>>
>> Such an object can store a void or an exception_ptr. Or rather, have the
>> state of a void or an exception_ptr.
>>
>
> So, if I understand correctly, the second void means exception_ptr? Why
> isn't it outcome<void,exception_ptr>? What are the semantic differences
> between e.g. expected<int,exception_ptr> and outcome<int,void>?

Are you confusing result and outcome?

result<T, EC>

outcome<T, EC, E|P>

The default template parameters give exactly the same as with v1. But if
say you chose `result<int, long>` or `outcome<int, long, std::string>`
then that works too.

>>>     , but for Outcome v2 the new values are:
>>>
>>>     yes, yes, yes, yes, no, yes, yes, yes.
>>>
>>>
>>> To me, your note that Outcome is struct-like rather than variant-like
>>> means that it does not have strict value-or-error semantics, so the last
>>> one would be "no".
>>
>> Unless you enable the positive status support manually, a strict
>> enforcement of value or error is made.
>
>
> Ah, so this means that it is optional, like in Noexcept. I'll reflect that
> in the comparison table.

Eh, well, it defaults to strict enforcement of either, it can only be
different if you change a macro. And as the code matures I may make the
strict enforcement mandatory.

>>> Also, could you expand on how Outcome can propagate
>>> errors from a C-style callback or across language boundaries? I'm
>>> referring to use cases like these:
>>> https://zajo.github.io/boost-noexcept/#example_lua
>>
>> I specifically personally need result to be C layout compatible so AFIO
>> can have C SWIG bindings. Outcome v1 was C layout compatible too, but v2
>> is even more so because it's just a struct of the form:
>>
>> struct
>> {
>>   T value;
>>   unsigned flags;
>>   EC error;
>> };
>>
>> ... and bit 0 of the unsigned means a T is present, and bit 2 means an
>> EC is present.
>>
>
> It seems that here you are making the case I've been making, that in
> general std::error_code is not sufficient, that an error-handling library
> must be able to propagate errors of user-defined types, and not only
> through an exception_ptr.

I did take into account your feedback during the review, yes, and I've
definitely thrown you some meat in v2.

> I also think it is a good idea to be
> C-compatible, but from the above it isn't clear to me how that works. Could
> you please post a complete example, specifically how can outcome<T>
> propagate errors from a C (not C++) API?

That it cannot do. You could store it into TLS the same as you're doing
behind the scenes though. Outcome v2 is far more barebones than v1 was.
It does pretty much nothing above the absolute bare minimum.

> That said, if you look at the example I linked, you'll see that it is about
> more than just being C-layout compatible, but also being able to propagate
> an error from a third-party C-style callback which does not return your
> struct. In case I'm missing something, could you post the outcome<T>
> version of the example I linked?

You'd declare some TLS storage, same as you do internally, and reference
that. So say:

```
static thread_local result<void, do_work_error> state;

int do_work( lua_State * L ) noexcept {
    bool success=rand()%2;
    if( success )
        return lua_pushnumber(L,42), 1;
    else
    {
        state = do_work_error{};
        return lua_error(L);
    }
}
result<int, do_work_error> call_lua( lua_State * L ) noexcept {
    lua_getfield( L, LUA_GLOBALSINDEX, "call_do_work" );
    if( int err=lua_pcall(L,0,1,0) ) {
        lua_pop(L,1);
        return state;
    }
    else {
        int answer=lua_tonumber(L,-1);
        lua_pop(L,1);
        return answer;
    }
}
```

Which is almost as succinct as yours.

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
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Error handling libraries and proposals

Boost - Dev mailing list
On Mon, Jul 10, 2017 at 4:19 PM, Niall Douglas via Boost <
[hidden email]> wrote:

> On 10/07/2017 22:06, Emil Dotchevski via Boost wrote:
> > On Mon, Jul 10, 2017 at 1:36 PM, Niall Douglas via Boost <
> > [hidden email]> wrote:
> >
> >> outcome<void, void> would equal outcome<void, void, std::exception_ptr>.
> >>
> >> Such an object can store a void or an exception_ptr. Or rather, have the
> >> state of a void or an exception_ptr.
> >>
> >
> > So, if I understand correctly, the second void means exception_ptr? Why
> > isn't it outcome<void,exception_ptr>? What are the semantic differences
> > between e.g. expected<int,exception_ptr> and outcome<int,void>?
>
> Are you confusing result and outcome?
>
> result<T, EC>
>
> outcome<T, EC, E|P>
>
> The default template parameters give exactly the same as with v1. But if
> say you chose `result<int, long>` or `outcome<int, long, std::string>`
> then that works too.
>

I still don't understand what does it mean to use void. I don't understand
what this means:

"outcome<void, void> would equal outcome<void, void, std::exception_ptr>"


> >>>     , but for Outcome v2 the new values are:
> >>>
> >>>     yes, yes, yes, yes, no, yes, yes, yes.
> >>>
> >>>
> >>> To me, your note that Outcome is struct-like rather than variant-like
> >>> means that it does not have strict value-or-error semantics, so the
> last
> >>> one would be "no".
> >>
> >> Unless you enable the positive status support manually, a strict
> >> enforcement of value or error is made.
> >
> >
> > Ah, so this means that it is optional, like in Noexcept. I'll reflect
> that
> > in the comparison table.
>
> Eh, well, it defaults to strict enforcement of either, it can only be
> different if you change a macro. And as the code matures I may make the
> strict enforcement mandatory.
>

I think that this is a key design decision. So is whether or not you have
to enumerate the possible error types.

Take this for what it's worth, but the earlier outcome design was a lot
more focused. Reading the documentation, some of it sounded like
evangelization of std::error_code, so the conclusion I drew was that the
idea is that you don't specify the error types because you should only be
using std::error_code, except that in the real world you might get an
exception from somewhere, in which case outcome lets you stuff a
std::exception_ptr into it too. That makes sense, even if I think that it
is not practical to assume that everyone will jump on the std::error_code
train.

On the other hand, expected<T,E> (Vicente) says no, std::error_code is not
the only option so this should be a template parameter. On top of that,
expected<T,E...> (Peter) says well, at the very least you might get an
exception from somewhere so you have to be able to have e.g.
expected<T,std::error_code,std::exception_ptr>, so we'll take more than a
single E.

The other significant difference you introduced post-review was that
outcome no longer had strict value-or-error semantics. This too helped set
it apart.

But now, and it may be just me, but I am confused. What exactly is the
difference between outcome and the two flavors of expected we have? Is it
essentially the same as expected<T,E...>, except that in outcome the strict
value-or-error semantics (that you may remove later) are optional?


> >>> Also, could you expand on how Outcome can propagate
> >>> errors from a C-style callback or across language boundaries? I'm
> >>> referring to use cases like these:
> >>> https://zajo.github.io/boost-noexcept/#example_lua
> >>
> >> I specifically personally need result to be C layout compatible so AFIO
> >> can have C SWIG bindings. Outcome v1 was C layout compatible too, but v2
> >> is even more so because it's just a struct of the form:
> >>
> >> struct
> >> {
> >>   T value;
> >>   unsigned flags;
> >>   EC error;
> >> };
> >>
> >> ... and bit 0 of the unsigned means a T is present, and bit 2 means an
> >> EC is present.
> >>
> >
> > It seems that here you are making the case I've been making, that in
> > general std::error_code is not sufficient, that an error-handling library
> > must be able to propagate errors of user-defined types, and not only
> > through an exception_ptr.
>
> I did take into account your feedback during the review, yes, and I've
> definitely thrown you some meat in v2.
>

Ha, I'm honored. :)


> > I also think it is a good idea to be
> > C-compatible, but from the above it isn't clear to me how that works.
> Could
> > you please post a complete example, specifically how can outcome<T>
> > propagate errors from a C (not C++) API?
>
> That it cannot do. You could store it into TLS the same as you're doing
> behind the scenes though. Outcome v2 is far more barebones than v1 was.
> It does pretty much nothing above the absolute bare minimum.
>
> > That said, if you look at the example I linked, you'll see that it is
> about
> > more than just being C-layout compatible, but also being able to
> propagate
> > an error from a third-party C-style callback which does not return your
> > struct. In case I'm missing something, could you post the outcome<T>
> > version of the example I linked?
>
> You'd declare some TLS storage, same as you do internally, and reference
> that. So say:
>
> ```
> static thread_local result<void, do_work_error> state;
>
> int do_work( lua_State * L ) noexcept {
>     bool success=rand()%2;
>     if( success )
>         return lua_pushnumber(L,42), 1;
>     else
>     {
>         state = do_work_error{};
>         return lua_error(L);
>     }
> }
> result<int, do_work_error> call_lua( lua_State * L ) noexcept {
>     lua_getfield( L, LUA_GLOBALSINDEX, "call_do_work" );
>     if( int err=lua_pcall(L,0,1,0) ) {
>         lua_pop(L,1);
>         return state;
>     }
>     else {
>         int answer=lua_tonumber(L,-1);
>         lua_pop(L,1);
>         return answer;
>     }
> }
> ```
>
> Which is almost as succinct as yours.
>

This might be reasonable if it is a one-off thing, but consider that the
general case is a bit more complicated, possibly involving different
compilation units, and you do need to ensure that when the thread
terminates "state" doesn't contain an error. It may or may not make sense
to add something to that effect to Outcome.

By the way, this illustrates that errors and successful return values are
very different semantically, which is why at least sometimes it is
necessary to find a different channel to pass errors out. At which point
one must ask what is the downside of using TLS for the error object? What
is the design rationale for insisting on stuffing errors into return
values, moving them one level up at a time, when in reality only the
reporting and the handling code care about the error?

The benefits of always passing errors through TLS are 1) it effectively
decouples successful results from error conditions, which in turn means
that only the error-reporting and the error-handling code are coupled with
the error object or its type; and 2) it doesn't require the enumeration of
all possible error types that a function may "return" (I'll once again
refer the reader to exception specifications as to why that is a bad idea).
The reason why not requiring the enumeration is linked to TLS use is that
it requires the storage to be "large enough", which is perhaps too large to
stuff into a return value (since we can't assume that a dynamic allocation
is permissible.)

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Error handling libraries and proposals

Boost - Dev mailing list
>> Are you confusing result and outcome?
>>
>> result<T, EC>
>>
>> outcome<T, EC, E|P>
>>
>> The default template parameters give exactly the same as with v1. But if
>> say you chose `result<int, long>` or `outcome<int, long, std::string>`
>> then that works too.
>>
>
> I still don't understand what does it mean to use void. I don't understand
> what this means:
>
> "outcome<void, void> would equal outcome<void, void, std::exception_ptr>"

The default declaration for outcome is:

```
template <
  class R,
  class S = std::error_code,
  class P = std::exception_ptr,
  class NoValuePolicy = policy::default_outcome_policy<R, S, P>
>
requires
  (std::is_void<EC>::value || std::is_default_constructible<EC>::value)
  && (std::is_void<P>::value || std::is_default_constructible<P>::value)
class [[nodiscard]] outcome;
```

Yay the Concepts TS. Anyway, therefore `outcome<void, void>` equals
`outcome<void, void, std::exception_ptr>`.

Does it make sense now?

(I think you'll find the synposis at
https://ned14.github.io/outcome/synopsis/outcome/ clears everything up
for you)

> Take this for what it's worth, but the earlier outcome design was a lot
> more focused.

You may be forgetting my initial claims of a "multi-modal" design. v1
wore many hats. v2 has no head, so it cannot wear a hat.

> Reading the documentation, some of it sounded like
> evangelization of std::error_code, so the conclusion I drew was that the
> idea is that you don't specify the error types because you should only be
> using std::error_code, except that in the real world you might get an
> exception from somewhere, in which case outcome lets you stuff a
> std::exception_ptr into it too. That makes sense, even if I think that it
> is not practical to assume that everyone will jump on the std::error_code
> train.

That was purely a simplifying narrative which was taken due to
continuing Reddit confusion. v1 always could do far more that the
tutorial suggested. You could, in fact, customise any of the types to
anything you liked so long as you met an error_code or an exception_ptr
contract.

v2 now concepts matches instead. If you feed it a type matching an
error_code concept, it treats it as an error code, otherwise it does
not. Same for exception_ptr. Thus `outcome<int, int, int>` is legal, but
unusable, because you cannot construct one without resorting to UB.

> On the other hand, expected<T,E> (Vicente) says no, std::error_code is not
> the only option so this should be a template parameter. On top of that,
> expected<T,E...> (Peter) says well, at the very least you might get an
> exception from somewhere so you have to be able to have e.g.
> expected<T,std::error_code,std::exception_ptr>, so we'll take more than a
> single E.

v2 was designed to dovetail into Expected neatly. It's basically a
hugely simplified and thus much faster to compile subset. That should
allow Expected to take on much more monadic stuff, if Vicente prefers.

> The other significant difference you introduced post-review was that
> outcome no longer had strict value-or-error semantics. This too helped set
> it apart.

In the default configuration, outcome<T, EC, E|P> is only strictly
value-or-error, value-or-exception, or value-or-error+exception.

> But now, and it may be just me, but I am confused. What exactly is the
> difference between outcome and the two flavors of expected we have? Is it
> essentially the same as expected<T,E...>, except that in outcome the strict
> value-or-error semantics (that you may remove later) are optional?

It's a low level subset. Struct-based storage, not variant-based. Fast.
Lightweight. ABI stable. But not rich, it's a barebones type.

> This might be reasonable if it is a one-off thing, but consider that the
> general case is a bit more complicated, possibly involving different
> compilation units, and you do need to ensure that when the thread
> terminates "state" doesn't contain an error. It may or may not make sense
> to add something to that effect to Outcome.

v2 is designed to be subclassed into localised implementations in a
local namespace, which is a new thing. So it's very easy to add
additional behaviours and features to your localised implementation,
whilst retaining the cross-ABI interoperability between many localised
implementations.

v2 only provides the raw building block. What you do with it after it
totally up to you. For example, one could build your Noexcept library
with it quite easily, and thus "plug in" to Expected, P0650 etc.

> By the way, this illustrates that errors and successful return values are
> very different semantically, which is why at least sometimes it is
> necessary to find a different channel to pass errors out. At which point
> one must ask what is the downside of using TLS for the error object? What
> is the design rationale for insisting on stuffing errors into return
> values, moving them one level up at a time, when in reality only the
> reporting and the handling code care about the error?
>
> The benefits of always passing errors through TLS are 1) it effectively
> decouples successful results from error conditions, which in turn means
> that only the error-reporting and the error-handling code are coupled with
> the error object or its type; and 2) it doesn't require the enumeration of
> all possible error types that a function may "return" (I'll once again
> refer the reader to exception specifications as to why that is a bad idea).
> The reason why not requiring the enumeration is linked to TLS use is that
> it requires the storage to be "large enough", which is perhaps too large to
> stuff into a return value (since we can't assume that a dynamic allocation
> is permissible.)

v2 Outcome is way lower level than any of that. It's a raw vocabulary
type, pure and simple, nothing provided other than absolute bare
minimum. Ready to act as a foundation stone for bigger stuff.

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
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Error handling libraries and proposals

Boost - Dev mailing list
On Mon, Jul 10, 2017 at 5:58 PM, Niall Douglas via Boost <
[hidden email]> wrote:

> >> Are you confusing result and outcome?
> >>
> >> result<T, EC>
> >>
> >> outcome<T, EC, E|P>
> >>
> >> The default template parameters give exactly the same as with v1. But if
> >> say you chose `result<int, long>` or `outcome<int, long, std::string>`
> >> then that works too.
> >>
> >
> > I still don't understand what does it mean to use void. I don't
> understand
> > what this means:
> >
> > "outcome<void, void> would equal outcome<void, void, std::exception_ptr>"
>
> The default declaration for outcome is:
>
> ```
> template <
>   class R,
>   class S = std::error_code,
>   class P = std::exception_ptr,
>   class NoValuePolicy = policy::default_outcome_policy<R, S, P>
> >
> requires
>   (std::is_void<EC>::value || std::is_default_constructible<EC>::value)
>   && (std::is_void<P>::value || std::is_default_constructible<P>::value)
> class [[nodiscard]] outcome;
> ```
>
> Yay the Concepts TS. Anyway, therefore `outcome<void, void>` equals
> `outcome<void, void, std::exception_ptr>`.
>
> Does it make sense now?
>

No. Why would I pass void for S or P?

By the way, such policy-based design is probably not a ideal for
error-handling library return types, for the same reason shared_ptr<T> is
better than Alexandrescu's policy-based smart pointer, and for the same
reason function<T> (as it is now standardized) is better than function<T,A>
(as it was in Boost initially, before I fixed it): it leads to much
increased coupling, and ABI incompatibilities. The thing is, error objects,
like smart pointers and function objects, need to be as frictionless as
possible when crossing API boundaries, and policy-based designs work
directly against that goal.

> Take this for what it's worth, but the earlier outcome design was a lot
> > more focused.
>
> You may be forgetting my initial claims of a "multi-modal" design. v1
> wore many hats. v2 has no head, so it cannot wear a hat.
>

I'm afraid my reading is that you're shifting important design decisions to
the user, who has (by definition) lower qualifications in the domain of
error handling. Flexibility is not always good, after all we all have a C++
compiler and can handle errors with all the flexibility we need -- without
an error handling library.


> > Reading the documentation, some of it sounded like
> > evangelization of std::error_code, so the conclusion I drew was that the
> > idea is that you don't specify the error types because you should only be
> > using std::error_code, except that in the real world you might get an
> > exception from somewhere, in which case outcome lets you stuff a
> > std::exception_ptr into it too. That makes sense, even if I think that it
> > is not practical to assume that everyone will jump on the std::error_code
> > train.
>
> That was purely a simplifying narrative which was taken due to
> continuing Reddit confusion. v1 always could do far more that the
> tutorial suggested. You could, in fact, customise any of the types to
> anything you liked so long as you met an error_code or an exception_ptr
> contract.
>

I know. That makes sense, it's crystal clear.


> v2 now concepts matches instead. If you feed it a type matching an
> error_code concept, it treats it as an error code, otherwise it does
> not. Same for exception_ptr. Thus `outcome<int, int, int>` is legal, but
> unusable, because you cannot construct one without resorting to UB.
>

Assuming we agree that passing int for the P makes no sense at all:

1. Could you show a practical case that illustrates the need for using
anything but std::exception_ptr for P?

2. What is the benefit of P being semantically different from S? In other
words, what is the difference/benefit of outcome<T,S,P> over
expected<T,S,P>?


> > On the other hand, expected<T,E> (Vicente) says no, std::error_code is
> not
> > the only option so this should be a template parameter. On top of that,
> > expected<T,E...> (Peter) says well, at the very least you might get an
> > exception from somewhere so you have to be able to have e.g.
> > expected<T,std::error_code,std::exception_ptr>, so we'll take more than
> a
> > single E.
>
> v2 was designed to dovetail into Expected neatly. It's basically a
> hugely simplified and thus much faster to compile subset. That should
> allow Expected to take on much more monadic stuff, if Vicente prefers.
>
> > The other significant difference you introduced post-review was that
> > outcome no longer had strict value-or-error semantics. This too helped
> set
> > it apart.
>
> In the default configuration, outcome<T, EC, E|P> is only strictly
> value-or-error, value-or-exception, or value-or-error+exception.
>

What other options are out there? What could I do with a struct with 3
members, which outcome "strictly" forbids?


> > But now, and it may be just me, but I am confused. What exactly is the
> > difference between outcome and the two flavors of expected we have? Is it
> > essentially the same as expected<T,E...>, except that in outcome the
> strict
> > value-or-error semantics (that you may remove later) are optional?
>
> It's a low level subset. Struct-based storage, not variant-based. Fast.
> Lightweight. ABI stable. But not rich, it's a barebones type.
>

Are you claiming that outcome<T,S,P> is faster than expected<T,S,P>? I
don't see how could that be true. Perhaps I'm missing something.

I'm also puzzled by the ABI stability claim. I mean, policy-based designs
are only ABI-stable if most users use the same policies, which obviously
defies the point of using policies.


> > This might be reasonable if it is a one-off thing, but consider that the
> > general case is a bit more complicated, possibly involving different
> > compilation units, and you do need to ensure that when the thread
> > terminates "state" doesn't contain an error. It may or may not make sense
> > to add something to that effect to Outcome.
>
> v2 is designed to be subclassed into localised implementations in a
> local namespace, which is a new thing. So it's very easy to add
> additional behaviours and features to your localised implementation,
> whilst retaining the cross-ABI interoperability between many localised
> implementations.
>
> v2 only provides the raw building block. What you do with it after it
> totally up to you.


The question is what's the value in using Outcome as a building block? Why
would anyone bother, if using Outcome they're basically free to do whatever?


> For example, one could build your Noexcept library
> with it quite easily, and thus "plug in" to Expected, P0650 etc.
>

I think Noexcept is a lot more lightweight than you think. The internal
machinery it is based on (what I think you imagine your building block
would replace) is about 500 lines, out of about 800.

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Outcome v2 (was: Re: Error handling libraries and proposals)

Boost - Dev mailing list
>     The default declaration for outcome is:
>
>     ```
>     template <
>       class R,
>       class S = std::error_code,
>       class P = std::exception_ptr,
>       class NoValuePolicy = policy::default_outcome_policy<R, S, P>
>     >
>     requires
>       (std::is_void<EC>::value || std::is_default_constructible<EC>::value)
>       && (std::is_void<P>::value || std::is_default_constructible<P>::value)
>     class [[nodiscard]] outcome;
>     ```
>
>     Yay the Concepts TS. Anyway, therefore `outcome<void, void>` equals
>     `outcome<void, void, std::exception_ptr>`.
>
>     Does it make sense now?
>
>
> No. Why would I pass void for S or P?

You wouldn't, as the voided types are not particularly useful.

However as a fundamental vocabulary type highly useful in constexpr
metaprogramming, being able to be configured with void is flexible. It
can also be useful in internal macro boilerplate. And finally, Expected
permits expected<void, void>, and seeing as I intend to offer explicit
construction from any expected concept matching type, I need to support
that as well.

> By the way, such policy-based design is probably not a ideal for
> error-handling library return types, for the same reason shared_ptr<T>
> is better than Alexandrescu's policy-based smart pointer, and for the
> same reason function<T> (as it is now standardized) is better than
> function<T,A> (as it was in Boost initially, before I fixed it): it
> leads to much increased coupling, and ABI incompatibilities. The thing
> is, error objects, like smart pointers and function objects, need to be
> as frictionless as possible when crossing API boundaries, and
> policy-based designs work directly against that goal.

Completely agreed for a std::result<T, EC>, and any upcoming WG21 paper
will have no NoValuePolicy template parameter. For a Boost library, it's
an extended customisation point. Indeed, outcome<> actually reuses 90%
of result<>'s implementation by supplying an outcome-ish no-value
policy, so it saves me a ton of maintenance and copy and paste as well.

So I'm calling it an internal implementation detail. Not to be worried
about. And I agree with the general point made.

>     > Take this for what it's worth, but the earlier outcome design was a lot
>     > more focused.
>
>     You may be forgetting my initial claims of a "multi-modal" design. v1
>     wore many hats. v2 has no head, so it cannot wear a hat.
>
>
> I'm afraid my reading is that you're shifting important design decisions
> to the user, who has (by definition) lower qualifications in the domain
> of error handling. Flexibility is not always good, after all we all have
> a C++ compiler and can handle errors with all the flexibility we need --
> without an error handling library.

v1 Outcome's core type was `template<class Policy> class basic_monad`.
Everything user facing was typedefed to that with a default choice of
policy.

v2 similarly defaults the choice of policy. v2 is neither more nor less
flexible. End users get the same degree of customisation power. Whether
they use it wisely or not is up to them, as a vocabulary type it's not
my remit to enforce good behaviour by library developers, just to
encourage it. If they want to shoot themselves in the foot, they can.

>     v2 now concepts matches instead. If you feed it a type matching an
>     error_code concept, it treats it as an error code, otherwise it does
>     not. Same for exception_ptr. Thus `outcome<int, int, int>` is legal, but
>     unusable, because you cannot construct one without resorting to UB.
>
>
> Assuming we agree that passing int for the P makes no sense at all:

Not at all. There is nothing wrong with int as either an exception or a
payload type. Some end user may want that for some reason. For the exact
same reason, you can throw an int in C++. It can make sense.

> 1. Could you show a practical case that illustrates the need for using
> anything but std::exception_ptr for P?

The idea behind P is that when trait::is_exception_ptr<P> is false, then
P is a payload type which provides additional information for your main
EC failure type. So:

outcome<Success, Failure, FailureInfo>

Or more concretely:

outcome<handle, std::error_code, std::filesystem::path>

... could return an open file handle on success, or an error code plus
the failing path on failure.

> 2. What is the benefit of P being semantically different from S? In
> other words, what is the difference/benefit of outcome<T,S,P> over
> expected<T,S,P>?

Peter Dimov wanted the ability for the Filesystem TS to return the
exception which would have been thrown in the error_code& overloads, so
calling code can effectively filter whether to throw the exception or
not. I can see that as a pretty useful use case, albeit expensive seeing
as an exception_ptr is expensive to allocate. But still a reasonable
half way house between a full throw-catch cycle and the currently
metadata impoverished error_code& overloads.

So outcome<handle, std::error_code, std::exception_ptr> does permit an
error_code+exception_ptr construction, and you get exactly the desired
functionality.

>     > The other significant difference you introduced post-review was that
>     > outcome no longer had strict value-or-error semantics. This too helped set
>     > it apart.
>
>     In the default configuration, outcome<T, EC, E|P> is only strictly
>     value-or-error, value-or-exception, or value-or-error+exception.
>
>
> What other options are out there? What could I do with a struct with 3
> members, which outcome "strictly" forbids?

Things you can't do:

* value + error
* value + exception
* value + error + exception

>     > But now, and it may be just me, but I am confused. What exactly is the
>     > difference between outcome and the two flavors of expected we have? Is it
>     > essentially the same as expected<T,E...>, except that in outcome the strict
>     > value-or-error semantics (that you may remove later) are optional?
>
>     It's a low level subset. Struct-based storage, not variant-based. Fast.
>     Lightweight. ABI stable. But not rich, it's a barebones type.
>
>
> Are you claiming that outcome<T,S,P> is faster than expected<T,S,P>? I
> don't see how could that be true. Perhaps I'm missing something.

It should be to compile. Outcome is essentially nothing :)

> I'm also puzzled by the ABI stability claim. I mean, policy-based
> designs are only ABI-stable if most users use the same policies, which
> obviously defies the point of using policies.

Remember Outcome permutes its namespace with the git SHA. So it's
effectively randomised, and thus no symbol collisions can occur.

The only ABI instability is thus the data layout, and as I've mentioned
I've deliberately kept result's data layout C-struct compatible.

>     > This might be reasonable if it is a one-off thing, but consider that the
>     > general case is a bit more complicated, possibly involving different
>     > compilation units, and you do need to ensure that when the thread
>     > terminates "state" doesn't contain an error. It may or may not make sense
>     > to add something to that effect to Outcome.
>
>     v2 is designed to be subclassed into localised implementations in a
>     local namespace, which is a new thing. So it's very easy to add
>     additional behaviours and features to your localised implementation,
>     whilst retaining the cross-ABI interoperability between many localised
>     implementations.
>
>     v2 only provides the raw building block. What you do with it after it
>     totally up to you.
>
>
> The question is what's the value in using Outcome as a building block?
> Why would anyone bother, if using Outcome they're basically free to do
> whatever?

Using your logic, why use std::pair or std::tuple when a struct will do?
Same argument.

>     For example, one could build your Noexcept library
>     with it quite easily, and thus "plug in" to Expected, P0650 etc.
>
>
> I think Noexcept is a lot more lightweight than you think. The internal
> machinery it is based on (what I think you imagine your building block
> would replace) is about 500 lines, out of about 800.

As you saw on SG14, using any black box library routines is always going
to be a problem for the fixed latency user base. Your library is shorter
than mine, but it calls unknown latency routines. Mine never does.

You also impose TLS on your end users. Mine is much lower level than
that, if end users wish to combine mine with TLS to achieve your
library, they'll find it very easy. So why innovate for them?

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
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Outcome v2 (was: Re: Error handling libraries and proposals)

Boost - Dev mailing list
Niall Douglas wrote:

> Indeed, outcome<> actually reuses 90% of result<>'s implementation by
> supplying an outcome-ish no-value policy, so it saves me a ton of
> maintenance and copy and paste as well.

I would make outcome<> reuse 100% of result<>'s implementation, by stealing
another page from your playbook:

class extended_error_code
{
    std::error_code e_;
    std::exception_ptr x_;
};

template<class T> using outcome = result<T, extended_error_code>;

I would also use union { T t; E e; } in result instead of a struct. :-)


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Outcome v2

Boost - Dev mailing list
On 11/07/2017 12:53, Peter Dimov via Boost wrote:

> Niall Douglas wrote:
>
>> Indeed, outcome<> actually reuses 90% of result<>'s implementation by
>> supplying an outcome-ish no-value policy, so it saves me a ton of
>> maintenance and copy and paste as well.
>
> I would make outcome<> reuse 100% of result<>'s implementation, by
> stealing another page from your playbook:
>
> class extended_error_code
> {
>    std::error_code e_;
>    std::exception_ptr x_;
> };
>
> template<class T> using outcome = result<T, extended_error_code>;

That may well happen yet. What held me back was that the universality of
EC = error_code is very important in order to use TRY effectively.
That's what led to the split design you see today, so you're basically
exchanging some purity in the design for ease of programming. TRY is
such a boon for not typing boilerplate.

However it could be that dropping outcome and having result gain the
ability to transform unrelated error types via some free function
mechanism would be better. Indeed Andrzej asked for that exact same
thing, I gave him one half of a solution.

> I would also use union { T t; E e; } in result instead of a struct. :-)

A majority of peer review feedback didn't see the need for variant
storage. Eliminating it very significantly reduces the complexity of the
implementation, it's a big gain. I was able to SFINAE all the
constructors because of the compile time budget released by eliminating
the variant storage.

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
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Outcome v2 (was: Re: Error handling libraries and proposals)

Boost - Dev mailing list
In reply to this post by Boost - Dev mailing list
On Tue, Jul 11, 2017 at 1:08 AM, Niall Douglas via Boost <
[hidden email]> wrote:

> outcome<handle, std::error_code, std::filesystem::path>
>
> ... could return an open file handle on success, or an error code plus
> the failing path on failure.
>

The third parameter, "error info", should not be specified by the error
reporting code because what info is relevant to a failure depends on the
context, which is not know an the point you report the error.


> >     For example, one could build your Noexcept library
> >     with it quite easily, and thus "plug in" to Expected, P0650 etc.
> >
> >
> > I think Noexcept is a lot more lightweight than you think. The internal
> > machinery it is based on (what I think you imagine your building block
> > would replace) is about 500 lines, out of about 800.
>
> As you saw on SG14, using any black box library routines is always going
> to be a problem for the fixed latency user base.


What is a black box library routine? If you're referring to the use of TLS,
like I said in that thread, it's a constexpr constructor so there is
nothing to initialize and no reason for the TLS to be slow. Nobody on SG14
challenged this point, so I'm not sure what you're referring to.


> Your library is shorter
> than mine, but it calls unknown latency routines. Mine never does.
>

That was addressing your offer to use Outcome as a "building block" for
Noexcept. The point is, the "building block" is heavier than the "product".
:)


> You also impose TLS on your end users. Mine is much lower level than
> that, if end users wish to combine mine with TLS to achieve your
> library, they'll find it very easy.


It's the other way around, actually. The use of TLS moves the error objects
off the critical path and removes the coupling between errors and
error-neutral contexts. These are Good Things.

The decision to force users to enumerate their error types (recall again
exception specifications) and to move errors up the call chain one level at
a time should be justified and supported with data showing that
thread_local is too costly in practice.

And the thing is, there is no reason for it to be costly. If it helps,
think of it as the refcounting support shared_ptr requires: we can call it
tricky, but it is not a problem. Worst case, Noexcept has to implement it
if it turns out that the built-in support on some platform is inefficient.

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Outcome v2

Boost - Dev mailing list
On 11/07/2017 18:13, Emil Dotchevski wrote:

> On Tue, Jul 11, 2017 at 1:08 AM, Niall Douglas via Boost
> <[hidden email] <mailto:[hidden email]>> wrote:
>
>     outcome<handle, std::error_code, std::filesystem::path>
>
>     ... could return an open file handle on success, or an error code plus
>     the failing path on failure.
>
>
> The third parameter, "error info", should not be specified by the error
> reporting code because what info is relevant to a failure depends on the
> context, which is not know an the point you report the error.

That *may* be the case, but it is not always the case.

In the case of say a file rename operation, it's very reasonable to
supply error context info of the two paths involved. Like
std::filesystem_error does. For such a rename() function, sure you'd
hard code the error info type.

>     As you saw on SG14, using any black box library routines is always going
>     to be a problem for the fixed latency user base.
>
>
> What is a black box library routine? If you're referring to the use of
> TLS, like I said in that thread, it's a constexpr constructor so there
> is nothing to initialize and no reason for the TLS to be slow. Nobody on
> SG14 challenged this point, so I'm not sure what you're referring to.

I think that nobody *bothered* to challenge you. That's very different.
They indicated misgivings with that design choice, you did not
acknowledge them in a way they felt indicated you were open to being
corrected, so they said nothing. Happens all the time on boost-dev too
of course.

Personally speaking, I actually don't know in truth. I just feel deep
suspicion. TLS used to be awful unpredictable latency some years ago,
only Windows's TlsAlloc() was sane, and it only had 64 slots for the
entire process which was easy to exceed. But C++11's thread_local has
forced significant improvements. I know what those are in theory, but
I've done no deep dive into individual, specific implementations. I'm
pretty sure most, if not almost all, on SG14 are in the same boat. C++
11 thread_local implementations are too new yet.

A really great CppCon talk topic would be benchmarking and poking with a
stick the three main thread_local implementations. Easily a full hour.

>     Your library is shorter
>     than mine, but it calls unknown latency routines. Mine never does.
>
>
> That was addressing your offer to use Outcome as a "building block" for
> Noexcept. The point is, the "building block" is heavier than the
> "product". :)

More lines of code has little to do with heaviness. I got some flak off
Reddit regarding Outcome v1's length in line count. Totally irrelevant
to compile time load.

>     You also impose TLS on your end users. Mine is much lower level than
>     that, if end users wish to combine mine with TLS to achieve your
>     library, they'll find it very easy.
>
>
> It's the other way around, actually. The use of TLS moves the error
> objects off the critical path and removes the coupling between errors
> and error-neutral contexts. These are Good Things.
>
> The decision to force users to enumerate their error types (recall again
> exception specifications) and to move errors up the call chain one level
> at a time should be justified and supported with data showing that
> thread_local is too costly in practice.

You're forgetting one of the primary reasons to use an Outcome/Expected
type is deliberately encode failure handling near the point of failure.
People specifically want to see the handling code there so it can be
audited and writing it cannot be moved elsewhere. So you want those
error types in there to force the issue.

> And the thing is, there is no reason for it to be costly. If it helps,
> think of it as the refcounting support shared_ptr requires: we can call
> it tricky, but it is not a problem. Worst case, Noexcept has to
> implement it if it turns out that the built-in support on some platform
> is inefficient.

I remain unconvinced.

I'm still strongly of the opinion that if you want C++ exception like
semantics, turn on C++ exceptions support. The primary reason to use
Expected/Outcome/whatever is where it's better in every way to use those
semantics instead for some particular use case.

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
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Outcome v2

Boost - Dev mailing list
On Tue, Jul 11, 2017 at 12:43 PM, Niall Douglas via Boost <
[hidden email]> wrote:

> On 11/07/2017 18:13, Emil Dotchevski wrote:
> > On Tue, Jul 11, 2017 at 1:08 AM, Niall Douglas via Boost
> > <[hidden email] <mailto:[hidden email]>> wrote:
> >
> >     outcome<handle, std::error_code, std::filesystem::path>
> >
> >     ... could return an open file handle on success, or an error code
> plus
> >     the failing path on failure.
> >
> >
> > The third parameter, "error info", should not be specified by the error
> > reporting code because what info is relevant to a failure depends on the
> > context, which is not know an the point you report the error.
>
> That *may* be the case, but it is not always the case.
>

When is it not the case? A trivial program that doesn't need error handling
library. Otherwise, the low level library that reports the error can not
know what is _needed_ in that program to handle the error. At the point of
reporting, all you can do is get things going, but most of the relevant
context comes in higher level functions.

And, importantly, the data that comes from the context in which the failure
is reported does _not_ depend on the failure. So, it is critical to allow
such data to be associated with _any_ error.


> In the case of say a file rename operation, it's very reasonable to
> supply error context info of the two paths involved. Like
> std::filesystem_error does. For such a rename() function, sure you'd
> hard code the error info type.
>
> >     As you saw on SG14, using any black box library routines is always
> going
> >     to be a problem for the fixed latency user base.
> >
> >
> > What is a black box library routine? If you're referring to the use of
> > TLS, like I said in that thread, it's a constexpr constructor so there
> > is nothing to initialize and no reason for the TLS to be slow. Nobody on
> > SG14 challenged this point, so I'm not sure what you're referring to.
>
> I think that nobody *bothered* to challenge you. That's very different.
> They indicated misgivings with that design choice, you did not
> acknowledge them in a way they felt indicated you were open to being
> corrected, so they said nothing. Happens all the time on boost-dev too
> of course.
>

I think they, like you, can't point at a particular problem but generally
distrust it and so they remain silent. Regardless, most of the arguments
I'm making are about semantics, not speed.

In Noexcept TLS use is means to an end, that end being moving the error
object out of the critical path, because the error is almost exclusively of
concern only to the code that reports it and the code that handles it.


> Personally speaking, I actually don't know in truth. I just feel deep
> suspicion. TLS used to be awful unpredictable latency some years ago,
> only Windows's TlsAlloc() was sane, and it only had 64 slots for the
> entire process which was easy to exceed. But C++11's thread_local has
> forced significant improvements. I know what those are in theory, but
> I've done no deep dive into individual, specific implementations. I'm
> pretty sure most, if not almost all, on SG14 are in the same boat. C++
> 11 thread_local implementations are too new yet.
>
> A really great CppCon talk topic would be benchmarking and poking with a
> stick the three main thread_local implementations. Easily a full hour.
>

Yes, however consider that the general problem of implementing TLS that
requires dynamic initialization and whatnot is a lot more complex than what
Noexcept needs. Literally, to make Noexcept work, you need a piece of TLS
memory where one pointer is set to zero. This is not a tall order, though I
admit that a general solution might do it poorly.


> >     Your library is shorter
> >     than mine, but it calls unknown latency routines. Mine never does.
> >
> >
> > That was addressing your offer to use Outcome as a "building block" for
> > Noexcept. The point is, the "building block" is heavier than the
> > "product". :)
>
> More lines of code has little to do with heaviness. I got some flak off
> Reddit regarding Outcome v1's length in line count. Totally irrelevant
> to compile time load.
>

Agreed. I still don't think it's serious to suggest that it makes sense to
implement Noexcept in terms of Outcome. :)


> >     You also impose TLS on your end users. Mine is much lower level than
> >     that, if end users wish to combine mine with TLS to achieve your
> >     library, they'll find it very easy.
> >
> >
> > It's the other way around, actually. The use of TLS moves the error
> > objects off the critical path and removes the coupling between errors
> > and error-neutral contexts. These are Good Things.
> >
> > The decision to force users to enumerate their error types (recall again
> > exception specifications) and to move errors up the call chain one level
> > at a time should be justified and supported with data showing that
> > thread_local is too costly in practice.
>
> You're forgetting one of the primary reasons to use an Outcome/Expected
> type is deliberately encode failure handling near the point of failure.
> People specifically want to see the handling code there so it can be
> audited and writing it cannot be moved elsewhere. So you want those
> error types in there to force the issue.
>

This use case is supported by TLS storage as well, however in my experience
the essence of this use case is that the program detects the error, logs it
and continues, there is really no recovery or handling.

This is legit, but in this case you need a good logging library more than
you need a good error handling library (the only other variation of "I'm
dealing with the error right now" is to translate it to another error,
which is always a bad idea.)


> > And the thing is, there is no reason for it to be costly. If it helps,
> > think of it as the refcounting support shared_ptr requires: we can call
> > it tricky, but it is not a problem. Worst case, Noexcept has to
> > implement it if it turns out that the built-in support on some platform
> > is inefficient.
>
> I remain unconvinced.
>
> I'm still strongly of the opinion that if you want C++ exception like
> semantics, turn on C++ exceptions support.


I agree. Noexcept is needed when you can't do that for some domain-specific
reasons or because the code you're dealing with is not exception-safe (I
have seen a lot of hand-waving that domain-specific reasons exist in
practice, but I have never seen hard data to back that up; and dealing with
code that is not exception-safe is pretty common.)

Anyway, what you call exception-like semantics in this case is the ability
to efficiently transport arbitrary types. The reason that is important is
because otherwise you need to translate (bad) or use exception_ptr. The
problem with using exception_ptr is that it does require an allocation, and
there is no way to interrogate it about its content. From where I stand,
Outcome does not solve the most important problem that needs solving by an
error handling library.

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Outcome v2

Boost - Dev mailing list
> Outcome does not solve the most important problem that needs solving by an
> error handling library.

Does Expected?

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
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Outcome v2

Boost - Dev mailing list
On Tue, Jul 11, 2017 at 2:11 PM, Niall Douglas via Boost <
[hidden email]> wrote:

> > Outcome does not solve the most important problem that needs solving by
> an
> > error handling library.
>
> Does Expected?
>

Nope, but the original outcome<T> was closer. I thought, perhaps it is
possible to make it able to transport arbitrary error types, and not by
exception_ptr. My attempt at solving that problem lead me to TLS but maybe
there are other ways.

On the other hand, I don't see a problem with TLS, it seems the perfect
solution. And I don't necessarily mean this from performance point of view,
it's really a philosophical disagreement. The error object is a
communication channel between the error-detecting code and the
error-handling code; everyone else should keep quiet except maybe to add
relevant contextual data.

If you would accept this analogy, if I send you a package, it seems wrong
for the postal service to open it up and replace the contents with
something they think is better, but I'm fine with them slapping a label or
two on the outside of the box. :)

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Outcome v2

Boost - Dev mailing list
In reply to this post by Boost - Dev mailing list
On 12/07/2017 02:18, Niall Douglas wrote:

> On 11/07/2017 12:53, Peter Dimov wrote:
>> I would make outcome<> reuse 100% of result<>'s implementation, by
>> stealing another page from your playbook:
>>
>> class extended_error_code
>> {
>>     std::error_code e_;
>>     std::exception_ptr x_;
>> };
>>
>> template<class T> using outcome = result<T, extended_error_code>;
>
> That may well happen yet. What held me back was that the universality of
> EC = error_code is very important in order to use TRY effectively.
> That's what led to the split design you see today, so you're basically
> exchanging some purity in the design for ease of programming. TRY is
> such a boon for not typing boilerplate.

Perhaps:

struct extended_error_code : public std::error_code
{
     std::exception_ptr ex;
};

template<class T> using outcome = result<T, extended_error_code>;


This allows generic code to treat EC exactly as a std::error_code (both
for method calls and to slice off the exception if they wish).

Having said that, this would basically render it impossible to have
value + exception *without* an error_code.  You could mitigate that by
defining a default "there is an exception" error_code (which you might
be doing anyway), though it feels a little weird.  (Then again, it's
probably worse to leave the std::error_code default-constructed.)

> A majority of peer review feedback didn't see the need for variant
> storage. Eliminating it very significantly reduces the complexity of the
> implementation, it's a big gain. I was able to SFINAE all the
> constructors because of the compile time budget released by eliminating
> the variant storage.

If you're going to maintain strict no-value-plus-error semantics then
union/variant storage makes sense, as otherwise you're wasting memory.
I'm not sure why this would increase complexity.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Outcome v2

Boost - Dev mailing list
Gavin Lambert wrote:

> Having said that, this would basically render it impossible to have value
> + exception *without* an error_code.

Why would one ever want that?


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Outcome v2

Boost - Dev mailing list
In reply to this post by Boost - Dev mailing list
Mere moments ago, quoth I:
> If you're going to maintain strict no-value-plus-error semantics then
> union/variant storage makes sense, as otherwise you're wasting memory.
> I'm not sure why this would increase complexity.

Although of course unions can be problematic for T with non-trivial copy
constructors (and for exception_ptr, which also has a non-trivial copy
constructor).

A proper variant implementation should take care of that for you, though
this didn't help you in Outcome v1 since you were rolling your own
variant implementation.  Perhaps this is what you were referring to?

If you're using struct { T; EC } storage, what do you do with
non-default-constructible T when constructed with an error code only?
Leaving it unconstructed seems like the only reasonable solution, but
this then either requires mucking about with constructors and
destructors in annoying fashions or using std::aligned_storage rather
than embedding T directly (which still requires mucking about with
constructors and destructors).

Using a variant just seems like the simpler way to go for this case.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
123
Loading...