Boost logo

Boost :

Subject: Re: [boost] [review] Review of Outcome v2 (Fri-19-Jan to Sun-28-Jan, 2018)
From: Andrzej Krzemienski (akrzemi1_at_[hidden])
Date: 2018-01-29 11:59:10

2018-01-28 5:02 GMT+01:00 Emil Dotchevski via Boost <boost_at_[hidden]>:

> On Sat, Jan 27, 2018 at 5:33 AM, Vinícius dos Santos Oliveira <
> vini.ipsmaker_at_[hidden]> wrote:
> > 2018-01-27 0:31 GMT-03:00 Emil Dotchevski via Boost <
> boost_at_[hidden]
> > >:
> >
> >> Question: if using the OUTCOME_TRY macro is equivalent to calling the
> >> function, checking for error and then returning an error if there is an
> >> error, how is this different from using exceptions? Semantically,
> >> exception
> >> handling does nothing more than check for errors and returning errors if
> >> there were errors
> >
> >
> > There is a single control flow to analyse: the return of the function.
> You
> > don't need a "parallel" control flow construct to check for error case.
> >
> Where is the parallel control flow in return parse(read_data(open_file()))?
> >
> > - You can't forget to check the error case (it's part of the type
> > system).
> >
> > You can't forget to check for errors if you use exceptions, either.
> Literally, if you use exceptions it is as if the compiler writes the ifs
> for you.
> >
> > - It's self-documenting.
> >
> > Only to the extent that you can see that a function may return an error.
> With exceptions, except for noexcept functions, functions may "return" an
> error.
> Some would count the fact that with e.g. Outcome you can specify what kind
> of errors can be returned as an advantage, but that is similar to
> statically enforced exception specifications. Sutter explained why that is
> a bad idea back in 2007:
> 01/24/questions-about-
> exception-specifications/.
> >
> > - There are no strange interactions between Outcome and the rest of
> > the language (e.g. throwing destructors, transporting exception
> between
> > threads, and so on...).
> >
> > So, don't throw in destructors. Also, you can't use Outcome in
> destructors, but that is fine -- it is a logic error to not be able to
> destroy an object.
> Though this reminds me: in C++, exceptions are the only way constructors
> may report an error, and this is very deliberate, integral part of RAII.
> This guarantees that you can't use an object that failed to initialize,
> which is the reason why member functions are free to assume, rather than
> check, that all invariants of the class have been established.
> Thus, exception handling is an integral part of the C++ object
> encapsulation model. Choose to not use exceptions and the result is that
> like in C, each function must check whether the object was initialized, and
> return some error code to indicate that condition. You're replacing a
> bullet-proof automatically enforced error checking system with a manual
> one, prone to errors; worse, we're talking about error handling code, which
> by its very nature is difficult to test.

There is a usage model that allows you to both report failures via result
and have your types retain strong invariants. Once you have decided to use
"explicit failure execution paths" approach in a library, you provide
factory functions rather than constructors as the interface for creating
new objects.

This solves another long-standing problem of C++ constructors: that they do
not have names and sometimes you do not know what they do:

vector<char> v1 (20); // size or capacity?
vector<char> v2 {size_t(20), 'c'}; // 20 elements or just 2?
vector<T> v3 {x.begin(), x.end()} // 2 elements or x.size() elements?
point<double> p {1.141, 0.535}; // polar or Cartesian co-ords?


Boost list run by bdawes at, gregod at, cpdaniel at, john at