Boost logo

Boost :

Subject: Re: [boost] Outcome v2 is feature complete
From: Niall Douglas (s_sourceforge_at_[hidden])
Date: 2017-07-11 14:09:13


>> Github: https://github.com/ned14/outcome
>>
>> Docs (highly incomplete): https://ned14.github.io/outcome/
>
> This is a welcome development, if still a bit too-policy-heavy for my
> taste.

The policy is there purely to say what to do when you call .value() on a
non-valued result/outcome. Compelling use cases could be:

- Delay constructing metadata to accompany any exception throws.
- Use .error() as a key to look up an associative map.
- Invoke Emil's Noexcept instead :)

One could have used ADL customisation points instead, but those tend to
surprise end users, so I felt the policy approach was better and leave
the ADL points for advanced users.

> Regarding construction, I have the following suggestion.
>
> Instead of reusing in_place_type, use the following tag types:
>
> struct in_place_value_t
> {
> constexpr in_place_value_t() noexcept {}
> };
>
> constexpr in_place_value_t in_place_value;
>
> struct in_place_error_t
> {
> constexpr in_place_error_t() noexcept {}
> };
>
> constexpr in_place_error_t in_place_error;
>
> This is isomorphic but superior to using in_place_index<0> and
> in_place_index<1>, which were my initial choice. Now the ambiguity when
> T == EC is resolved and
>
> template<class... A> result( in_place_value_t, A&&... a );
>
> always initializes the value and does not need to be disabled when T == EC.

You've struck on a very interesting design point indeed, and one which
caused me a few restless nights.

My initial approach was to do exactly what you just described, after all
it's how v1 implemented its tagged emplacing constructors, and v1 had a
host of make_(valued|errored|excepted)_*() functions too. v1's API was
all about construction of specific, named state.

But upon further reflection - and I want to emphasise that I think the
jury is still out on this decision, and I may revert during the next few
months - I decided that std::variant<> by-type emplacement was superior,
so I went with that instead.

The reason why is subtle. These types, result & outcome, are not in the
same use space as Expected where expected<int, int> is perfectly
reasonable due to its use case as a monad. result & outcome are
specifically intended to be used for one thing and one thing only:
returning value *or* error from functions.

And for that one use case, result<int, int> makes no sense. Actually,
it's stronger than that: result<int, int> must **NOT** make sense.
Everything about the API and contract must loudly proclaim that
"ambiguous success and failure types are discouraged" because the type
system should be set up to reflect the non-ambiguity of success vs failure.

Now, we don't ban in place construction of ambiguous success and failure
types because sometimes you are facing code you didn't write yourself,
and you have to deal with it. So you make it possible. But you do make
the programmer type a lot more. You make it annoying.

> In addition, I would add
>
> template<class... A> result( A&&... a );
>
> which initializes the value when it's constructible from a..., the error
> when that's constructible from a..., and is disabled when neither or
> both are constructible. (The implicit/explicit duality when sizeof...(A)
> == 1 complicates the actual implementation of the above but conceptually
> it still works as explained. Also, the sizeof...(A) == 0 case needs to
> be disabled.)

The reason I kept to single argument converting constructors is that I
felt that the added gain of not having to type in_place_type<> was not
worth the additional compile time load. Also variadic templates are slow :(

And finally, std::variant doesn't implement multiple argument converting
constructors. And I figured WG21 know what they're doing.

Niall

-- 
ned Productions Limited Consulting
http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk