Boost logo

Boost :

From: Emil Dotchevski (emildotchevski_at_[hidden])
Date: 2020-06-08 03:07:52


On Sun, Jun 7, 2020 at 2:19 PM Joaquin M López Muñoz <
joaquinlopezmunoz_at_[hidden]> wrote:
> So, if I'm getting this right, the benefit of try_catch vs. try_handle_*
> is that
> the returned type need not be a result<T> (or similar) thing. Is this
right?
>
> Can you explain the benefits of using try_catch vs. resorting to
> language-level
> "real" try and catch blocks?

One benefit is that you can associate arbitrary error objects with
exceptions, and dispatch error handling based on their availability. For
example, if you use Boost Exception, you could say:

try
{
  f();
}
catch( read_error const & e )
{
  if( auto const * fn = boost::get_error_info<file_name>(e) )
    ....; // The exception has file_name associated with it
  else
    ....; // file_name not available
}

With LEAF, you would instead say:

leaf::try_catch(
  []
  {
    f();
  },
  []( leaf::catch_<read_error>, file_name const & fn )
  {
    ....; // The exception has file_name associated with it
  },
  []( leaf::catch_<read_error> )
  {
    ....; file_name not available
  } );

This is both cleaner as well as easier to read, as the dispatching follows
a uniform pattern, rather than custom if-else logic.

(Also, Boost Exception allocates memory dynamically, LEAF does not).

So, we could select error handlers based on the type of the exception as
usual, but we could also do it based on the types of the other available
error objects. Therefore, we can ditch exception type hierarchies
altogether.

Consider this exception type hierarchy:

struct read_error: virtual std::exception { };
struct file_error: virtual std::exception { };
struct file_read_error: virtual file_error, virtual read_error { };

What's the point of this? It is to enable us to handle errors like so:

try
{
  f();
}
catch( file_read_error const & )
{
  ....; // handle file read errors
}
catch( read_error const & )
{
  ....; // handle read errors that are not file errors
}

LEAF supports a style of error handling where all exceptions thrown are of
type std::exception. In that case, instead of the hierarchy above, we could
simply define:

struct read_error { };
struct file_error { };

Then, to indicate a file read error, we could say:

throw leaf::exception(std::exception(), file_error { }, read_error { });

To handle errors:

leaf::try_catch(
  []
  {
    f();
  },
  []( file_error, read_error )
  {
    ....; // handle file read errors
  },
  []( read_error )
  {
    ....; // handle read errors that are not file errors
  } );

Note that we don't bother to use catch_<>, because try_catch always catches
exceptions, and we've chosen to not dispatch based on the exception type.

Is this a good idea? I think that it is. For example, it enables us to use
preload() to add further classification after the initial reporting of the
error, as it bubbles up the call stack:

config read_config(....);
{
  // mark *any* failure that occurs inside read_config as read_config_error:
  auto load = leaf::preload( read_config_error { } );

  .... // Call functions which throw on error
}

Then:

leaf::try_catch(
  []
  {
    auto c = read_config();
    ....; // call more functions which may fail
  },
  []( read_config_error, json_parse_error )
  {
    ....; // handle read_config failures due to a json parse error
  },
  []( read_config_error)
  {
    ....; // handle any other read_config fauilure.
  } );


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