Well, the reason exceptions should be reserved for exceptional cases
is because they
are gotos. There is no way around that, a function
that both returns values and throws exceptions is more complex than
one that doesn't because you are jumping through call frames.
I once worked with a billing system that threw a BillingException when
a charge didn't go through. It works ok, as long as your only response
to such an error is to spew an error message to the user. But the more
you need to handle it, the less an exception makes sense. The code
that tried to handle the BillingException was a tangled mess of try
and catch statemetns mixed with retry loops. For example, if there was
a temporary error at the payment provider, you would just retry a few
times, a permanent error, try with another account, if the customers
remaining funds were to low show an error message or if they were just
a few dollars of, charge them that and be happy we got something from
them.
On the other hand, the billing system could also throw a DbException
in case there was something wrong with the database connection. That's
a different kind of problem and fatal for a database-backed
website. Nothing to do, except log the error and crash.
The point of exceptions is not so much that you can catch and handle
them, but that it separates exceptional situations from in-band normal
error handling. Now what is in-band and what is exceptional depends on
the circumstances which is why, as you say, Boost.ASIO provides both
variants. If your system is supposed to handle the errors, use the one
without exceptions. If not then use the one with exceptions.