Boost logo

Boost :

Subject: Re: [boost] Boost.Outcome review - First questions
From: Niall Douglas (s_sourceforge_at_[hidden])
Date: 2017-05-23 15:04:37


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

>>>> 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;

public:
  // 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?

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

Third party code where the author may modify the source should supply a
custom policy class to basic_monad.

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

I think this is all great stuff for pondering for future C++ standard
libraries. But not strictly relevant to this Outcome review.

>> 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.
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
made. 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++
>>> community.
>> 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.

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