Discussion:
[boost] [outcome v2] Please comment on new result<T, EC> reference API docs
Niall Douglas via Boost
2017-06-21 17:41:34 UTC
Permalink
Dear Boost,

I've been hard at work refactoring Outcome to match the peer review a
few weeks ago. Much comment at the time mentioned the poor experience of
the reference API docs, so can I get a community check of this:

https://dedi4.nedprod.com/static/result-v2/doc_result.html

This was generated by the new C++ reference docs generator
https://github.com/foonathan/standardese. You'll no doubt spot quite a
few formatting bugs, Jonathan says he's onto them. But the usage of
libclang rather than doxygen's broken C++ parser clearly shows a lot of
benefit, it's just standardese still remains quite a lot short in terms
of necessary features to make output fully correct.

I won't give the changelog for Outcome v2 result<T> over Outcome v1 yet.
Let's see how you fare with the above reference API docs first. But as
you'll note, there is considerable change over before as we now slot
into a niche not occupied by Expected nor EitherMonad, indeed this now
looks much more like Robert's or Lawrence Crowl's slimmed down object.
This result<T> is approx 0.6 CPU cycles per stack frame unwound slower
which I feel is acceptable in exchange for no more variant storage,
which in turn released lots of SFINAE budget for me.

(FYI v2 outcome<T> isn't done yet, but it will extend v2 result<T>)

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
Peter Dimov via Boost
2017-06-21 18:11:56 UTC
Permalink
Post by Niall Douglas via Boost
Dear Boost,
I've been hard at work refactoring Outcome to match the peer review a few
weeks ago. Much comment at the time mentioned the poor experience of the
https://dedi4.nedprod.com/static/result-v2/doc_result.html
Ideally, one would want only result<> documented, with all its public
members, without the implementation detail result_base<>. Not sure if this
is possible with standardese at the moment.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas via Boost
2017-06-21 21:28:27 UTC
Permalink
Post by Peter Dimov via Boost
Post by Niall Douglas via Boost
I've been hard at work refactoring Outcome to match the peer review a
few weeks ago. Much comment at the time mentioned the poor experience
https://dedi4.nedprod.com/static/result-v2/doc_result.html
Ideally, one would want only result<> documented, with all its public
members, without the implementation detail result_base<>. Not sure if
this is possible with standardese at the moment.
It is not possible with standardese at the moment. But before any second
review, I'd manually copy and paste result_base<> into result<> so it
looks right.

More importantly though, is that reference API documentation what people
were looking for? With clauses for effects, requires, throws etc?

Do I need to write more detail for the APIs, or is what is there enough?

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
Peter Dimov via Boost
2017-06-21 21:49:46 UTC
Permalink
Post by Niall Douglas via Boost
More importantly though, is that reference API documentation what people
were looking for? With clauses for effects, requires, throws etc?
Yes, this is the style the C++ standard uses.
Post by Niall Douglas via Boost
Do I need to write more detail for the APIs, or is what is there enough?
You could be more detailed in places. For example,

""
Function template outcome::policy::throw_directly::narrow_value_check

template <class Impl>
static constexpr void narrow_value_check(Impl* self) noexcept;

Performs a narrow check of state, used in the assume_value() functions
""

You have to say what happens, that is, you're missing the Effects clause
here.

Another minor comment:

noexcept(noexcept(value_type(std::forward<T>(t)))&&noexcept(error_type(std::forward<U>(u))));

This is usually written as

noexcept(is_nothrow_constructible_v<value_type, T> &&
is_nothrow_constructible_v<error_type, U>);

If you haven't, you could look at the C++ standard to get an idea of how it
specifies things, for example here: http://eel.is/c++draft/tuple.tuple

SmartPtr uses a similar style too, f.ex. here:

http://www.boost.org/doc/libs/develop/libs/smart_ptr/doc/html/smart_ptr.html#shared_ptr_members


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas via Boost
2017-06-21 22:25:07 UTC
Permalink
Post by Peter Dimov via Boost
Post by Niall Douglas via Boost
Do I need to write more detail for the APIs, or is what is there enough?
You could be more detailed in places. For example,
""
Function template outcome::policy::throw_directly::narrow_value_check
template <class Impl>
static constexpr void narrow_value_check(Impl* self) noexcept;
Performs a narrow check of state, used in the assume_value() functions
""
You have to say what happens, that is, you're missing the Effects clause
here.
Great point.
Post by Peter Dimov via Boost
noexcept(noexcept(value_type(std::forward<T>(t)))&&noexcept(error_type(std::forward<U>(u))));
This is usually written as
noexcept(is_nothrow_constructible_v<value_type, T> &&
is_nothrow_constructible_v<error_type, U>);
I was trying to work around an ICE on MSVC :(

But it's not working, so I'm putting it back to how you describe.
Post by Peter Dimov via Boost
If you haven't, you could look at the C++ standard to get an idea of how
it specifies things, for example here: http://eel.is/c++draft/tuple.tuple
http://www.boost.org/doc/libs/develop/libs/smart_ptr/doc/html/smart_ptr.html#shared_ptr_members
I find writing this overspecification highly unnatural to me. It's not
how I think about APIs at all.

Thanks for your feedback Peter. Good to know this sort of documentation
is what people want. I'll keep pressing onwards.

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
Peter Dimov via Boost
2017-06-21 23:21:58 UTC
Permalink
Post by Niall Douglas via Boost
https://dedi4.nedprod.com/static/result-v2/doc_result.html
Niall, what does result<R, void> represent?

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas via Boost
2017-06-21 23:58:24 UTC
Permalink
Post by Peter Dimov via Boost
Post by Niall Douglas via Boost
https://dedi4.nedprod.com/static/result-v2/doc_result.html
Niall, what does result<R, void> represent?
Same as expected<R, void>.

It's legal to return that failure occurred, but no information as to
why. I see that as being possibly useful.

What's far more interesting is result<void, void>. Yes expected<void,
void> is also legal too. Where things get mind bendy is that v2
result<T, E> can return a T with additional info E, so you can quite
legitimately have a result<void, void> with both .has_value() and
.has_error() returning true.

I agree before you say it that that is daft. But I see no good reason to
not support it other than "that is daft". And besides, outcome<void,
void, exception_ptr> looks much more useful, and that would have a
result<void, void> internally, so there is a real actual use case for
result<void, void>.

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
Emil Dotchevski via Boost
2017-06-22 00:09:46 UTC
Permalink
On Wed, Jun 21, 2017 at 4:58 PM, Niall Douglas via Boost <
Post by Niall Douglas via Boost
Post by Peter Dimov via Boost
Post by Niall Douglas via Boost
https://dedi4.nedprod.com/static/result-v2/doc_result.html
Niall, what does result<R, void> represent?
Same as expected<R, void>.
It's legal to return that failure occurred, but no information as to
why. I see that as being possibly useful.
What's far more interesting is result<void, void>. Yes expected<void,
void> is also legal too. Where things get mind bendy is that v2
result<T, E> can return a T with additional info E, so you can quite
legitimately have a result<void, void> with both .has_value() and
.has_error() returning true.
Wasn't this one of the issues people took with Noexcept, that since it
works with any return type whatsoever, there is no clear invariant, and you
could end up in a situation where you have a value and an error? (I
actually agree with this criticism, but in my mind this is a small price to
pay for not burdening return objects with transporting errors as well as
values.)

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas via Boost
2017-06-22 00:50:18 UTC
Permalink
Post by Emil Dotchevski via Boost
Post by Niall Douglas via Boost
What's far more interesting is result<void, void>. Yes expected<void,
void> is also legal too. Where things get mind bendy is that v2
result<T, E> can return a T with additional info E, so you can quite
legitimately have a result<void, void> with both .has_value() and
.has_error() returning true.
Wasn't this one of the issues people took with Noexcept, that since it
works with any return type whatsoever, there is no clear invariant, and you
could end up in a situation where you have a value and an error? (I
actually agree with this criticism, but in my mind this is a small price to
pay for not burdening return objects with transporting errors as well as
values.)
The idea, since we no longer have variant storage, is to see if we'd
like to implement
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0262r0.html
(except with the parameter order reversed).

Now, I am fairly sure that .error() is the wrong way to return a status.
I am minded that a .status() should do that. I just did a major refactor
there as yesterday's code made GCC 6 and VS2017.3 barf. I'm on it.

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
Peter Dimov via Boost
2017-06-22 01:08:14 UTC
Permalink
The idea, since we no longer have variant storage, is to see if we'd like
to implement
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0262r0.html
(except with the parameter order reversed).
I'm not particularly enthusiastic about status_value (and the argument given
in the paper is unconvincing.) Neither am I thrilled by the idea of
result<R, void> (an optional) or result<void, void> (something like a
glorified pair<bool, bool>.)

result<T, EC> in its original form represented a very clear concept: the
type of a function that must either return a value, or a reason for the lack
of value. All of the above do not fit this concept and to me ring hollow.

The fact that for outcome<T, EC, exception_ptr> we may want to have both an
EC and an exception_ptr does not necessarily mean that we need to give up on
the Either( Value, Reason ). In outcome's case, the reason is just more
elaborate.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas via Boost
2017-06-22 02:23:56 UTC
Permalink
Post by Peter Dimov via Boost
Post by Niall Douglas via Boost
The idea, since we no longer have variant storage, is to see if we'd
like to implement
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0262r0.html
(except with the parameter order reversed).
I'm not particularly enthusiastic about status_value (and the argument
given in the paper is unconvincing.) Neither am I thrilled by the idea
of result<R, void> (an optional) or result<void, void> (something like a
glorified pair<bool, bool>.)
I've fixed the problems you raised at
https://dedi4.nedprod.com/static/result-v2/doc_result.html and moved the
implementation stuff into a impl namespace so standardese will still
show the docs for the composited types, but not be usable by end users.
Post by Peter Dimov via Boost
result<T, EC> in its original form represented a very clear concept: the
type of a function that must either return a value, or a reason for the
lack of value. All of the above do not fit this concept and to me ring
hollow.
Andrzej feels similarly to you.

But I don't think things are as bad as both of you feel. In v2 result<T,
EC> you now can have:

1. Successful: T

2. Failure: EC

3. Successful + Status: T + EC

The reason I don't think this is a problem is because you can't
accidentally return a wrong result<T, EC> in practice. Either you're
using result to return Success + Status, or you're using result to
return Success | Failure. It is very hard to accidentally write code
which does the wrong thing here, almost certainly the type system would
refuse to compile incorrect code.

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
Andrzej Krzemienski via Boost
2017-06-22 07:53:44 UTC
Permalink
Post by Niall Douglas via Boost
Post by Peter Dimov via Boost
Post by Niall Douglas via Boost
The idea, since we no longer have variant storage, is to see if we'd
like to implement
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0262r0.html
(except with the parameter order reversed).
I'm not particularly enthusiastic about status_value (and the argument
given in the paper is unconvincing.) Neither am I thrilled by the idea
of result<R, void> (an optional) or result<void, void> (something like a
glorified pair<bool, bool>.)
I've fixed the problems you raised at
https://dedi4.nedprod.com/static/result-v2/doc_result.html and moved the
implementation stuff into a impl namespace so standardese will still
show the docs for the composited types, but not be usable by end users.
Post by Peter Dimov via Boost
result<T, EC> in its original form represented a very clear concept: the
type of a function that must either return a value, or a reason for the
lack of value. All of the above do not fit this concept and to me ring
hollow.
Andrzej feels similarly to you.
But I don't think things are as bad as both of you feel. In v2 result<T,
1. Successful: T
2. Failure: EC
3. Successful + Status: T + EC
The reason I don't think this is a problem is because you can't
accidentally return a wrong result<T, EC> in practice. Either you're
using result to return Success + Status, or you're using result to
return Success | Failure. It is very hard to accidentally write code
which does the wrong thing here, almost certainly the type system would
refuse to compile incorrect code.
You could say "if you do not like the state `has both value and error`,
then don't create it", and this could be considered acceptable, but now
that you have said you intend to change "error" into "status", you are
affecting even this conservative usage.

In the paper by Laurence Crowl, in the example with queue status:

```
Value queue::value_pop();
queue_op_status queue::wait_pop(Value&);
```

what he is describing is:
pair<optional<T>, status>

Whereas what you provide (if I got it correctly) is:
pair<optional<T>, optional<status>>

Which means I can get a value with no status. This still does not implement
P0262.

Regards,
&rzej;

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas via Boost
2017-06-22 21:09:17 UTC
Permalink
Post by Andrzej Krzemienski via Boost
You could say "if you do not like the state `has both value and error`,
then don't create it", and this could be considered acceptable, but now
that you have said you intend to change "error" into "status", you are
affecting even this conservative usage.
```
Value queue::value_pop();
queue_op_status queue::wait_pop(Value&);
```
pair<optional<T>, status>
pair<optional<T>, optional<status>>
Which means I can get a value with no status. This still does not implement
P0262.
In response to yours and Peter's concerns, I've now tightened the
semantics considerably. For result<T, S> you now may do:

1. Construct with a T. .value() returns that T. .status() throws
bad_result_access. There is no .error().

2. Construct with a T and an S. .value() returns that T, .status()
returns that S. There is no .error().

And that's it. If trait::enable_errored_result_creation<S> is true
however, you get this instead:

1. Construct with a T. .value() returns that T. .error() throws
bad_result_access. There is no .status().

2. Construct with an S. .value() throws that S according to policy.
.error() returns that S. There is no .status().

So result<T, S> is either wearing a "status hat", or it is wearing a
"failure hat". It cannot be both.

The only remaining objection now is that result<...> is named the same
yet has different semantics depending on its S type. Yet error_code is
also a status, it's just a *negative* status rather than a *positive*
status.

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
Niall Douglas via Boost
2017-06-22 22:24:43 UTC
Permalink
Post by Niall Douglas via Boost
So result<T, S> is either wearing a "status hat", or it is wearing a
"failure hat". It cannot be both.
The only remaining objection now is that result<...> is named the same
yet has different semantics depending on its S type. Yet error_code is
also a status, it's just a *negative* status rather than a *positive*
status.
I've pushed clarifying reference API docs regarding this trait
separation of positive versus negative status types to
https://dedi4.nedprod.com/static/result-v2/doc_result.html.

I appreciate that this change is controversial. However, I am also
minded that there is no point in Outcome v2 covering the identical
ground as Expected, just less well. Outcome ought to be isomorphic to
Expected, be strong at the areas Expected is weak at and so on.

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
Peter Dimov via Boost
2017-06-22 23:00:09 UTC
Permalink
I appreciate that this change is controversial. However, I am also minded
that there is no point in Outcome v2 covering the identical ground as
Expected, just less well.
Can't say I agree with that. Outcome should cover the ground that needs to
be covered. Not cover useless ground that nobody needs covered (status) just
to avoid the "expected" ground. If that makes result<T, EC> virtually
identical to expected<T, E>, so be it. This is just a sign that both you and
Vicente have the basic design correct. You will still differentiate based on
implementation quality, extra features of error_code_extended, and the added
value that outcome brings to the party.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas via Boost
2017-06-22 23:51:43 UTC
Permalink
Post by Peter Dimov via Boost
Post by Niall Douglas via Boost
I appreciate that this change is controversial. However, I am also
minded that there is no point in Outcome v2 covering the identical
ground as Expected, just less well.
Can't say I agree with that. Outcome should cover the ground that needs
to be covered. Not cover useless ground that nobody needs covered
(status) just to avoid the "expected" ground. If that makes result<T,
EC> virtually identical to expected<T, E>, so be it. This is just a sign
that both you and Vicente have the basic design correct. You will still
differentiate based on implementation quality, extra features of
error_code_extended, and the added value that outcome brings to the party.
I appreciate this.

However I am thinking here in terms of WG21 standardisation,
specifically SG14's work on a std::vector upgrade which doesn't have the
really unfortunate unpredictable latency. The general idea is that a low
latency std::vector would never expand its capacity automatically,
instead it would return success + capacity approaching warning status.
You then could schedule the construction of a new, bigger vector outside
the hot path.

That's why Lawrence's Status + Value proposal garnered such interest,
but there were concerns about whether an expected<T, E> type solution
like Outcome v1 would be a better approach instead, so the idea was that
expected<T, please_reallocate<T>> could be returned. I don't much care
for that idea personally, but it has its fans.

I am dropping the expected<T, E> implementation from Outcome v2. That
will lose me a ton of user base. I am minded to claw some of that back
from elsewhere, perhaps solve some other pressing problems for SG14
around the Status + Value need. It costs me almost nothing in terms of
implementation, so a bit like with empty state, I am minded to provide
it opt-in. We'll see how it goes.

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
Peter Dimov via Boost
2017-06-23 00:25:58 UTC
Permalink
However I am thinking here in terms of WG21 standardisation, specifically
SG14's work on a std::vector upgrade which doesn't have the really
unfortunate unpredictable latency. The general idea is that a low latency
std::vector would never expand its capacity automatically, instead it
would return success + capacity approaching warning status. You then could
schedule the construction of a new, bigger vector outside the hot path.
I find this example as unconvincing as Lawrence's original one. For one,
vector functions that grow the size typically return void. So you could just
turn that into bool or an enum and you'd be done.

For another, when you're in the hot path, the "capacity warning" status is
not actionable, so it's of little use. Instead, when you get out of the hot
path (or, better yet, before you get into it), you'd simply check
capacity(), see if it's yellow/red, and reserve. Or not even check, just
reserve( size() + <hot path max size requirement> ).
so the idea was that expected<T, please_reallocate<T>> could be returned.
Sounds like the discussion took place in a pub. Not that there's anything
wrong with that. :-)


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas via Boost
2017-06-23 01:13:29 UTC
Permalink
Post by Peter Dimov via Boost
Post by Niall Douglas via Boost
However I am thinking here in terms of WG21 standardisation,
specifically SG14's work on a std::vector upgrade which doesn't have
the really unfortunate unpredictable latency. The general idea is that
a low latency std::vector would never expand its capacity
automatically, instead it would return success + capacity approaching
warning status. You then could schedule the construction of a new,
bigger vector outside the hot path.
I find this example as unconvincing as Lawrence's original one. For one,
vector functions that grow the size typically return void. So you could
just turn that into bool or an enum and you'd be done.
For another, when you're in the hot path, the "capacity warning" status
is not actionable, so it's of little use. Instead, when you get out of
the hot path (or, better yet, before you get into it), you'd simply
check capacity(), see if it's yellow/red, and reserve. Or not even
check, just reserve( size() + <hot path max size requirement> ).
All valid observations.

Yet SG14 likes anything which relocates latency unpredictable things
(exception throws, malloc, etc) to the caller. It's certainly how I'd
have personally designed the STL originally. std::vector's latency
insensitive design has always irritated me, and I still hope - without
much basis - that any STL v2 will fix that.

I'm minded to try and deliver SG14 a vocabulary type which can help with
that so they can propose it for standardisation. As much as it'll cause
problems with the Boost review, it delights SG14 members. I received
quite a lot of private email during and after the Outcome v1 review
about how from the review feedback on boost-dev it was clear here
doesn't understand SG14's needs and priorities. I really wish they'd say
that here instead of emailing me privately about it. I already know,
it's a difference of mindset and philosophy.

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
Emil Dotchevski via Boost
2017-06-23 07:05:23 UTC
Permalink
On Thu, Jun 22, 2017 at 6:13 PM, Niall Douglas via Boost <
Post by Niall Douglas via Boost
Post by Peter Dimov via Boost
Post by Niall Douglas via Boost
However I am thinking here in terms of WG21 standardisation,
specifically SG14's work on a std::vector upgrade which doesn't have
the really unfortunate unpredictable latency. The general idea is that
a low latency std::vector would never expand its capacity
automatically, instead it would return success + capacity approaching
warning status. You then could schedule the construction of a new,
bigger vector outside the hot path.
I find this example as unconvincing as Lawrence's original one. For one,
vector functions that grow the size typically return void. So you could
just turn that into bool or an enum and you'd be done.
For another, when you're in the hot path, the "capacity warning" status
is not actionable, so it's of little use. Instead, when you get out of
the hot path (or, better yet, before you get into it), you'd simply
check capacity(), see if it's yellow/red, and reserve. Or not even
check, just reserve( size() + <hot path max size requirement> ).
All valid observations.
Yet SG14 likes anything which relocates latency unpredictable things
(exception throws, malloc, etc) to the caller. It's certainly how I'd
have personally designed the STL originally. std::vector's latency
insensitive design has always irritated me, and I still hope - without
much basis - that any STL v2 will fix that.
If there is anything wrong with std::vector that isn't it.

If you want a container that is about as fast as vector when indexing but
can grow without reallocation, use deque. If you insist on the elements
being sequential in memory, reserve.

By the way, even if vectors didn't reallocate, the fact that the elements
must occupy sequential memory can become a problem on some platforms for
very large vectors, exacerbating heap fragmentation issues. Incidentally,
these tend to be the same platforms where the performance impact
on reallocations is most problematic.

At any rate, such issues are a consequence of language design choices
originating in C, and can not be alleviated by "fixing" std::vector.

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas via Boost
2017-06-23 14:48:25 UTC
Permalink
Post by Emil Dotchevski via Boost
Post by Niall Douglas via Boost
Yet SG14 likes anything which relocates latency unpredictable things
(exception throws, malloc, etc) to the caller. It's certainly how I'd
have personally designed the STL originally. std::vector's latency
insensitive design has always irritated me, and I still hope - without
much basis - that any STL v2 will fix that.
If there is anything wrong with std::vector that isn't it.
There is a significant minority who want their containers to never, ever
allocate memory on their own.

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
Peter Dimov via Boost
2017-06-23 10:07:42 UTC
Permalink
Post by Niall Douglas via Boost
All valid observations.
Yet SG14 likes anything which relocates latency unpredictable things
(exception throws, malloc, etc) to the caller. It's certainly how I'd have
personally designed the STL originally. std::vector's latency insensitive
design has always irritated me, and I still hope - without much basis -
that any STL v2 will fix that.
I'm minded to try and deliver SG14 a vocabulary type which can help with
that so they can propose it for standardisation.
Do you have in mind some outline of how this SG14-friendly, status-based,
vector would look like? A few signatures are fine, no need to list the whole
synopsis, although it's not that much.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas via Boost
2017-06-23 15:37:19 UTC
Permalink
Post by Peter Dimov via Boost
Post by Niall Douglas via Boost
Yet SG14 likes anything which relocates latency unpredictable things
(exception throws, malloc, etc) to the caller. It's certainly how I'd
have personally designed the STL originally. std::vector's latency
insensitive design has always irritated me, and I still hope - without
much basis - that any STL v2 will fix that.
I'm minded to try and deliver SG14 a vocabulary type which can help
with that so they can propose it for standardisation.
Do you have in mind some outline of how this SG14-friendly,
status-based, vector would look like? A few signatures are fine, no need
to list the whole synopsis, although it's not that much.
Ah, that's not my field. I've just been monitoring the work of others.
And do remember I'm not on SG14, I can't afford to attend their
meetings, same as WG21 meetings. Most, but not all, of their work can be
found at https://github.com/WG21-SG14.

I can tell you however what I'd personally prefer, though be aware the
below is off the top of my head and not thought through. Personally
speaking, I'd prefer all STL v2 containers to be exclusively made up of
composed views of fixed-at-construction capacity vectors and
fixed-at-compile-time arrays. So you might create:

std2::vector<int> a(3); // not three items, but capacity for three items
a.push_back(1); // returns result<size_t, std::error_code>
a.push_back(1); // returns success, capacity() - size() = 1
a.push_back(1); // returns success, capacity() - size() = 0
a.push_back(1); // returns failure, std::errc::not_enough_memory
std2::vector<int> b(3);
b.push_back(2); // returns success, capacity() - size() = 2
std2::span c(a, b); // compose
c.size() == 4
c.push_back(2); // returns success, capacity() - size() = 1
c.size() == 5
c[0] == 1
c[3] == 2
c[4] == 2
std2::vector d(std::move(c)); // move contents into new storage

And you'd keep going with the composing views up into associative maps
etc, always being in direct manual control of any memory allocation at
all times. std2::vector and std::array are the primitive containers, all
other STL v2 containers are built on top in layers with reusable and
generic views and spans. So you wouldn't really have an unordered_map
container any more, you'd have a std2::mapping_view from a std::vector2
for the hash table onto a set of mapped items. You manually control
buckets expansaion.

That's the sort of design I'd personally prefer for STL v2 containers
(remember no one is proposing to retire STL v1 containers, your
std::vector<T> remains exactly as now). I appreciate it's not quite the
Ranges TS and that's where things will actually end up at, but it's
still very possible that SG14 will design their own low latency STL
which doesn't repeat the latency unpredictability of the current STL or
a Ranges TS based STL. We'll see.

You probably have noted the lack of use of any positive status in any of
the above. That's because the above is my personal preference, I'd
personally speaking only see a wise use for positive status in insert
operations on associative maps where a positive status indicates if a
new key was inserted or if an existing value was replaced. But others
have different opinions on all that, and as I mentioned my personal
preference for STL v2 just described is not well thought through, so I
defer to those who've done a lot more work on that. And they appear to
want positive status more than I would personally see good reason for.
Why I am not sure, because I don't know what they have planned for it.

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
Andrzej Krzemienski via Boost
2017-06-26 07:48:08 UTC
Permalink
Post by Niall Douglas via Boost
Post by Peter Dimov via Boost
Post by Niall Douglas via Boost
However I am thinking here in terms of WG21 standardisation,
specifically SG14's work on a std::vector upgrade which doesn't have
the really unfortunate unpredictable latency. The general idea is that
a low latency std::vector would never expand its capacity
automatically, instead it would return success + capacity approaching
warning status. You then could schedule the construction of a new,
bigger vector outside the hot path.
I find this example as unconvincing as Lawrence's original one. For one,
vector functions that grow the size typically return void. So you could
just turn that into bool or an enum and you'd be done.
For another, when you're in the hot path, the "capacity warning" status
is not actionable, so it's of little use. Instead, when you get out of
the hot path (or, better yet, before you get into it), you'd simply
check capacity(), see if it's yellow/red, and reserve. Or not even
check, just reserve( size() + <hot path max size requirement> ).
All valid observations.
Yet SG14 likes anything which relocates latency unpredictable things
(exception throws, malloc, etc) to the caller. It's certainly how I'd
have personally designed the STL originally. std::vector's latency
insensitive design has always irritated me, and I still hope - without
much basis - that any STL v2 will fix that.
A superior alternative to vector? -- ok, but this is not clear how the
"value + status" vocabulary type helps here. It is not enough to say, I can
return two things. You would also have to demonstrate, how users will work
with such status value: if it is even possible, without compromising the
clarity of the code.
Post by Niall Douglas via Boost
I'm minded to try and deliver SG14 a vocabulary type which can help with
that so they can propose it for standardisation. As much as it'll cause
problems with the Boost review, it delights SG14 members. I received
quite a lot of private email during and after the Outcome v1 review
about how from the review feedback on boost-dev it was clear here
doesn't understand SG14's needs and priorities. I really wish they'd say
that here instead of emailing me privately about it. I already know,
it's a difference of mindset and philosophy.
So, let's summarize what you are saying here:
1. Your goal is to service "SG14 people".
2. "SG14 people" do not trust Boost community could understand/appreciate
their needs to the extent that they do not even offer their input during
the review.
3. You want this library for "SG14 people" to go through Boost review, and
become part of Boost.

Maybe this is a wrong way to go, because what you will hear here is, "not
well justified", "no demonstrated use cases that would be improved by your
library".

Alternatively, what you could do, given the fact that "SG14 people" refuse
to give their input in boost-dev, is to act like a proxy and convey their
arguments here. But so far, you have been only conveying the "I need this"
part, not the really interesting part: "when I have it I can do this". In
the other message you showed us how a vector with capacity fixed upon
construction is better than `std::vector`, but it didn't illustrate why
there is a need for a "value + status" type. I realize my expectations must
be frustrating. On the other hand, without it, I do not see how people in
boost-dev can fulfill their task of giving you a valuable feedback.

Regards,
&rzej;

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
degski via Boost
2017-06-23 04:18:03 UTC
Permalink
For one, vector functions that grow the size typically return void. So you
could just turn that into bool or an enum and you'd be done.
Maybe pop_back() could be made to return the popped element instead of void.

degski
--
"*Ihre sogenannte Religion wirkt bloß wie ein Opiat reizend, betäubend,
Schmerzen aus Schwäche stillend.*" - Novalis 1798

_______________________________________________
Unsubscribe & other changes
Emil Dotchevski via Boost
2017-06-23 07:07:00 UTC
Permalink
Post by degski via Boost
For one, vector functions that grow the size typically return void. So
you
could just turn that into bool or an enum and you'd be done.
Maybe pop_back() could be made to return the popped element instead of void.
You'd be paying for a move you didn't want to buy.

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Peter Dimov via Boost
2017-06-23 09:55:03 UTC
Permalink
Post by degski via Boost
For one, vector functions that grow the size typically return void. So
you could just turn that into bool or an enum and you'd be done.
Maybe pop_back() could be made to return the popped element instead of void.
pop_back doesn't grow the size. It never needs to reallocate. As for why it
doesn't return the element, because copying it may throw.


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Andrzej Krzemienski via Boost
2017-06-26 07:21:57 UTC
Permalink
Post by Niall Douglas via Boost
Post by Niall Douglas via Boost
So result<T, S> is either wearing a "status hat", or it is wearing a
"failure hat". It cannot be both.
The only remaining objection now is that result<...> is named the same
yet has different semantics depending on its S type. Yet error_code is
also a status, it's just a *negative* status rather than a *positive*
status.
I've pushed clarifying reference API docs regarding this trait
separation of positive versus negative status types to
https://dedi4.nedprod.com/static/result-v2/doc_result.html.
I appreciate that this change is controversial. However, I am also
minded that there is no point in Outcome v2 covering the identical
ground as Expected, just less well. Outcome ought to be isomorphic to
Expected, be strong at the areas Expected is weak at and so on.
It seems to me you do not appreciate the value of Outcome v1. The ground is
not identical to Expected. You said you refused to implement some parts of
Expected because of practical considerations -- I was convinced by that as
this indicated that you have tested it in practice both as a user and as an
implementer.

The significant value of Outcome v1 is that you have used it already in two
big libraries. It has been tested in the field. Hence, probably, the
practical solutions like the `TRY` operation. With this library comes a big
practical experience of the author, the micro optimizations, usage idioms.

Now with Outcome v2, you seem to loose this advantage; you are implementing
a theoretical feature, which have never been tested in practice, of which
it is not clear what users expect, and if it even be used.

Regards,
&rzej;

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Andrzej Krzemienski via Boost
2017-06-26 07:14:11 UTC
Permalink
Post by Peter Dimov via Boost
Post by Andrzej Krzemienski via Boost
You could say "if you do not like the state `has both value and error`,
then don't create it", and this could be considered acceptable, but now
that you have said you intend to change "error" into "status", you are
affecting even this conservative usage.
```
Value queue::value_pop();
queue_op_status queue::wait_pop(Value&);
```
pair<optional<T>, status>
pair<optional<T>, optional<status>>
Which means I can get a value with no status. This still does not
implement
Post by Andrzej Krzemienski via Boost
P0262.
In response to yours and Peter's concerns, I've now tightened the
1. Construct with a T. .value() returns that T. .status() throws
bad_result_access. There is no .error().
2. Construct with a T and an S. .value() returns that T, .status()
returns that S. There is no .error().
And that's it. If trait::enable_errored_result_creation<S> is true
1. Construct with a T. .value() returns that T. .error() throws
bad_result_access. There is no .status().
2. Construct with an S. .value() throws that S according to policy.
.error() returns that S. There is no .status().
So result<T, S> is either wearing a "status hat", or it is wearing a
"failure hat". It cannot be both.
The only remaining objection now is that result<...> is named the same
yet has different semantics depending on its S type. Yet error_code is
also a status, it's just a *negative* status rather than a *positive*
status.
If you are determined to go this way, why not offer two separate templates:
`annotated` and `result`. The former for the "status" functionality, the
latter for the Outcome-v1-like "either T or error", and have them just
share the same implementation. Since they have different semantics and
member functions why have them share the same name?

Regards,
&rzej;

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas via Boost
2017-06-26 14:52:39 UTC
Permalink
Post by Andrzej Krzemienski via Boost
Post by Niall Douglas via Boost
The only remaining objection now is that result<...> is named the same
yet has different semantics depending on its S type. Yet error_code is
also a status, it's just a *negative* status rather than a *positive*
status.
`annotated` and `result`. The former for the "status" functionality, the
latter for the Outcome-v1-like "either T or error", and have them just
share the same implementation. Since they have different semantics and
member functions why have them share the same name?
So good news for you, yesterday after pondering things over the weekend
I disabled the positive status functionality. It can be reenabled via
macro, but it's off by default. result<T, E> v2 is now a strict subset
of the Outcome v1 result<T>, except you can choose your own E.

I'm not far from finishing outcome<R, S, P>, maybe tomorrow. I'll then
be replacing the v1 implementation with the v2, and get to work on
getting the existing unit test suite to compile and pass.

After that Outcome v2 will undergo at least three months of being tested
in real world usage before I consider it ready for review again. During
this time I'll deliver that AFIO v2 based NuDB alternative I promised
Vinnie, it'll be an excellent test of the v2 Outcome.

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
Emil Dotchevski via Boost
2017-06-22 19:01:40 UTC
Permalink
On Wed, Jun 21, 2017 at 7:23 PM, Niall Douglas via Boost <
Post by Niall Douglas via Boost
Post by Peter Dimov via Boost
Post by Niall Douglas via Boost
The idea, since we no longer have variant storage, is to see if we'd
like to implement
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0262r0.html
(except with the parameter order reversed).
I'm not particularly enthusiastic about status_value (and the argument
given in the paper is unconvincing.) Neither am I thrilled by the idea
of result<R, void> (an optional) or result<void, void> (something like a
glorified pair<bool, bool>.)
I've fixed the problems you raised at
https://dedi4.nedprod.com/static/result-v2/doc_result.html and moved the
implementation stuff into a impl namespace so standardese will still
show the docs for the composited types, but not be usable by end users.
Post by Peter Dimov via Boost
result<T, EC> in its original form represented a very clear concept: the
type of a function that must either return a value, or a reason for the
lack of value. All of the above do not fit this concept and to me ring
hollow.
Andrzej feels similarly to you.
But I don't think things are as bad as both of you feel. In v2 result<T,
1. Successful: T
2. Failure: EC
3. Successful + Status: T + EC
The reason I don't think this is a problem is because you can't
accidentally return a wrong result<T, EC> in practice. Either you're
using result to return Success + Status
If I wanted to return Sucess + Status, I would return pair<Success,Status>
as the value (as opposed to the error) from a given function.

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Andrzej Krzemienski via Boost
2017-06-22 07:43:11 UTC
Permalink
Post by Niall Douglas via Boost
Dear Boost,
I've been hard at work refactoring Outcome to match the peer review a
few weeks ago. Much comment at the time mentioned the poor experience of
https://dedi4.nedprod.com/static/result-v2/doc_result.html
This was generated by the new C++ reference docs generator
https://github.com/foonathan/standardese. You'll no doubt spot quite a
few formatting bugs, Jonathan says he's onto them. But the usage of
libclang rather than doxygen's broken C++ parser clearly shows a lot of
benefit, it's just standardese still remains quite a lot short in terms
of necessary features to make output fully correct.
I won't give the changelog for Outcome v2 result<T> over Outcome v1 yet.
Let's see how you fare with the above reference API docs first. But as
you'll note, there is considerable change over before as we now slot
into a niche not occupied by Expected nor EitherMonad, indeed this now
looks much more like Robert's or Lawrence Crowl's slimmed down object.
This result<T> is approx 0.6 CPU cycles per stack frame unwound slower
which I feel is acceptable in exchange for no more variant storage,
which in turn released lots of SFINAE budget for me.
(FYI v2 outcome<T> isn't done yet, but it will extend v2 result<T>)
Niall,
There is certainly more information now in the reference section than
before. This is clearly an improvement. You probably want to conceal some
of the types used for implementation from the docs, like those from
namespace `impl`, but I understand this is just a prototype.

One thing I want to suggest, is that the standardese used in the standard
Library has problems of its own, that you have a chance of avoiding. For
instance, as described here:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0411r0.html

the "Requires" clause mixes two things: (1) requirements that can be (and
are) checked at compile-time (like DefaultConstructible), and (2)
requirements on values that if not met cause UB (like `i < this->size()`).
The linked paper proposes, and I encourage the same to use two different
clauses:

Precondition: i < this->size();
Requires: is DefaultConstructible

And as some have suggested, even a third one that describes all the
enable_if-s in the interface:

"Enabled_if: is_copyable_v<T>" instead of "Effects: this overload shall not
participate in overload resolution unless is_copyable_v<T> is true".

Regards,
&rzej;

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Niall Douglas via Boost
2017-06-22 17:46:29 UTC
Permalink
Post by Andrzej Krzemienski via Boost
There is certainly more information now in the reference section than
before. This is clearly an improvement. You probably want to conceal some
of the types used for implementation from the docs, like those from
namespace `impl`, but I understand this is just a prototype.
Namespace impl is purely due to current limitations in the standardese
tool. It's supposed to be namespace detail.
Post by Andrzej Krzemienski via Boost
One thing I want to suggest, is that the standardese used in the standard
Library has problems of its own, that you have a chance of avoiding. For
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0411r0.html
the "Requires" clause mixes two things: (1) requirements that can be (and
are) checked at compile-time (like DefaultConstructible), and (2)
requirements on values that if not met cause UB (like `i < this->size()`).
The linked paper proposes, and I encourage the same to use two different
Precondition: i < this->size();
Requires: is DefaultConstructible
And as some have suggested, even a third one that describes all the
"Enabled_if: is_copyable_v<T>" instead of "Effects: this overload shall not
participate in overload resolution unless is_copyable_v<T> is true".
Enhancement submitted to https://github.com/foonathan/standardese/issues/62

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
Loading...