Subject: Re: [boost] Boost.Outcome review - First questions
From: Vicente J. Botet Escriba (vicente.botet_at_[hidden])
Date: 2017-05-23 19:13:21
Le 23/05/2017 à 17:04, Niall Douglas via Boost a écrit :
>>> Formal empty state remains for outcome<T>, result<T>, option<T>. As I've
>>> hopefully shown by now in other discussion threads, the formal empty
>>> state is very useful, saves a lot of boilerplate. For those not wanting
>>> the empty state, there is expected<T, E>.
>> Okay, understood, even if I don't agree, at least for result<T> and
>> option<T>. For outcome<T> I have my doubts, I don't know if the
>> never-empty warranties can be implemented in an efficient way.
> Why do you think outcome<T> cannot implement a never empty warranty?
> Perhaps you are thinking of std::exception_ptr's very unfortunate lack
> of exception warranties on its move and copy constructors?
> I agree that the lack of definition by the C++ standard there is deeply
> unhelpful. Older STLs also didn't implement a move constructor because
> the standard doesn't require one, and marked the copy constructor as
> throwing. Very unhelpful.
> But recent STLs appear to have since added a nothrow move constructor. I
> think we are safe-in-practice rather than safe-through-design.
We need a change on the standard to ensure that.
>>>>> raw_value_type, raw_error_type and raw_exception_type are the true,
>>>>> original type configured. You are correct that this is a deviation from
>>>>> LEWG Expected which would cause code written for LEWG Expected to fail
>>>>> to compile with Outcome's Expected. I think that safe.
>>>> If the raw_ are the reason d'être to avoid the void specialization, and
>>>> this avers to be a useful technique, I believe it merits a full
>>>> implementation section explaining how this improve the compiler
>>>> performances, DRY, et all.
>>> I had thought it a very common technique, so common that it not worth
>>> explaining here.
>> Maybe you can point me to some blog, mail exchange, ...
>>> Am I wrong on this?
>> Possibly. What is clear to you is is not forcedly to other.
> Ok, people please tell me if this technique is unknown to them:
> template<class A, class B, class C> class Something
> class A_construction_disabled // note this is in private:
> A_construction_disabled() = delete;
> using enable_A_construction = typename
> std::conditional<std::is_void<A>::value, A_construction_disabled, A>::type;
> // Implicit constructor from A, if not void
> Something(enable_A_construction &&a) ...
> This lets you avoid enable_if testing for void on every single member
> function able to consume a type A. Surely this is a widely used and well
> understood metaprogramming technique?
Thanks for explaining.
Does this provide a constructor without argument, when A is void??
>>> The answer is that we currently don't. The seamless interop applies to
>>> within Outcome's island of types only. However it is very
>>> straightforward to write converting constructors to construct the std::*
>>> editions into Outcome editions. Getting them back out is on the end
>>> user, though I could be persuaded to write an explicit conversion
>>> operator for the std::** editions.
>> So if the interaction is 3pp libraries is by defining conversion
>> operators, why do we need Outcome. We could define already those
>> operators without making the design more complex.
> The intention is to provide interaction with *STL* supplied alternatives
> via this means. That's because we can't adjust the source code of STL types.
Except if we write a proposal ;-)
> Third party code where the author may modify the source should supply a
> custom policy class to basic_monad.
No, I don't think creating a new class using outcome is a solution. If
the 3pp has already one the user must be forced to use it.
>> For example I'm proposing a ProductType concept to the standard. Each
>> product type could define when it converts from any ProductType
>> satisfying certain properties. This will simplify and extend the current
>> tuple-like interfaces with less code and more software.
>> I believe we could define a PossiblyValued type of classes and define
>> conversion from them in a non intrusive way. Note that I used
>> PossiblyValued and not MonadError as the required interfaces wil not be
>> the same. A PossiblyValued value type that is close to Nullable, but
>> where the not-a-value is considered an error and can have multiple
>> instances. optional and expected and any the smart pointer I know will
>> be PossiblyValued types.
>> Let me know if you consider this design on the ivory tower and a weird
> I think this is all great stuff for pondering for future C++ standard
> libraries. But not strictly relevant to this Outcome review.
It is related as the approach is applicable to classes as optional,
It is related because you are not proposing any Monadic interface
(bind,unwrap) but just a PossibleValued interface.
>>> That would be the traditional way of implementing this. I chose not to
>>> follow that design in this particular case.
>> I've no problem as far as the common implementation is hidden and
>> doesn't guide the concrete interface. I believe that generic programming
>> must be built on top od concepts, not inside a intrusive class that know
>> how to do everything.
> I think you may not have studied the source code sufficiently.
Not at all. Not yet ;-)
> basic_monad definitely does not know how to do everything.
> basic_monad takes a policy class to flavour it with personality. The
> policy class is a type full of typedefs and constants setting up what
> this particular basic_monad will do. The storage layout is strictly
> separately defined by policy than the public member functions mixed in
> by the policy to the final basic_monad class instantiated.
> So, you could use a std::variant for the storage layout, or three
> std::optional<>'s, or three unique_ptr<>'s, or more excitedly, a
> std::expected<T, E>! Up to the definer.
> The public member functions provided by this particular basic_monad are
> customisable by a separate sub-policy policy. basic_monad itself
> provides all the public member functions guaranteed to be common across
> all basic_monad varieties, though it can and does call into the policy
> class for implementation. This common interface allows a common
> machinery to be built on top, so specifically the implicit conversion
> from less to more representative, and the TRY operation.
> So yes the above is intrusive, it forces an outcome-y thing to always be
This is what I don't like. It is if in order to use rang algorithms I
would need to wrap any possible range. We want to be able to use any
range without wrapping them.
> But how you implement it is up to you, so long as you fulfil the
> Outcome public contract.
>>>> Niall, I'm not against not for you library. We're just reviewing it now.
>>>> For me the goal is to improve it, and if we can at the same time improve
>>>> the std expected proposal this will be very valuable to the C++
>>> I would be very glad if this process helps the Expected proposal.
>>> Expected is a great proposal, it needs into the C++ standard.
>> It is weird, that you say that it is a great proposal, but you found
>> something that must be added, modified or removed. I would like to
>> understand these differences, because surely the expected design must be
>> improved when I would take into account *valid* use cases that I have
>> not considered yet.
> I nitpick tiny corner case problems, but I consider the overall proposal
> to be great. I do thank you personally in Outcome's Acknowledgement for
> all your service in making Expected happen, without all your work
> Outcome would not exist and my other C++ libraries using Outcome would
> be the worse for it.
You are welcome.