Boost logo

Boost :

Subject: Re: [boost] [outcome] Second high level summary of review feedback accepted so far
From: Andrzej Krzemienski (akrzemi1_at_[hidden])
Date: 2017-06-06 08:45:34


2017-06-06 4:35 GMT+02:00 Gavin Lambert via Boost <boost_at_[hidden]>:

> On 5/06/2017 11:08, Peter Dimov wrote:
>
>> The correct approach here, I think, is not to define fat error
>> codes, but to return an outcome whose error() is the error_code and
>> whose exception() is the appropriate filesystem_error exception,
>> complete with the path(s).
>>
>
> This sounds like the design I was suggesting in the "to variant or not to
> variant" thread.
>
>
> On 6/06/2017 02:46, Peter Dimov wrote:
>
>> Vicente J. Botet Escriba wrote:
>>
>> Oh, in this case it is clear that result<T> is not enough. We need
>>> expected<T, filesystem::error>
>>> where filesystem:error contains the error_code and the path.
>>>
>>
>> I don't find this particularly appealing, because the whole point of
>> std::error_code was to avoid the need for each library to define separate
>> error classes. expected<T, filesystem::error> will work, I'd just not be
>> willing to recommend it as a general solution.
>>
>
> Is it more attractive if perhaps we had a result<T, E> where E was
> constrained to a type derived from std::error_code? (I'm imagining a sort
> of orthogonal hierarchy with types derived from std::exception, although
> not for error-identification purposes, just to carry additional data
> payloads.) Implicit slicing would allow this to be used generically in
> contexts where an error_code is expected.
>
> The error_code design is a little unfortunate since users are encouraged
> to pass them around by value, which would slice off additional context
> information. OTOH even if passed around by reference so the information is
> preserved, it wouldn't be readily accessible without dynamic_casts. (The
> same is true for exceptions as well, but some language/compiler conspiracy
> hides this.)
>
> The idea of error_code having an arbitrary error-info pointer seems
> attractive, but the above semantics would probably require it being a
> shared_ptr<void>, which would probably annoy everybody for various reasons
> (including type safety, atomics, and memory allocation).
>
> Niall's error_code_extended is a sort of half-way point where it provides
> some additional fixed data payload which is probably useful in many cases
> as a compromise type; additionally defined as POD to avoid dynamic
> allocation and so that the ring buffer stomping over the data doesn't upset
> too many things. It's a good idea, and I'm not sure if we can come up with
> something better (other than arguing about what members it should have),
> but perhaps there are some other possibilities to consider.
>
> Perhaps rather than allowing arbitrary Es we could have a single E that
> still provides some additional flexibility:
>
> template<typename EI>
> struct error_code_info : public error_code
> {
> // ...
> const optional<EI>& error_info() const;
> // ...
> optional<EI> m_error_info;
> };
>
> template<typename T, typename EI>
> using result = expected_impl<T, error_code_info<EI>>;
>
> (Again, just a sketch to present an idea; don't get too hung up on the
> specifics.)
>
> Could something like this work? An error can be specified with an
> arbitrary data payload, and yet consumers could just slice that off and
> treat it as a plain error_code if they want. Meanwhile the result/expected
> implementation can rely on noexcept/move guarantees provided by
> error_code_info.
>
> The main complication I see with this is that you probably don't want to
> over-constrain the EI type, to simplify usage -- that's why I suggested it
> store optional<EI> rather than EI directly, so that error_code_info could
> be noexcept in all cases; if EI doesn't have noexcept move/copy then
> error_code_info just discards the info if EI's constructor throws.
>
> (So this does allow an empty state and the empty state should not be
> considered weird; consumer code just needs to tolerate that like they would
> any other optional<>.)
>
> This does mean that consumers can introduce memory allocation into their
> error codes (probably via std::string), which some people won't like -- but
> this leaves the choice of doing that or avoiding that up to them (and the
> libraries they choose to consume).
>
> The main downside I see of this is that it's less straightforward to pass
> around error_code_info<EI>s than error_codes, but since this should be
> mostly confined to a well defined call chain (with specific concrete EI
> values rather than generics) I hope this wouldn't be a problem in actual
> practice. (Using a shared_ptr<void> would avoid that issue but I think
> that's probably worse overall.)
>
> It might also result in a proliferation of EI types (as each method of
> each library could conceivably define a unique one) but again I doubt that
> should be an issue in practice.

But does this preserve the guarantee that result<T, E> is trivially
copyable?

Another alternative to consider is to change something in the ring buffer
records, so that it resembles:

```
struct RingBufferRecord
{
  enum {/*name different kind of payloads*/} _discriminator;
  union
  {
    /*define different kinds of payload*/
  }
};
```

This way the enumeration tells you how to reinterpret the data. You do not
know all possible payloads when designing the ring buffer, so you probably
need to introduce something akin to virtual functions:

```
struct RingBufferRecord
{
  aligned_storage<256> _raw_storage;
  void (*_interpret_raw_storage) (aligned_storage<256>&);
};
```

I admit this idea is not well thought over.

Regards,
&rzej;


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