$include_dir="/home/hyper-archives/boost/include"; include("$include_dir/msg-header.inc") ?>
Subject: Re: [boost] [outcome] Second high level summary of review feedback accepted so far
From: Gavin Lambert (gavinl_at_[hidden])
Date: 2017-06-06 02:35:13
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.