Boost logo

Boost :

Subject: Re: [boost] Noexcept
From: Emil Dotchevski (emildotchevski_at_[hidden])
Date: 2017-06-20 08:32:41


On Mon, Jun 19, 2017 at 11:58 PM, Andrzej Krzemienski via Boost <
boost_at_[hidden]> wrote:

> 2017-06-20 3:38 GMT+02:00 Emil Dotchevski via Boost <boost_at_[hidden]
> >:
>
> > On Mon, Jun 19, 2017 at 2:41 PM, Andrzej Krzemienski via Boost <
> > > 1. I want to separate resource acquisition errors (exceptions are still
> > > thrown upon memory exhaustion) from input validation.
> >
> > Why?
>
> ...

I do not even treat validation failure as
> "error". But I still like to have the "short exit" behavior of errors.
>

If it's not an error then it is not an error -- and you should not treat it
as such.

> > > 2. Some debuggers/IDEs by default engage when any exception is thrown.
> I
> > do
> > > not want this to happen when an incorrect input from the user is
> > obtained.
> > >
> >
> > "By default", so turn off that option.
>
> But after a while I have concluded that it is a good default. Even if I am
> debugging something else, if I get a "resource-failure", or "logic error"
> (like invariant broken)

Yes, std::logic_error is another embarrassment for C++. Logic errors by
definition leave the program in an undefined state, the last thing you want
to do in this case is to start unwinding the stack. You should use an
assert instead.

> I want to be alerted, and possibly stop what I was
> debugging before. This default setting is my friend, provided I do not use
> exceptions for just any "irregularity".
>

Exceptions are not used in case of "irregularities" but to enforce
postconditions. When the program throws, it is in well defined state,
working correctly, as if the compiler automatically writes "if" statements
to check for errors before it executes any code for which it would be a
logic error if control reaches it. The _only_ cost of this goodness is that
your code must be exception safe.

Programmers who write debuggers that by default break when a C++ exception
is thrown likely do not understand the semantic differences between OS
exceptions (e.g. segfaults, which *do* indicate logic errors) and C++
exceptions. Semantically, that's like breaking, by default, every time a C
function returns an error code.

> > > 3. I want validation failers to be handled immediately: one level up
> the
> > > stack. I do not expect or intend to ever propagate them further.
> >
> > You can catch exceptions one level up if you want to. Right? :)
>
> I can. And it would work. But it just feels not the right tool for the job.
> It would not reflect my intention as clearly as `outcome`.
>

That's because (in your mind, as you stated) you're not using Outcome to
handle "real" errors.

> > However, if you're only propagating errors one level up, it really
> doesn't
> > matter how you're handling them. I mean, how much trouble can you get
> into
> > in this case? It's trivial.
>
> But t reflects my intentions clearly and gives me confidence that the error
> information will not escape the scope if I forget to put a try-block

Not really, if you forget to check for errors and call .value() on the
outcome object, it'll throw (if I understand the outcome semantics
correctly).

That exceptions are propagated if you forget to handle them when you should
is a good thing. It means no error gets ignored.

> Error handling libraries are needed in more complex use cases where errors
> > must be propagated across multiple levels, across threads, across API
> > boundaries. The important design goals are:
> >
> > 1) The error object created by reporting code should be able to be
> > propagated across (potentially many) error-neutral contexts which should
> > not be required to "translate" it (that is, turn it into a different
> error
> > object.) The idea of translation of errors gave us exception
> specifications
> > which are notoriously one of the more embarrassing aspects of C++.
> >
> > 2) Error-neutral contexts should be able to ignore any errors reported by
> > lower level code but also intercept _any_ error, augment it with relevant
> > information (which may not be available at the point the error is
> detected)
> > and let it propagate up the call stack, intact.
> >
> > 3) Error-handling contexts should be able to recognize the errors they
> can
> > deal with but remain neutral to others.
>
> I recognize these needs. And in the contexts where you require the above
> characteristics (probably 97% of all code) exceptions are the tool for the
> job.
>
> For rare situations where I need different characteristics of error
> reporting mechanism, I will need to resort to something else, like a
> dedicated library.
>

I personally think that libraries are definitely needed when they can deal
efficiently with 97% of all use cases, the remaining 3% being not nearly as
important. Evidently we disagree.

> > Your use of outcome is probably fine in this simple case but
> >
> > out::expected<Range, BadInput> parse_range (const std::string& input)
> >
> > looks much too close to exception specifications:
> >
> > Range parse_range(const std::string& input) throw(BadInput)
>
> In some other language - yes. In a language, where such throw specification
> is enforced statically, like in Java.
>

It's a bad idea. Again: generally, functions (especially library functions)
can not know all the different kinds of errors they might need to forward
(one way or another) up the call stack. From
https://herbsutter.com/2007/01/24/questions-about-exception-specifications/:

"When you go down the Java path, people love exception specifications until
they find themselves all too often encouraged, or even forced, to add
throws Exception, which immediately renders the exception specification
entirely meaningless. (Example: Imagine writing a Java generic that
manipulates an arbitrary type T…)"

> Assuming we agree that it is not
> > acceptable for error-neutral contexts to kill errors they don't
> recognize,
> > this is a problem.
>
> Ok. It is just that I have parts of the program that I do not want to be
> exception neutral by accident.
>

You mean bugs where exceptions aren't handled when they should be, which in
C++ may result in std::terminate, which seems harsh. But these bugs are
also possible when errors aren't reported by throwing, the result being
that they're neither handled nor propagated up the call stack -- in other
words, they're being ignored. I submit that std::terminate is much
preferable outcome in this case (no pun intended).


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