2
0
mirror of https://github.com/boostorg/leaf.git synced 2026-01-24 18:02:22 +00:00
2018-10-13 10:36:54 -07:00
2018-10-13 10:30:17 -07:00
2018-10-13 10:30:17 -07:00
2018-09-16 15:56:34 -07:00
2018-10-06 10:10:56 -07:00
2018-09-21 16:36:39 +03:00
2018-10-13 10:30:17 -07:00
2018-10-07 07:42:51 -07:00

:sourcedir: .
:last-update-label!:
:icons: font
= LEAF
Low-latency Error Augmentation Framework for C++11
:toclevels: 3
:toc: left
:toc-title:

[abstract]
== Abstract

LEAF is a non-intrusive C++11 library for transporting arbitrary error information from contexts that detect and report failures, as well as from intermediate error-neutral contexts, to scopes where the error is ultimately handled.

LEAF does not allocate dynamic memoryfootnote:[Except when transporting error info between threads, see <<capture,`capture`>>.], which makes it suitable for low-latency and other performance-critical environments. It is compatible with all error handling APIs, and can be used with or without exception handling.

[[distribution]]
== Distribution

LEAF is distributed under the http://www.boost.org/LICENSE_1_0.txt[Boost Software License, Version 1.0].

The source code is available in https://github.com/zajo/leaf[this GitHub repository].

NOTE: LEAF is not part of Boost. Please post questions and feedback on the Boost Developers Mailing List.

[[building]]
== Building

LEAF is a header-only library and it requires no building. The unit tests use Boost Build, but the library itself has no dependency on Boost or any other library.

[[portability]]
== Portability

LEAF requires a {CPP}11 compiler. See unit test matrix at https://travis-ci.org/zajo/leaf[Travis-CI].

[[tutorial]]
== Tutorial

As an example, here is a function that reads data from a file into a buffer, returns a user-defined `error` enum to indicate success or to report errors, and uses LEAF to provide additional error info:

====
[source,c++]
----
error file_read( FILE & f, void * buf, int size ) {
  int n = fread(buf,1,size,&f);

  if( ferror(&f) ) { <1>
    leaf::put( ei_errno { errno } );
    return file_read_error;
  }

  if( n!=size ) <2>
    return file_eof_error;

  return ok;
}
----
<1> The function fails if `ferror` indicates an error.
<2> It also fails if `fread` reports that it didn't read all of the data requested.
====

In the first case, there is a relevant `errno` code, so the function uses `<<put,put>>` to make it available later when the error is handled.

The second condition, while treated as a failure by our `file_read` function, is not a failure as far as `fread` is concerned, and therefore `errno` is not available. So in this case we simply return `file_eof_error`.

The `ei_errno` struct is a simple error info type, defined as follows:

[source,c++]
----
struct ei_errno { int value; };
----

The user can define any number of similar types and, when an error is detected, pass them to `<<put,put>>` to make relevant information available to error handling contexts, if needed.

NOTE: Error info types must define a public data member called `value` and must provide a `noexcept` move constructor and `noexcept` destructor. Any number of valid error info objects may be passed in a single call to `<<put,put>>`.

Now, let's consider a possible caller of `file_read`:

====
[source,c++]
----
error print_file( char const * file_name ) {
  std::shared_ptr<FILE> f;
  if( error err = file_open(file_name,f) ) <1>
    return err;

  auto put = leaf::preload( ei_file_name {file_name } ); <2>

  int s;
  if( error err = file_size(*f,s) )
    return err;

  std::string buffer( 1+s, '\0' );
  if( error err = file_read(*f,&buffer[0],buffer.size()-1) )
    return err;

  std::cout << buffer;
  std::cout.flush();
  if( std::cout.fail() )
    return cout_error;

  put.cancel();
  return ok;
}
----
<1> This function takes a `file_name` argument and calls another function, `file_open` (not shown), that opens the file for reading. If that function reports a failure, we simply forward it to the caller.
<2> Next, we call the convenience function `<<preload,preload>>`, moving an `ei_file_name` error info object into the temporary object `put`. Unless canceled, when this object is destroyed, all error info objects passed to `preload` will be forwarded by rvalue reference to `<<put,put>>` automatically. This way we can rest assured that the file name will be available with any failure reported by a `return` statement in `print_file`.
====

TIP: `print_file` uses preload only after `file_open` (not shown) has successfully opened the file. That's because, presumably, `file_open` itself has the file name and will have already passed it to `<<put,put>>`.

NOTE: The `ei_file_name` type, similarly to `ei_errno`, is a simple struct containing a string `value`, e.g.: +
`struct ei_file_name { std::string value; };`

If all functions called by `print_file` succeed, we call `put.<<cancel,cancel>>()` (to instruct its destructor to not forward the preloaded `ei_file_name` object to `<<put,put>>`), and return `ok`.

TIP: If failures are reported by throwing exceptions, it is not necessary to call `<<cancel,cancel>>`; to determine if a failure is being reported, LEAF calls `<<has_current_error,has_current_error>>`, which by default uses `std::uncaught_exception`.

Finally, let's consider the `main` function, which is able to handle errors reported by `print_file`:

====
[source,c++]
----
int main( int argc, char const * argv[ ] ) {
  char const * fn;
  if( error err=parse_command_line(argc,argv,fn) ) { <1>
    std::cout << "Bad command line argument" << std::endl;
    return 1;
  }

  leaf::expect<ei_file_name,ei_errno> info; <2>

  switch( error err=print_file(fn) ) {
    case ok:
      return 0;

    case file_open_error: <3>
      unwrap( info.match<ei_file_name,ei_errno>( [ ] ( std::string const & fn, int errn ) {
        if( errn==ENOENT )
          std::cerr << "File not found: " << fn << std::endl;
        else
          std::cerr << "Failed to open " << fn << ", errno=" << errn << std::endl;
      } ) );
      return 2;

    case file_size_error:
    case file_read_error:
    case file_eof_error: <4>
      unwrap(
        info.match<ei_file_name,ei_errno>( [ ] ( std::string const & fn, int errn ) {
          std::cerr << "Failed to access " << fn << ", errno=" << errn << std::endl;
        } ),
        info.match<ei_errno>( [ ] ( int errn ) {
          std::cerr << "I/O error, errno=" << errn << std::endl;
        } ),
        info.match<>( [ ] {
          std::cerr << "I/O error" << std::endl;
        } ) );
      return 3;

    default: <5>
      std::cerr <<
        "Unknown error code " << err << ", cryptic information follows." << std::endl <<
        leaf::diagnostic_information;
      return 4;
  }
}
----
<1> Parse the command line to obtain a file name.
<2> Tell LEAF that in case `print_file` reports an error, we expect to possibly have error info of type `ei_file_name` and/or `ei_errno` available.
<3> In case `print_file` reports a `file_open_error`, if both `ei_file_name` and `ei_errno` are available, the call to `<<match,match>>` will succeed, and then `<<unwrap,unwrap>>` will pass both the `ei_file_name::value` and `ei_errno::value` to the supplied lambda. But if either `ei_file_name` or `ei_errno` is not available, `unwrap` will throw `<<mismatch_error,mismatch_error>>`, having failed to find a suitable `<<match,match>>`. Presumably (since this program does not use exception handling), this indicates that receiving a `file_open_error` without both `ei_file_name` and `ei_errno` available is a logic error.
<4> Here we provide identical handling for any of `file_size_error`, `file_read_error` or `file_eof_error`, by first trying to `<<match,match>>` both `ei_file_name` and `ei_errno`; but if that fails, we're prepared to deal with an error condition where only `ei_errno` is available. If neither is available, the final `<<match,match>>` will print a generic error message, thus guaranteeing that this call to `unwrap` will never throw.
<5> Finally, the `default` case is designed to help diagnose logic errors where we got an error code which we forgot to handle. It prints the unrecognized error code, followed by `<<diagnostic_information,diagnostic_information>>`, which will print a complete, if not user-friendly, list of all available error info.
====

NOTE: The complete program from this tutorial is available https://github.com/zajo/leaf/blob/master/example/print_file_ec.cpp[here]. There is also https://github.com/zajo/leaf/blob/master/example/print_file_eh.cpp[another] version of the same program that uses exception handling to report errors.

[[reference]]
== Reference

[[available]]
=== `available`

====
.#include <boost/leaf/expect.hpp>
[source,c++]
----
namespace boost { namespace leaf {

  class available {

    available( available const & ) = delete;
    available & operator=( available const & ) = delete;

  public:

    available() noexcept;
    ~available() noexcept

    void set_to_propagate() noexcept;

    [[noreturn]] void rethrow_with_current_info();

    template <class... ErrorInfo,class F>
    <<unspecified_type>> match( F && f ) noexcept;

  };

} }
----
====

Class `available` is used to access any error info objects currently available in the calling thread (see `<<put,put>>`). Objects of class `available` are not copyable or moveable.

NOTE: Typically the functionality provided by class `available` is accessed through instancing the `<<expect,expect>>` class template, which derives from class `available`.

'''

[[available_ctor]]
==== Constructor

[source,c++]
----
namespace boost { namespace leaf {

  available::available() noexcept;

} }

----

Effects: :: Initializes an `available` instance so that when it is destroyed it will reset (clear) all error info objects that are currently available in the calling thread. This behavior can be disabled by a call to `<<set_to_propagate,set_to_propagate>>`.

'''

[[available_dtor]]
==== Destructor

[source,c++]
----
namespace boost { namespace leaf {

  available::~available() noexcept;

} }

----

Effects: :: Unless the user has called `<<set_to_propagate,set_to_propagate>>`, resets (clears) all error info objects that are currently available in the calling thread.

'''

[[available::set_to_propagate]]
==== `set_to_propagate`

[source,c++]
----
namespace boost { namespace leaf {

  void available::set_to_propagate() noexcept;

} }

----

Effects: :: By default, `<<available_dtor,~available>>` will reset (clear) all error info objects that are currently available in the calling thread (see `<<put,put>>`). Call `set_to_propagate` to disable this behavior.

'''

[[available::rethrow_with_current_info]]
==== `rethrow_with_current_info`

[source,c++]
----
namespace boost { namespace leaf {

  void available::rethrow_with_current_info();

} }

----

Effects: :: Equivalent to:
+
[source,c++]
----
set_to_propagate();
throw;
----

'''

[[available::match]]
==== `match`


[source,c++]
----
namespace boost { namespace leaf {

  template <class... ErrorInfo,class F>
  <<unspecified_type>> available::match( F && f ) noexcept;

} }

----

Returns: :: An object of unspecified type designed to be passed directly to `<<unwrap,unwrap>>`, which takes any number of such objects, and proceeds to inspect them in order, until it finds a match where error info objects are currently available in the calling thread (see `<<put,put>>`) for all specified `ErrorInfo...` types. If matched, `unwrap` invokes `f`, passing the `.value` of each available error info object.

Throws: :: If `unwrap` is unable to find a suitable match, it throwsfootnoteref:[onlythrow,This is the only LEAF function that throws.] `<<mismatch_error,mismatch_error>>`.

'''

[[expect]]
=== `expect`

====
.#include <boost/leaf/expect.hpp>
[source,c++]
----
namespace boost { namespace leaf {

  template <class... ErrorInfo>
  class expect: public available {

    expect( expect const & ) = delete;
    expect & operator=( expect const & ) = delete;

  public:

    expect() noexcept;
    ~expect() noexcept;

  };

} }
----
====

The `expect` class template is used to communicate to LEAF that error info objects of the specified `ErrorInfo...` types are expected in the current scope, to help handle failures.

`expect` objects are not copyable or movable. They form a hierarchy, such that error info types requested higher up the call chain remain "expected" in lower scopes, regardless of whether or not they're specified in lower level `expect` instances.

'''

[[expect_ctor]]
==== Constructor

[source,c++]
----
namespace boost { namespace leaf {

  expect<class ErrorInfo...>::expect() noexcept;

} }
----

Effects: ::

. Provides storage for objects of the specified `ErrorInfo...` types, enabling the `<<put,put>>` function template for use with these types within the current scope. When an error info object is passed to `put`, it is discarded unless the call originates in a scope where that specific error info type is expected.

. Resets (clears) all error info objects that are currently available. Note, the reset is _not_ limited to the specified `ErrorInfo...` types.

'''

[[expect_dtor]]
==== Destructor

[source,c++]
----
namespace boost { namespace leaf {

  expect<class ErrorInfo...>::~expect( noexcept;

} }
----

Effects: ::

. The storage provided by the `expect` constructor for error info objects is removed, except for error info types specified in other active `expect` instances up the call stack.

. If `<<has_current_error,has_current_error>>` is `true`, calls `<<set_to_propagate,set_to_propagate`>>.

'''

[[unwrap]]
=== `unwrap`

====
.#include <boost/leaf/expect.hpp>
[source,c++]
----
namespace boost { namespace leaf {

  struct mismatch_error: std::exception { };

  template <class... Match>
  void unwrap( Match && ... m );

} }
----
====

Effects: :: `unwrap` takes any number of objects returned by `<<match,match>>`, and proceeds to inspect them in order, until it finds a match where error info objects are currently available in the calling thread (see `<<put,put>>`) for all `ErrorInfo...` types used to instantiate the `<<match,match>>` function template. If found, `unwrap` invokes the function `f` (passed to `match`), passing the `.value` of each available error info object.

Throws: :: If no match is found, `unwrap` throwsfootnoteref:[onlythrow] `<<mismatch_error,mismatch_error>>`.

'''

[[put]]
=== `put`

====
.#include <boost/leaf/put.hpp>
[source,c++]
----
namespace boost { namespace leaf {

  template <class... ErrorInfo>
  void put( ErrorInfo && ... info ) noexcept;

} }
----
====

Effects: :: Moves each specified `info` object of type that is expected in the calling thread, into the storage provided by `<<expect,expect>>`. Use  `<<available,available>>` or `<<expect,expect>>` to access them.
+
All other `info` objects passed to `put` are discarded.

'''

[[throw_with_info]]
=== `throw_with_info`

====
.#include <boost/leaf/put.hpp>
[source,c++]
----
namespace boost { namespace leaf {

  template <class... ErrorInfo,class Exception>
  [[noreturn]] void throw_with_info( Exception const & e, ErrorInfo && ... info );

} }
----
====

Effects: :: As if:
+
[source,c++]
----
put(std::forward<ErrorInfo>(info)...);
throw e;
----

'''

[[preload]]
=== `preload`

====
.#include <boost/leaf/put.hpp>
[source,c++]
----
namespace boost { namespace leaf {

  template <class... ErrorInfo>
  <<unspecified_type>> preload( ErrorInfo && ... info );

} }
----
====

Returns: :: An object of unspecified moveable type which holds copies of all the passed `info` objects. Upon its destruction the stored copies are all forwarded by rvalue reference to `<<put,put>>`, except that:

- If `<<has_current_error,has_current_error>>` is `false`, or the user calls  `cancel` (a member function of the returned object), all preloaded error info objects are discarded.
- If any of the `info` objects passed to `preload` is a function, it is expected to return the actual error info object to be passed to `put`, and the function call to obtain it is deferred until the object returned by `preload` is destroyed (think `errno`, which obviously should not be captured at the time `preload` is called).

'''

[[has_current_error]]
=== `has_current_error`

====
.#include <boost/leaf/has_current_error.hpp>
[source,c++]
----
namespace boost { namespace leaf {

  bool has_current_error() noexcept;
  void set_has_current_error( bool (*f)() ) noexcept;

} }
----
====

LEAF uses `has_current_error` to determine if an error is currently being propagated up the call stack. By default, `has_current_error` returns `std::uncaught_exception()`. Use `set_has_current_error` to hook up a different implementation, if needed.

NOTE: `has_current_error` is an optimization, for example when using `<<preload,preload>>`, the call to `<<put,put>>` will be skipped unless `has_current_error` returns `true`. It is valid to pass to `set_has_current_error` a function which always returns `true`.

'''

[[diagnostic_information]]
=== `diagnostic_information`

====
.#include <boost/leaf/diagnostic_information.hpp>
[source,c++]
----
namespace boost { namespace leaf {

  <<unspecified_type>> diagnostic_information;
  std::ostream & operator<<( std::ostream &, <<unspecified_type>> const & );

} }
----
====

`diagnostic_information` is a dummy object, or token, which can be passed to `operator<<` (like `std::endl`) to output a developer-friendly (but not user-friendly) representation of all of the currently available error info objects.

Each error info object is output based on the following rules:

- If its type defines a suitable `operator<<` overload, it is used by the `operator<<` overload for `diagnostic_information` directly; otherwise
- If the type of its `value` data member defines a suitable `operator<<` overload, it will be used instead;
- Otherwise the error info type can not be output by the diagnostic information system. This is not illegal, using such error info types will not result in a compile error.

'''

[[current_exception_diagnostic_information]]
=== `current_exception_diagnostic_information`

====
.#include <boost/leaf/current_exception_diagnostic_information.hpp>
[source,c++]
----
namespace boost { namespace leaf {

  <<unspecified_type>> current_exception_diagnostic_information;
  std::ostream & operator<<( std::ostream &, <<unspecified_type>> const & );

} }
----
====

`current_exception_diagnostic_information` is a dummy object, or token, which can be passed to `operator<<` (like `std::endl`) to output into a `std::ostream` developer-friendly (but not user-friendly) information about the current uncaught exception, followed by outputing `<<diagnostic_information,diagnostic_information>>`.

Typical use for `current_exception_diagnostic_information` is:

[source,c++]
----
catch(...) {
  std::cerr << Unhandled exception! << std::endl <<
    leaf::current_exception_diagnostic_information;
}
----

'''

[[capture]]
=== `capture`

====
.#include <boost/leaf/capture.hpp>
[source,c++]
----
namespace boost { namespace leaf {

  class capture {

    capture( capture const & ) = delete;
    capture & operator=( capture const & ) = delete;
  
  public:

    explicit capture( bool do_capture=true ) noexcept;
    capture( capture && ) noexcept;
    release() noexcept;

  };

} }
----
====

Objects of class `capture` can be used to transport the currently available error info objects from one thread to another.

NOTE: If a thread communicates failures by throwing exceptions, do not use `capture` directly. Instead, use `leaf::<<get,get>>` to get the result of a `std::future`. In case that throws, all error info will be transported to the calling thread automatically.

'''

[[capture_ctors]]
==== Constructors

[source,c++]
----
namespace boost { namespace leaf {

  explicit capture::capture( bool do_capture=true ) noexcept;
  capture::capture( capture && ) noexcept;

} }
----

Effects: ::
- The first constructor moves all of the currently available (in the calling thread) exception info objects into a dynamically-allocated buffer stored in the `capture` object, but only if `do_capture` is `true`.
- The move constructor does not throw.

'''

[[capture::release]]
==== `release`

[source,c++]
----
namespace boost { namespace leaf {

  void capture::release() noexcept;

} }
----

Moves all exception info objects captured from the thread in which `this` was initialized, and makes them available in the calling thread.

'''

[[transport]]
=== `transport`

====
.#include <boost/leaf/transport.hpp>
[source,c++]
----
namespace boost { namespace leaf {

  template <class... ErrorInfo,class F>
  <<unspecified>> transport( F f )

} }
----
====

Returns: :: A function object which, when called:
. Performs the same operations as the constructor of  `<<expect,expect>><ErrorInfo...>`, then
. forwards all of its arguments to `f`, and returns the return value of `f`.

The returned function is designed to be used as a wrapper for `f` when it's passed to `std::async` or `std::packaged_task` and launched in a worker thread. 

Later the user is expected to call `<<get,get>>` instead of `std::future::get` directly; this way, in case `f` throws, all of the error info objects are automatically transported (together with the exception object) from the worker thread into the waiting thread.

[NOTE]
 There are two examples on transporting error info objects between threads: https://github.com/zajo/leaf/blob/master/example/transport_eh.cpp[transport_eh.cpp], which uses exception handling to communicate errors, and  https://github.com/zajo/leaf/blob/master/example/transport_ec.cpp[transport_ec.cpp], which does not.

'''

[[get]]
=== `get`

====
.#include <boost/leaf/transport.hpp>
[source,c++]
----
namespace boost { namespace leaf {

  template <class Future>
  decltype(std::declval<Future>().get()) get( Future && f );

} }
----
====

Effects: :: This function simply returns `std::forward<Future>(f).get()`, expecting that `f` is of type `std::future<>` or another similar type that defines a `get` member function, to obtain the result from a worker thread started using `<<transport,transport>>`. In case the worker thread throws, all error info objects from the worker thread are automatically made available in the calling thread.

TIP: There is no need to use `<<expect,expect>>` when calling `<<get,get>>`; in case a worker thread throws an exception, _all_ available error info objects are trasported and made available in the calling thread.

NOTE: Click https://github.com/zajo/leaf/blob/master/example/transport_eh.cpp[here] to see a complete example on transporting error info objects between threads.

'''

[[common]]
=== Common Error Information Types

====
.#include <boost/leaf/common.hpp>
[source,c++]
----
namespace boost { namespace leaf {

  struct ei_api_function { char const * value; };
  struct ei_file_name { std::string value; };

  struct ei_errno {
    int value;
    friend std::ostream & operator<<( std::ostream & os, ei_errno const & err );
  };

  ei_errno get_errno() noexcept {
    return ei_errno { errno };
  }

} }
----
====

This header defines some common error info objects which can be used directly:

- The `ei_api_function` type is designed to capture the name of the function for which a failure is reported. For example, if you're reporting an error detected by `fread`, you could use `leaf::ei_api_function { "fread" }`.
+
WARNING: The passed value is stored as a C string, so you should only pass string literals for `value`.
- When a file operation fails, you could use `ei_file_name` to capture the name of the file.
- `ei_errno` is suitable to capture `errno`.
+
TIP: If using `<<preload,preload>>`, pass `&get_errno` instead of an instance of `ei_errno`; this way `errno` will be captured after the error is detected, rather than at the time `preload` is called.
+
NOTE: `ei_errno` objects can be streamed to a `std::ostream`, which uses `strerror` to convert the `errno` code to a friendlier error message. This is designed for use with `<<diagnostic_information,diagnostic_information>>`.

[[techniques]]
== Programming Techniques

=== Capturing `errno` with `preload`

Typically, when calling `<<preload,preload>>` we pass the actual error info object(s) that we want forwarded to `<<put,put>>`. This copies them into the returned temporary object. Later, if we report an error from the same function, the destructor of the temporary object will forward all of its contents to `<<put,put>>` by rvalue reference.

But this behavior is incorrect for capturing `errno`. Consider:

[source,c++]
----
error read_file( FILE & f ) {
  auto put = leaf::preload( ei_errno { errno } ); //incorrect
  ....
  if( ferror(&f) )
    return my_error;
}
----

The problem is that `errno` must not be captured before it is set by a failed operation. The solution is to instead pass a function to `preload`:

[source,c++]
----
error read_file( FILE & f ) {
  auto put = leaf::preload( [ ] { return ei_errno { errno }; );
  ....
  if( ferror(&f) )
    return my_error;
}
----

When `preload` is passed a function, obtaining the error info object is deferred until the temporary object is being destroyed.

TIP: The header `boost/leaf/common.hpp` defines a function called `get_errno` which can be used for capturing `errno`, rather than using a lambda each time.

=== Transporting Error Information between Threads

The memory provided by `<<expect,expect>>` for `<<put,put>>` to store error info objects uses thread-local storage. This is ideal when errors are handled before the reporting thread ends, but sometimes error handling must happen in another thread, at the time a worker thread is joined.

The first problem is that in the spirit of LEAF, the context that handles errors is the one specifying what info it needs, by calling `<<expect,expect>>`, which in this case should be controlled by the main thread. This is achieved by instantiating the function template `<<transport,transport>>` instead of `expect`: it takes a function object (the worker thread function), and returns a function object that calls it after calling `expect` internally.

Secondly, to capture the current error info detached from the calling thread, create an object of class `<<capture,capture>>`. This moves all error info from thread-local to dynamically-allocated memory controlled by that object. The `noexcept` move constructor can now be used to move the error info to the main thread. Next, call `<<release,release>>` to once more move the captured error info to thread-local storage in the new thread.

This approach requires that a worker thread returns a variant type which can either hold the result in case of success, or an error code + a `capture` instance in case of error. This is illustrated with the https://github.com/zajo/leaf/blob/master/example/print_file_ec.cpp[print_file_ec.cpp] example.

A cleaner solution is possible if worker threads communicate errors by throwing exceptions. In this case you don't have to deal with the `capture` class directly: simply wrap the thread function in a call to `transport`, and later pass the `std::future` object to `leaf::<<get,get>>` to retrieve the result. This is illustrated with the https://github.com/zajo/leaf/blob/master/example/print_file_eh.cpp[print_file_eh.cpp] example.

== Design Rationale

The first observation driving the LEAF design is that unless a specific type of info (e.g. a file name) is used at the time an error is being handled, there is no need for it to be reported. On the other hand, if the error handling context can use or requires some info, it would not be burdened by having to explicitly declare that need. The end result of this reasoning is `<<expect,expect>>`/`<<put,put>>`.

The second observation is that ideally, like any other communication mechanism, it makes sense to formally define an interface for the error info that can be used by the error handling code. In terms of C++ exception handling, it would be nice to be able to say something like:

[source,c++]
----
try {

  process_file();

} catch( file_read_error<ei_file_name,ei_errno> & e ) {

  std::cerr <<
    "Could not read " << e.get<ei_file_name>() <<
    ", errno=" << e.get<ei_errno>() << std::endl;

} catch( file_read_error<ei_errno> & e ) {

  std::cerr <<
    "File read error, errno=" << e.get<ei_errno>() << std::endl;

} catch( file_read_error<> & e ) {

  std::cerr << "File read error!" << std::endl;

}
----

That is to say, it is desirable to be able to dispatch error handling based not only on the kind of failure being handled, but also based on the kind of error info available. Unfortunately this syntax is not possible and, even if it were, not all programs use exceptions to handle errors. The result of this train of thought is `<<match,match>>`/`<<unwrap,unwrap>>`.

Last but not least, there is certain redundancy and repetition in error-neutral contexts that simply forward errors to their caller. What is the point in receiving some error info from a lower level function (e.g. a file name), when at this point we can't do anything with it, except to forward it to our caller, until we reach a scope that can actually make use of the data? Even with move semantics, why bother move such data one level at a time, from one stack location to another immediately above, only to move it again when we `return` again?

It is more correct for such information to be passed from a context where it is available, _directly to the exact stack location where it would be accessed by the error handling code_. The result is that `<<expect,expect>>`/`<<put,put>>`/`<<match,match>>` use `thread_local` storage. +
 +
 +

[small overline right]#Copyright (c) Emil Dotchevski, 2018#
Description
Mirrored via gitea-mirror
Readme 19 MiB
Languages
C++ 96.9%
Meson 1.5%
Python 1.2%
Shell 0.2%
Batchfile 0.1%