2
0
mirror of https://github.com/boostorg/leaf.git synced 2026-02-21 03:02:14 +00:00
Files
leaf/doc/leaf.adoc
2019-01-01 23:01:26 -08:00

3284 lines
115 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
:last-update-label!:
:icons: font
:prewrap!:
:source-highlighter: coderay
:stylesheet: zajo.css
= LEAF
Low-latency Error Augmentation Framework for C++11
:toclevels: 3
:toc: left
:toc-title:
[abstract]
== Abstract
LEAF is a {CPP}11 error handling library. Features:
====
* Header-only, no dependencies.
* No dynamic memory allocations.footnote:[Except when error objects are transported between threads, see <<capture-expect,`capture`>>.]
* Associate objects of arbitrary types with any failure -- when it is initially reported or at a later time.
* Compatible with `std::error_code`, `errno` and any other error handling API.
* Use with or without exception handling.
* Support for multi-thread programming.
[.text-right]
https://github.com/zajo/leaf[GitHub] | <<introduction,Introduction>> | <<tutorial>> | <<techniques>> | <<synopsis>> | <<reference>> | <<rationale>>
====
NOTE: If you are at least an intermediate level developer and have seen `expected`/`outcome`-like interfaces before, and you are also wondering how the heck you can return a type-erased error object without using heap allocations, you may want to read the <<rationale>> first.
[[introduction]]
== Two Minute Introduction
Let's suppose that to handle errors from a given function `f` we need an error code and a file name. In addition -- depending on the error -- we may need a request ID, also of type string. In terms of LEAF, we can write:
[source,c++]
----
{
leaf::expect<error_code, e_file_name, e_request_id> exp; //<1>
if( leaf::result<T> r = f() )
{
//Success! //<2>
}
else
{
bool matched = leaf::handle_error( exp, r, //<3>
[ ]( error_code ec, e_file_name const & fn, e_request_id const & id )
{
//<4>
},
[ ]( error_code ec, e_file_name const & fn )
{
//<5>
}
);
}
}
----
[.text-right]
`<<expect,expect>>` | `<<result,result>>` | <<handle_error-result>>
<1> The `exp` object provides storage for an `error_code`, an `e_file_name` and an `e_request_id`, which `f()` (next line) may communicate in case it fails.
<2> Successful result values are stored inside of `r` of type `result<T>`, which is a value-or-error variant type.
<3> The call to `f()` has failed; `handle_error` takes `exp`, `r`, and any number of error handling functions to be matched, in order, against the objects currently stored in `exp`, associated with `r`.
<4> Handle the case where we received `error_code`, `e_file_name` and `e_request_id` (associated with the failure reported in `r`).
<5> Handle the case where we only received `error_code` and `e_file_name` (associated with the failure reported in `r`).
The `error_code` can be a simple `enum`:
[source,c++]
----
enum error_code
{
ec1=1,
ec2,
....
};
----
Because both the file name and the request ID are strings, we will wrap each of them in a simple `struct`, so that the type system can tell them apart:
[source,c++]
----
struct e_file_name { std::string value; };
struct e_request_id { std::string value; };
----
Reporting an error with LEAF looks like this:
[source,c++]
----
leaf::result<T> g() noexcept
{
if( success )
return T(....); <1>
else
return leaf::error( ec1, e_request_id{id} ); //<2>
}
----
[.text-right]
`<<result,result>>` | `<<error::error,error::error>>`
<1> Initializing the returned `result<T>` with a `T` object indicates a success.
<2> Report an error, store the passed `error_code` and `e_request_id` in appropriate `expect` object(s) from calling scopes. Presumably, at this point we don't have access to a relevant file name, but that's fine -- we associate with the reported error what relevant information we do have: the error code and the request ID.
Forwarding errors reported by a lower level function looks like this:
[source,c++]
----
leaf::result<T> f( char const * file_name ) noexcept
{
if( leaf::result<T> r=g() )
{
....
return r; //<1>
}
else
return r.error( e_file_name{file_name} ); //<2>
}
----
[.text-right]
`<<result,result>>` | `<<result::error,result::error>>`
<1> Success, return `r`.
<2> Forward the error reported (by `g`) in `r`, storing `e_file_name` in appropriate `expect` object(s) from calling scopes. Note that `g` has no access to the `file_name` passed to `f` and therefore is unable to include it in the reported error; yet the `file_name` is very much relevant to that error. At this point the file name is associated with the error in addition to the relevant `error_code` and `e_request_id` which, presumably, `g` has already communicated.
== What If I Want to Use Exception Handling?
That would be a 1-minute introduction. :-)
Let's assume that to handle exceptions from a given function `f` we need a file name. In addition -- depending on the error -- we may need a request ID, also of type string. In terms of LEAF, we can write:
[source,c++]
----
{
leaf::expect<e_file_name, e_request_id> exp; //<1>
try
{
f();
}
catch( my_error const & e )
{
leaf::handle_exception( exp, e, //<2>
[ ]( e_file_name const & fn, e_request_id const & id )
{
//<3>
},
[ ]( e_file_name const & fn )
{
//<4>
}
);
}
}
----
[.text-right]
`<<expect,expect>>` | <<handle_exception>>
<1> The `exp` object provides storage for an `e_file_name` and an `e_request_id`, which `f()` (next line) may communicate in case it throws.
<2> We caught a `my_error` exception; `handle_error` takes `exp`, `e`, and any number of error handling functions to be matched, in order, against the error objects currently stored in `exp`, associated with `e`.
<3> Handle the case where we received `e_file_name` and `e_request_id` (associated with the failure reported by `e`).
<4> Handle the case where we only received `e_file_name` (associated with the failure reported by `e`).
Because both the file name and the request ID are strings, we will wrap each of them in a simple `struct`, so that the type system can tell them apart:
[source,c++]
----
struct e_file_name { std::string value; };
struct e_request_id { std::string value; };
----
Such error objects can be passed to LEAF at the point of the `throw`:
[source,c++]
----
T g()
{
if( success )
return T(....); <1>
else
throw leaf::exception( my_error(), e_request_id{id} ); //<2>
}
----
[.text-right]
`<<exception,exception>>`
<1> Success, just return `T`.
<2> Throw a `my_error` object, store the passed `e_request_id` in appropriate `expect` object(s) from calling scopes. Presumably, at this point we don't have access to a relevant file name, but that's fine -- we associate with the exception object what relevant information we do have: the request ID.
Exceptions can be augmented in exception-neutral contexts:
[source,c++]
----
void f( char const * file_name )
{
auto propagate = leaf::preload( e_file_name{file_name} ); //<1>
g();
}
----
[.text-right]
<<preload>>
<1> In case `g()` (next line) throws, the `file_name` will be associated with the exception object, in addition to the relevant `e_request_id` which, presumably, `g` has already communicated. Note that `g` has no access to the `file_name` passed to `f` and therefore is unable to include it in the reported error; yet the `file_name` is very much relevant to that error.
TIP: When used with exception handling, LEAF can be viewed as <<boost_exception,a better Boost Exception>>.
[[tutorial]]
== Tutorial
We'll write a short but complete program, using LEAF to handle errors. We'll implement two versions, one that uses exception handling, and one that does not. To see the source code of the complete programs from this tutorial, follow these links:
* https://github.com/zajo/leaf/blob/master/example/print_file_result.cpp?ts=3[print_file_result.cpp] (without exception handling)
* https://github.com/zajo/leaf/blob/master/example/print_file_eh.cpp?ts=3[print_file_eh.cpp] (with exception handling)
First, let's see how to use LEAF without exception handling.
[[tutorial-noexcept]]
=== Using `result<T>`
We'll write a program that reads a text file in a buffer and prints it to `std::cout`, using LEAF to handle errors. First, we need an `enum` to define our error codes:
[source,c++]
----
enum error_code
{
input_file_open_error=1,
input_file_size_error,
input_file_read_error,
input_eof_error,
cout_error
};
----
We don't need an enumerated value that indicates success. That's because we will use the handy class template `<<result,result>><T>` as the return type in functions which may fail. It is a value-or-error variant type which holds a `T` except if initialized with a `leaf::<<error,error>>`.
To enable `leaf::error` to work with our `error_code` `enum`, we need to specialize the <<is_error_type>> template:
[source,c++]
----
namespace boost { namespace leaf {
template<> struct is_error_type<error_code>: std::true_type { };
} }
----
[TIP]
--
The `is_error_type` template needs not be specialized for:
* types that define an accessible data member `value`,
* `std::error_code`,
* `boost::system::error_code`.
--
Here is a function that reads data from a `FILE` into a buffer and reports the various errors which may occur (it returns `result<void>` because in case of success it doesn't return a value):
[source,c++]
----
leaf::result<void> file_read( FILE & f, void * buf, int size )
{
int n = fread( buf, 1, size, &f );
if( ferror(&f) )
return leaf::error( input_file_read_error, e_errno{errno} ); //<1>
if( n!=size )
return leaf::error( input_eof_error ); //<2>
return { }; //<3>
}
----
[.text-right]
`<<result,result>>` | `<<error::error,error::error>>`
<1> If `ferror` indicates an error, we return `input_file_read_error` and, because there is a relevant `errno` code, we _also_ pass that to the `leaf::<<error,error>>` constructor (LEAF defines `struct e_errno { int value; }`).
<2> If `fread` reports that it couldn't read all of the data requested, we return `input_eof_error`. In this case there is no relevant `errno` to pass on, because this is not an error as far as `fread` is concerned.
<3> `result<void>` can be initialized with `{ }` to indicate success.
NOTE: `leaf::error` may be passed objects of arbitrary movable types, however objects of types that are not specific to error handling -- such as `errno` values, which are of type `int` -- should be wrapped in a `struct`. By convention, such structs use the `e_` prefix and contain a single data member called `value`, e.g. `struct e_errno { int value; }`. +
+
For example, we could define `struct e_input_name { std::string value; }` and `struct e_output_name { std::string value; }` and LEAF will treat them as separate entities even though their `.value` members are of the same type `std::string`.
Now, let's consider a possible caller of `file_read`, called `print_file`:
[source,c++]
----
leaf::result<void> print_file( char const * file_name )
{
leaf::result<std::shared_ptr<FILE>> f = file_open(file_name);
if( !f ) //<1>
return f.error(); //<2>
auto propagate = leaf::preload( e_file_name{file_name} ); //<3>
leaf::result<int> s = file_size(*f.value());
if( !s ) //<4>
return s.error(); //<5>
std::string buffer( 1+s.value(), '\0' );
leaf::result<void> fr = file_read( *f.value, &buffer[0], buffer.size()-1 );
if( !fr )
return fr.error();
std::cout << buffer;
std::cout.flush();
if( std::cout.fail() )
return leaf::error( cout_error ); //<6>
return { }; //<7>
}
----
[.text-right]
`<<result,result>>` | `<<result::error,result::error>>` | <<preload>>
<1> If `file_open` returns an error...
<2> ...we forward it to the caller. Notice that we don't return `leaf::error()`, which would indicate a newly detected error; we return `f.error()`, which propagates the error already stored in `f`.
<3> `<<preload,preload>>` takes any number of error objects and prepares them to become associated (automatically, at the time the returned object expires) with a `leaf::<<error,error>>` value created in the future. The effect is that from this point on, any error returned or forwarded by `print_file` will have an associated file name, in addition to everything else passed to `leaf::<<error,error>>` explicitly (`e_file_name` is defined as `struct e_file_name { std::string value; }`).
<4> If `file_size` returns an error...
<5> ...we forward it to the caller.
<6> If `std::cout` fails to write the buffer, we return `cout_error`.
<7> Success!
Notice the repetitiveness in simply forwarding errors to the caller. LEAF defines two macros, `<<LEAF_AUTO,LEAF_AUTO>>` and `<<LEAF_CHECK,LEAF_CHECK>>`, which can help reduce the clutter:
* The `LEAF_AUTO` macro takes two arguments, an identifier and a `result<T>`. In case the passed `result<T>` indicates an error, `LEAF_AUTO` returns that error to the caller (therefore control leaves the enclosing function). In case of success, `LEAF_AUTO` defines a variable, of type `T &` (using the provided identifier) that refers to the `T` object stored inside the passed `result<T>`.
* The `LEAF_CHECK` macro is designed to be used similarly in functions that return `result<void>`, but of course it doesn't define a variable.
Below is the same `print_file` function simplified using `LEAF_AUTO` and `LEAF_CHECK` (remember that the variables defined by `LEAF_AUTO` are not of type `result<T>`, but of type `T &`; for example `s` used to be of type `result<int>`, but now its type is simply `int &`):
[source,c++]
----
leaf::result<void> print_file( char const * file_name )
{
LEAF_AUTO( f, file_open(file_name) ); //<1>
auto propagate = leaf::preload( e_file_name{file_name} );
LEAF_AUTO( s, file_size(*f) ); //<2>
std::string buffer( 1+s, '\0' );
LEAF_CHECK( file_read(*f,&buffer[0],buffer.size()-1) ); //<3>
std::cout << buffer;
std::cout.flush();
if( std::cout.fail() )
return leaf::error( cout_error );
return { };
}
----
[.text-right]
<<LEAF_AUTO>> | <<LEAF_CHECK>> | <<preload>> | `<<error::error,error::error>>`
<1> Call `file_open`, check for errors, unpack the returned `result<std::shared_ptr<FILE>>` and define a variable `f` of type `std::shared_ptr<FILE> &` that refers to its `<<result::value,value>>()`.
<2> Call `file_size`, check for errors, unpack the returned `result<int>` and define a variable `s` of type `int &` that refers to its `value()`.
<3> Call `file_read`, check for errors (`file_read` returns `result<void>`).
Finally, let's look at the `main` function, which handles all errors in this program:
[source,c++]
----
int main( int argc, char const * argv[ ] )
{
char const * fn = parse_command_line(argc,argv);
if( !fn )
{
std::cout << "Bad command line argument" << std::endl;
return 1;
}
//We expect error_code, e_file_name and e_errno objects to be associated
//with errors handled in this function. They will be stored inside of exp.
leaf::expect<error_code, e_file_name, e_errno> exp;
if( auto r = print_file(fn) )
{
return 0; //Success, we're done!
}
else
{
//Probe exp for the error_code object associated with the error stored in r.
switch( auto ec = *leaf::peek<error_code>(exp,r) )
{
case input_file_open_error:
{
//handle_error takes a list of functions (in this case only one). It attempts to
//match each function (in order) to objects currently available in exp, which
//are associated with the error value stored in r. If no function can be matched,
//handle_error returns false. Otherwise the matched function is invoked with
//the corresponding available error objects.
bool matched = leaf::handle_error( exp, r,
[ ] ( e_file_name const & fn, e_errno const & errn )
{
if( errn.value==ENOENT )
std::cerr << "File not found: " << fn.value << std::endl;
else
std::cerr << "Failed to open " << fn.value << ", errno=" << errn << std::endl;
}
);
assert(matched);
return 2;
}
case input_file_size_error:
case input_file_read_error:
case input_eof_error:
{
//In this case handle_error is given 3 functions. It will first check if both
//e_file_name and e_errno, associated with r, are avialable in exp; if not, it will
//next check if just e_errno is available; and if not, the last function (which
//takes no arguments) will always match to print a generic error message.
bool matched = leaf::handle_error( exp, r,
[ ] ( e_file_name const & fn, e_errno const & errn )
{
std::cerr << "Failed to access " << fn.value << ", errno=" << errn << std::endl;
},
[ ] ( e_errno const & errn )
{
std::cerr << "I/O error, errno=" << errn << std::endl;
},
[ ]
{
std::cerr << "I/O error" << std::endl;
}
);
assert(matched);
return 3;
}
case cout_error:
{
//Report failure to write to std::cout, print the relevant errno.
bool matched = leaf::handle_error( exp, r,
[ ] ( e_errno const & errn )
{
std::cerr << "Output error, errno=" << errn << std::endl;
}
);
assert(matched);
return 4;
}
//This catch-all case helps diagnose logic errors (presumably, missing case labels
//in the switch statement).
default:
{
std::cerr << "Unknown error code " << ec << ", cryptic information follows." << std::endl; //<7>
leaf::diagnostic_output(std::cerr,exp,r);
return 5;
}
}
}
}
----
[.text-right]
`<<expect,expect>>` | <<peek-result>> | <<handle_error-result>> | <<diagnostic_output-result>>
To summarize, when using LEAF without exception handling:
* Functions that may fail return instances of `<<result,result>><T>`, a value-or-error variant class template.
* In case a function detects a failure, the returned `result<T>` can be initialized implicitly by returning `leaf::<<error,error>>`, which may be passed any and all information we have that is relevant to the failure, in the form of objects of arbitrary movable types.
* When a lower level function reports an error, that error is forwarded to the caller, passing any additional relevant information available in the current scope.
* In order for any object passed to `leaf::<<error,error>>` to be stored rather than discarded, the function that handles the error must contain an instance of the class template `<<expect,expect>>` that provides the necessary storage for its type.
* Using `<<handle_error-expect,handle_error>>`, available objects associated with the `<<error,error>>` value being handled can be matched to what is required in order to deal with that `error`.
NOTE: The complete program from this tutorial is available https://github.com/zajo/leaf/blob/master/example/print_file_result.cpp?ts=3[here]. There is also https://github.com/zajo/leaf/blob/master/example/print_file_eh.cpp?ts=3[another] version of the same program that uses exception handling to report errors (see <<tutorial-eh,tutorial below>>).
'''
[[tutorial-eh]]
=== Using Exception Handling
And now, we'll write the same program that reads a text file in a buffer and prints it to `std::cout`, this time using exceptions to report errors.
TIP: When used with exception handling, LEAF can be viewed as <<boost_exception,a better Boost Exception>>.
First, we need to define our exception class hierarchy:
[source,c++]
----
struct print_file_error : virtual std::exception { };
struct command_line_error : virtual print_file_error { };
struct bad_command_line : virtual command_line_error { };
struct input_error : virtual print_file_error { };
struct input_file_error : virtual input_error { };
struct input_file_open_error : virtual input_file_error { };
struct input_file_size_error : virtual input_file_error { };
struct input_file_read_error : virtual input_file_error { };
struct input_eof_error : virtual input_file_error { };
----
NOTE: To avoid ambiguities in the dynamic type conversion which occurs when catching a base type, it is generally recommended to use virtual inheritance in exception type hierarchies.
Here is a function that reads data from a file into a buffer and throws exceptions to communicate failures:
[source,c++]
----
void file_read( FILE & f, void * buf, int size )
{
int n = fread( buf, 1, size, &f );
if( ferror(&f) )
throw leaf::exception( input_file_read_error(), e_errno{errno} ); //<1>
if( n!=size )
throw input_eof_error(); //<2>
}
----
[.text-right]
`<<exception,exception>>`
<1> If `ferror` indicates an error, we throw `input_file_read_error` and, because there is a relevant `errno` code, we pass that to the `<<exception,exception>>` function template _also_ (LEAF defines `struct e_errno { int value; }`).
<2> If `fread` reports that it couldn't read all of the data requested, we throw `input_eof_error`. In this case there is no relevant `errno` to pass on, because this is not an error as far as `fread` is concerned.
NOTE: `leaf::error` may be passed objects of arbitrary movable types, however objects of types that are not specific to error handling -- such as `errno` values, which are of type `int` -- should be wrapped in a `struct`. By convention, such structs use the `e_` prefix and contain a single data member called `value`, e.g. `struct e_errno { int value; }`. +
+
Similarly, we could define `struct e_input_name { std::string value; }` and `struct e_output_name { std::string value; }` and LEAF will treat them as separate entities even though their `.value` members are of the same type `std::string`.
Now, let's consider a possible caller of `file_read`, called `print_file`:
[source,c++]
----
void print_file( char const * file_name )
{
std::shared_ptr<FILE> f = file_open( file_name ); //<1>
auto propagate1 = leaf::preload( e_file_name{file_name} ); //<2>
std::string buffer( 1+file_size(*f), '\0' ); //<3>
file_read( *f, &buffer[0], buffer.size()-1 );
auto propagate2 = leaf::defer( [ ] { return e_errno{errno}; } ); //<4>
std::cout << buffer;
std::cout.flush();
}
----
[.text-right]
<<preload>> | <<defer>>
<1> `std::shared_ptr<FILE> file_open( char const * file_name)` throws on error.
<2> `<<preload,preload>>` takes any number of error objects and prepares them to become associated (automatically, at the time the returned object expires) with an exception thrown in the future. The effect is that from this point on, a file name will be associated with any exception escaping `print_file`, in addition to everything else passed earlier to the `leaf::<<exception,exception>>` function template explicitly (`e_file_name` is defined as `struct e_file_name { std::string value; }`).
<3> `int file_size( FILE & f )` throws on error.
<4> `<<defer,defer>>` is similar to `preload`: it prepares an error object to become associated with an exception thrown in the future, but instead of taking the object itself, `defer` takes a function that returns it. The function is invoked in the returned object's destructor, at which point it becomes associated with the exception being propagated. Assuming `std::cout` is configured to throw on error, the effect of this line is that those exceptions will have the relevant `errno` associated with them.
Finally, let's consider the `main` function, which is able to handle exceptions thrown by `print_file`:
[source,c++]
----
int main( int argc, char const * argv[ ] )
{
//Configure std::cout to throw on error.
std::cout.exceptions ( std::ostream::failbit | std::ostream::badbit );
//We expect e_file_name and e_errno objects to be associated with errors
//handled in this function. They will be stored inside of exp.
leaf::expect<e_file_name, e_errno> exp;
try
{
print_file(parse_command_line(argc,argv));
return 0;
}
catch( bad_command_line const & )
{
std::cout << "Bad command line argument" << std::endl;
return 1;
}
catch( input_file_open_error const & e )
{
//handle_exception takes a list of functions (in this case only one). It attempts to
//match each function (in order) to objects currently available in exp, which
//are associated with the error value stored in e. If no function can be matched,
//handle_exception returns false. Otherwise the matched function is invoked with
//the corresponding available error objects.
leaf::handle_exception( exp, e,
[ ] ( e_file_name const & fn, e_errno const & errn )
{
if( errn.value==ENOENT )
std::cerr << "File not found: " << fn.value << std::endl;
else
std::cerr << "Failed to open " << fn.value << ", errno=" << errn << std::endl;
}
);
return 2;
}
catch( input_error const & e )
{
//In this case handle_exception is given 3 functions. It will first check if both
//e_file_name and e_errno, associated with e, are avialable in exp; if not, it will
//next check if just e_errno is available; and if not, the last function (which
//takes no arguments) will always match to print a generic error message.
leaf::handle_exception( exp, e,
[ ] ( e_file_name const & fn, e_errno const & errn )
{
std::cerr << "Input error, " << fn.value << ", errno=" << errn << std::endl;
},
[ ] ( e_errno const & errn )
{
std::cerr << "Input error, errno=" << errn << std::endl;
},
[ ]
{
std::cerr << "Input error" << std::endl;
}
);
return 3;
}
catch( std::ostream::failure const & e )
{
//Report failure to write to std::cout, print the relevant errno.
leaf::handle_exception( exp, e,
[ ] ( e_errno const & errn )
{
std::cerr << "Output error, errno=" << errn << std::endl;
}
);
return 4;
}
catch(...)
{
//This catch-all case helps diagnose logic errors (presumably, missing catch).
std::cerr << "Unknown error, cryptic information follows." << std::endl;
leaf::diagnostic_output_current_exception(std::cerr,exp);
return 5;
}
}
----
[.text-right]
`<<expect,expect>>` | <<handle_exception>> | <<diagnostic_output_current_exception>>
To summarize, when using LEAF with exception handling:
* In case a function detects a failure, it may throw instances of the `leaf::<<exception,exception>>` class template, initializing it with any number of error objects, to associate with the exception any information it has that is relevant to the failure.
* Alternatively it may use `<<preload,preload>>` to associate error objects with an exception object (of any type) thrown later on, including exceptions thrown by third-party code.
* In order for any error object passed to the `leaf::<<exception,exception>>` function template to be stored rather than discarded, the function that catches the exception must contain an instance of the class template `<<expect,expect>>` that provides the necessary storage for its type.
* Using `<<handle_exception,handle_exception>>`, available error objects associated with the exception being handled can be matched to what is required in order to deal with that exception.
NOTE: The complete program from this tutorial is available https://github.com/zajo/leaf/blob/master/example/print_file_eh.cpp?ts=3[here]. There is also https://github.com/zajo/leaf/blob/master/example/print_file_result.cpp?ts=3[another] version of the same program that does not use exception handling to report errors (see <<tutorial-noexcept,previous tutorial>>).
[[synopsis]]
== Synopsis
=== `expect.hpp`
include::synopses/expect.adoc[]
'''
=== `error.hpp`
include::synopses/error.adoc[]
'''
=== `preload.hpp`
include::synopses/preload.adoc[]
'''
=== `error_capture.hpp`
include::synopses/error_capture.adoc[]
'''
=== `result.hpp`
include::synopses/result.adoc[]
'''
=== `common.hpp`
include::synopses/common.adoc[]
'''
=== `exception.hpp`
include::synopses/exception.adoc[]
'''
=== `diagnostic_output_current_exception.hpp`
include::synopses/diagnostic_output_current_exception.adoc[]
[.text-right]
`<<diagnostic_output_current_exception,diagnostic_output_current_exception>>`
=== `exception_capture.hpp`
include::synopses/exception_capture.adoc[]
[[reference]]
== Reference
[[wrapping]]
=== Wrapping of Error Types
With LEAF, users can efficiently associate with errors or with exceptions any number of values that pertain to a failure. These values may be of any no-throw movable type for which the <<is_error_type>> template is properly specialized.
However, when transporting objects of general types that are not specific to error handling, each value should be enclosed in a C-`struct` that acts as its compile-time identifier and gives it semantic meaning. Examples:
[source,c++]
----
struct e_input_name { std::string value; };
struct e_output_name { std::string value; };
struct e_minimum_temperature { int value; };
struct e_maximum_temperature { int value; };
----
Various LEAF functions take a list of error objects to associate with an `<<error,error>>` value. For example, to indicate an error, a function that returns a `<<result,result>><T>` may use something like:
[source,c++]
----
return leaf::error( my_error_code, e_input_name{n1}, e_output_name{n2} );
----
Similar types that define an accessible data member `value` can be used with LEAF without having to specialize the `is_error_type` template. By convention such types use the `e_` prefix.
'''
=== Diagnostic Information
LEAF will attempt to print error objects in various `diagnostic_output` overloads it defines. It will first attempt to use `operator<<` overload that takes the actual error object type (e.g. the enclosing `struct`, see <<wrapping>>). If such overload does not exist, the fallback is to attempt to use `operator<<` overload that takes the `.value` (in case the error object type defines it). If that also doesn't work, LEAF is unable to print values of that particular error type (this is permissible, not an error).
Even with types that define a printable `.value`, the user may still want to overload `operator<<` for the enclosing `struct`, e.g.:
[source,c++]
----
struct e_errno
{
int value;
friend std::ostream & operator<<( std::ostream & os, e_errno const & e )
{
return os << "errno = " << e.value << ", \"" << strerror(e.value) << '"';
}
};
----
The `e_errno` type above is designed to hold `errno` values. The defined `operator<<` overload will automatically include the output from `strerror` when `e_errno` values are printed by `diagnostic_output` overloads (LEAF defines `e_errno` in `<boost/leaf/common.hpp>`, together with other commonly-used error types).
TIP: The output from `diagnostic_output` overloads is developer-friendly but not user-friendly. Therefore, `operator<<` overloads for error types should only print technical information in English, and should not attempt to localize strings or to format a message. Formatting a localized user-friendly message should be done at the time individual errors are handled.
'''
[[expect]]
=== Class Template `expect`
include::synopses/expect.adoc[]
All `expect<E...>` objects must use automatic storage duration. They are not copyable and are not movable.
An `expect<E...>` object contains exactly `sizeof...(E)` _slots_, each slot providing storage for a single object of the corresponding type `E`. It is invalid to specify the same type more than once in `E...`; so, each type `E` uniquely identifies an `expect` slot. All slots are initially empty.
Slots of the same type `E` across different `expect` objects (that belong to the calling thread) form a stack. The slot created last for a given type `E` is at the top of that stack. When an object is passed to the `leaf::<<error,error>>` constructor, it is moved into the corresponding slot on the top of that stack, and is associated with that `leaf::error` value. If no `expect` objects contain a corresponding slot, the object passed to the `leaf::error` constructor is discarded (however, see <<e_unexpected>>).
An object stored in an `expect` slot can be accessed in several different ways, all requiring the `leaf::error` value it was associated with. While an `expect` object can not store multiple values of the same type, this association guarantees that the object being accessed pertains to that specific `error` value (`error` values act as program-wide unique failure identifiers).
When an `expect<E...>` object is destroyed, each stored object is moved to the corresponding slot one level below the top of the stack formed by the slots of the same type across different `expect` objects. If that stack is empty, the object is discarded.
'''
[[expect::expect]]
==== Constructor
.#include <boost/leaf/expect.hpp>
[source,c++]
----
namespace boost { namespace leaf {
expect::expect() noexcept;
} }
----
Description: :: Initializes an empty `expect` instance.
Postcondition: :: `<<peek-expect,peek>><P>(*this,e)` returns a null pointer for any `P` and any `<<error,error>>` value `e`.
'''
[[expect-dtor]]
==== Destructor
.#include <boost/leaf/expect.hpp>
[source,c++]
----
namespace boost { namespace leaf {
expect::~expect() noexcept;
} }
----
Effects: :: Each stored object is moved to a corresponding slot in other existing `expect` instances according to the rules described `<<expect,here>>`.
'''
[[capture-expect]]
==== `capture`
.#include <boost/leaf/expect.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class... E>
error_capture capture( expect<E...> & exp, error const & e );
} }
----
Effects: :: Moves all objects currently stored in `exp` and associated with the `leaf::<<error,error>>` value `e`, into the returned `<<error_capture,error_capture>>` object. The contents of the `error_capture` object are immutable and allocated on the heap.
NOTE: `error_capture` objects are useful for transporting error objects across thread boundaries.
'''
[[handle_error-expect]]
==== `handle_error`
.#include <boost/leaf/expect.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class R>
struct uhnandled_error
{
static constexpr R value( error const & e ) noexcept;
};
template <class... E, class... F>
<<deduced_return_type>> handle_error( expect<E...> const & exp, error const & e, F && ... f ) noexcept;
} }
----
Requirements: ::
* `sizeof...(F) > 0`;
* Each `f...` must be callable;
* All `f...` must return the same static type.
Return type: :: The return type `R` of `handle_error` is deduced as the return type of the `f...` functions, except if their return type is `void`, in which case `R` is deduced as `bool`, and the behavior (described below) is as if the `f...` functions return `true`.
Effects: :: Attempts to match, by type, the objects currently stored in `exp`, associated with the `<<error,error>>` value `e`, with the arguments of each of the `f...` function objects, in order.
+
If a complete match is found among `f...`, the matched function is called with the entire set of corresponding objects from `exp` (the function may not modify those values), and the returned value is forwarded to the caller of `handle_error`.
+
Otherwise, `handle_error` returns `unhandled_error<R>::value(e)`, where `R` is the deduced return type of `handle_error`. The main `unhadled_error` template is defined such that:
+
--
* if `R` is `bool`, `unhandled_error<R>::value(e)` returns `false`;
* else, if `R` is an instance of the `<<result,result>>` template, `unhandled_error<R>::value(e)` returns `e` (note, `result<T>` can be implicitly initialized by the `<<error,error>>` object `e`);
* else, if `std::is_integral<R>::value`, the call to `unhandled_error<R>::value(e)` returns `static_cast<R>(-1)`;
* else, the call to `unhandled_error<R>::value(e)` returns `R()`.
--
+
NOTE: The `unhandled_error` template may be specialized for user-defined types as needed.
Example: ::
+
[source,c++]
----
bool matched = handle_error( exp, e,
[ ] ( e_file_name const & fn, e_errno const & errn )
{
std::cerr << "Failed to access " << fn.value << ", errno=" << errn << std::endl;
},
[ ] ( e_errno const & errn )
{
std::cerr << "I/O error, errno=" << errn << std::endl;
}
);
----
+
Assuming `struct e_file_name { std::string value; }` and `struct e_errno { int value; }`, the call to `handle_error` above will: +
+
* Check if the `expect` object `exp` contains `e_file_name` and `e_errno` objects, associated with the `leaf::<<error,error>>` value `e`. If it does, it will pass them to the first lambda function, then return `true`;
* Otherwise if it contains just `e_errno`, it will pass it to the second lambda function, then return `true`;
* Otherwise, `handle_error` returns `false`.
'''
[[peek-expect]]
==== `peek`
.#include <boost/leaf/expect.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class P, class... E>
P const * peek( expect<E...> const & exp, error const & e ) noexcept;
} }
----
Returns: :: If `exp` currently stores an object of type `P` associated with the `<<error,error>>` value `e`, returns a read-only pointer to that object. Otherwise returns a null pointer.
'''
[[diagnostic_output-expect]]
==== `diagnostic_output`
.#include <boost/leaf/expect.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class... E>
void diagnostic_output( std::ostream & os, expect<E...> const & exp );
template <class... E>
void diagnostic_output( std::ostream & os, expect<E...> const & exp, error const & e );
} }
----
Effects: :: Prints diagnostic information about the objects stored in `exp`. The second overload will only print diagnostic information about objects stored in `exp` which are associated with the `leaf::<<error,error>>` value `e`.
NOTE: The printing of each individual object is done by the rules described <<wrapping,here>>.
'''
[[error]]
=== Class `error`
include::synopses/error.adoc[]
Objects of class `error` are values that identify an error across the entire program. They can be copied, moved, assigned to, and compared to other error objects. They occupy as much memory, and are as efficient as `unsigned int`.
When an `e...` sequence is passed to the `error` constructor or to <<error::propagate>>, these objects are moved into matching storage provided by `<<expect,expect>>` instances, where it is associated with the `error` object (which can later be passed to `<<peek-expect,peek>>` or `<<handle_error-expect,handle_error>>` to retrieve them).
'''
[[error::error]]
==== Constructor
.#include <boost/leaf/error.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class... E>
error::error( E && ... e ) noexcept;
} }
----
Requirements: :: `<<is_error_type,is_error_type>><E>::value` must be `true` for each `E`.
Effects: :: Each of the `e...` objects is either moved into the corresponding storage provided by `expect` instances (where it is associated with `*this`), or discarded. See `<<expect,expect>>`.
Postconditions: :: `*this` is a unique value across the entire program. The user may create any number of other `error` values that compare equal to `*this`, by copy, move or assignment, just like with any other value type.
'''
[[error::propagate]]
==== `propagate`
.#include <boost/leaf/error.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class... E>
error error::propagate( E && ... e ) const noexcept;
} }
----
Effects: :: Each of the `e...` objects is either moved into the corresponding storage provided by `expect` instances (where it is associated with `*this`), or discarded. See `<<expect,expect>>`.
Returns: :: `*this`.
'''
[[operator_eq-error]]
==== `operator==`
.#include <boost/leaf/error.hpp>
[source,c++]
----
namespace boost { namespace leaf {
bool operator==( error const & e1, error const & e2 ) noexcept;
} }
----
Returns: :: `true` if the two values `e1` and `e2` are equal, `false` otherwise.
'''
[[operator_neq-error]]
==== `operator!=`
.#include <boost/leaf/error.hpp>
[source,c++]
----
namespace boost { namespace leaf {
bool operator!=( error const & e1, error const & e2 ) noexcept;
} }
----
Returns: :: `!(e1==e2)`.
'''
[[operator_shl-error]]
==== `operator<<`
.#include <boost/leaf/error.hpp>
[source,c++]
----
namespace boost { namespace leaf {
std::ostream & operator<<( std::ostream & os, error const & e )
} }
----
Effects: :: Prints an `unsigned int` value that uniquely identifies the value `e`.
'''
[[next_error_value]]
==== `next_error_value`
.#include <boost/leaf/error.hpp>
[source,c++]
----
namespace boost { namespace leaf {
error next_error_value() noexcept;
} }
----
Returns: :: The `error` value which will be constructed the next time the `<<error::error,error>>` constructor is invoked from the calling thread.
+
This function can be used to associate error objects with the next `error` value to be reported. Use with caution, only when restricted to reporting errors using specific third-party types, incompatible with LEAF -- for example when reporting an error from a C callback. As soon as control exits this critical path, you should create and return a `leaf::error` object (which will be equal to the `error` object returned by the earlier call to `next_error_value`).
IMPORTANT: `error` values are unique across the entire program.
'''
[[last_error_value]]
==== `last_error_value`
.#include <boost/leaf/error.hpp>
[source,c++]
----
namespace boost { namespace leaf {
error last_error_value() noexcept;
} }
----
Returns: :: The last `error` value constructed by the calling thread.
IMPORTANT: `error` values are unique across the entire program.
'''
[[is_error_type]]
==== `is_error_type`
.#include <boost/leaf/error.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class T>
struct is_error_type
{
static constexpr bool value = <<unspecified>>;
};
} }
----
The `is_error_type` template should be specialized for each user-defined type which the user desires to be used as an error type within LEAF, for example:
[source,c++]
----
enum my_error
{
error1,
error2,
....
};
namespace boost { namespace leaf {
template<> struct is_error_type<my_error>: std::true_type { };
} }
----
This requirement is designed to trigger a diagnostic in case the user unintentionally passes some random object to the `leaf::error` constructor.
[TIP]
--
The `is_error_type` template needs not be specialized for:
* types that define an accessible data member `value`,
* `std::error_code`,
* `boost::system::error_code`.
--
'''
=== Automatic Propagation
include::synopses/preload.adoc[]
These two functions are used to automatically associate error objects with failures that propagate through the scope where they are invoked.
'''
[[preload]]
==== `preload`
.#include <boost/leaf/error.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class... E>
<<unspecified-type>> preload( E && ... e ) noexcept;
} }
----
Requirements: :: `<<is_error_type,is_error_type>><E>::value` must be `true` for each `E`.
Effects: :: All `e...` objects are forwarded and stored into the returned object of unspecified type, which should be captured by `auto` and kept alive in the calling scope. When that object is destroyed:
* If a new `leaf::<<error,error>>` value was created (in the calling thread) since the object returned by `preload` was created, the stored `e...` objects are propagated and become associated with the _last_ such `leaf::error` value;
* Otherwise, if `std::unhandled_exception()` returns `true`, the stored `e...` objects are propagated and become associated with the _first_ `leaf::error` value created later on;
* Otherwise, the stored `e...` objects are discarded.
'''
[[defer]]
==== `defer`
.#include <boost/leaf/error.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class... F>
<<unspecified-type>> defer( F && ... f ) noexcept;
} }
----
Requirements: :: Each of the `f~i~` objects must be a function that does not throw exceptions, takes no arguments and returns an object of a no-throw movable type `E~i~` for which `<<is_error_type,is_error_type>><E~i~>::value` is `true`.
Effects: :: All `f...` objects are forwarded and stored into the returned object of unspecified type, which should be captured by `auto` and kept alive in the calling scope. When that object is destroyed:
* If a new `leaf::<<error,error>>` value was created (in the calling thread) since the object returned by `defer` was created, each of the stored `f...` is called, and the returned object is propagated and becomes associated with the _last_ such `leaf::error` value;
* Otherwise, if `std::unhandled_exception()` returns `true`, each of the stored `f...` is called, and the returned objects are propagated and become associated with the _first_ `leaf::error` value created later on;
* Otherwise, the stored `f...` objects are discarded.
'''
[[error_capture]]
=== Class `error_capture`
include::synopses/error_capture.adoc[]
Objects of class `error_capture` are similar to `<<expect,expect>>` instances in that they contain error objects and can be examined by (their own overloads of) `<<peek-error_capture,peek>>` and `<<handle_error-error_capture,handle_error>>`. However, unlike `expect` objects, `error_capture` objects:
* are immutable;
* are allocated on the heap;
* associate all of the stored objects with exactly one `error` value;
* when probed with `peek`/`handle_error`, the lookup is dynamic;
* define `noexcept` copy/move/assignment operations.
The default constructor can be used to initialize an empty `error_capture`. Use `<<capture-expect,capture>>` to capture all objects associated with a given `error` value from a given `expect` object.
[NOTE]
--
Typical use of `error_capture` objects is to transport error objects across threads, however they are rarely used directly. Instead:
* With exception handling, use `<<capture_exception,capture_exception>>` / `<<get,get>>`;
* Without exception handling, simply return a <<capture-result,captured>> `result<T>` from a worker thread.
--
'''
[[error_capture::error_capture]]
==== Constructor
.#include <boost/leaf/error_capture.hpp>
[source,c++]
----
namespace boost { namespace leaf {
error_capture::error_capture() noexcept;
} }
----
Effects: Initializes an empty `error_capture` instance.
Postcondition: :: `<<peek-error_capture,peek>><P>(*this,e)` returns a null pointer for any `P` and any `<<error,error>>` value `e`.
'''
[[error_capture::operator_bool]]
==== Conversion to `bool`
.#include <boost/leaf/error_capture.hpp>
[source,c++]
----
namespace boost { namespace leaf {
error_capture::operator bool() const noexcept;
} }
----
Returns: :: `false` if `*this` is empty, `true` otherwise.
'''
[[error_capture::unload]]
==== `unload`
.#include <boost/leaf/error_capture.hpp>
[source,c++]
----
namespace boost { namespace leaf {
error error::unload() noexcept;
} }
----
Effects: :: The objects stored in `*this` are moved into storage provided by `<<expect,expect>>` objects in the calling thread, as if each stored object is passed to the constructor of `<<error,error>>`.
Returns: :: The `error` value the unloaded objects are associated with.
Postcondition: :: `!(*this)`.
'''
[[handle_error-error_capture]]
==== `handle_error`
.#include <boost/leaf/error_capture.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class... F>
<<deduced_return_type>> error_capture::handle_error( error_capture const & ec, F && ... f ) noexcept;
} }
----
Requirements: ::
* `sizeof...(F) > 0`;
* Each `f...` must be callable;
* All `f...` must return the same static type.
Return type: :: The return type `R` of `handle_error` is deduced as the return type of the `f...` functions, except if their return type is `void`, in which case `R` is deduced as `bool`, and the behavior (described below) is as if the `f...` functions return `true`.
Effects: :: Attempts to match, by type, the objects currently stored in `ec`, with the arguments of each of the `f...` function objects, in order.
+
If a complete match is found among `f...`, the matched function is called with the entire set of corresponding objects from `ec` (the function may not modify those values), and the returned value is forwarded to the caller of `handle_error`.
+
Otherwise, `handle_error` returns `unhandled_error<R>::value(e)`, where `R` is the deduced return type of `handle_error`, and `e` is the `<<error,error>>` object captured in `ec`. The main `unhadled_error` template is defined such that:
+
--
* if `R` is `bool`, `unhandled_error<R>::value(e)` returns `false`;
* else, if `R` is an instance of the `<<result,result>>` template, `unhandled_error<R>::value(e)` returns `e` (note, `result<T>` can be implicitly initialized by the `<<error,error>>` object `e`);
* else, if `std::is_integral<R>::value`, the call to `unhandled_error<R>::value(e)` returns `static_cast<R>(-1)`;
* else, the call to `unhandled_error<R>::value(e)` returns `R()`.
--
+
NOTE: The `unhandled_error` template may be specialized for user-defined types as needed.
Example: ::
+
[source,c++]
----
bool matched = handle_error( ec,
[ ] ( e_file_name const & fn, e_errno const & errn )
{
std::cerr << "Failed to access " << fn << ", errno=" << errn << std::endl;
},
[ ] ( e_errno const & errn )
{
std::cerr << "I/O error, errno=" << errn << std::endl;
}
);
----
+
Assuming `struct e_file_name { std::string value; }` and `struct e_errno { int value; }`, the call to `handle_error` above will: +
+
* Check if the `error_capture` object `ec` contains `e_file_name` and `e_errno` objects. If it does, it will pass them to the first lambda function, then return `true`;
* Otherwise if it contains just `e_errno`, it will pass it to the second lambda function, then return `true`;
* Otherwise, `handle_error` returns `false`.
'''
[[peek-error_capture]]
==== `peek`
.#include <boost/leaf/error_capture.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class P>
P const * peek( error_capture const & ec ) noexcept;
} }
----
Returns: :: If `ec` currently stores an object of type `P`, returns a read-only pointer to that object. Otherwise returns a null pointer.
'''
[[diagnostic_output-error_capture]]
==== `diagnostic_output`
.#include <boost/leaf/error_capture.hpp>
[source,c++]
----
namespace boost { namespace leaf {
void diagnostic_output( std::ostream & os, error_capture const & ec );
} }
----
Effects: :: Prints diagnostic information about the objects stored in `ec`.
NOTE: The printing of each individual object is done by the rules described <<wrapping,here>>.
'''
[[result]]
=== Class Template `result`
include::synopses/result.adoc[]
'''
[[result::result]]
==== Constructors
.#include <boost/leaf/result.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class T>
result<T>::result() noexcept;
template <class T>
result<T>::result( T const & v );
template <class T>
result<T>::result( T && v ) noexcept;
template <class T>
result<T>::result( leaf::error const & e ) noexcept;
template <class T>
result<T>::result( leaf::error_capture const & ec ) noexcept;
} }
----
A `result<T>` object is in one of two states:
* Value state, in which case it contains an object of type `T`, and `<<result::value,value>>`/`<<result::value,operator*>>` can be used to access the contained value.
* Error state, in which case it contains an object of type `<<error,error>>` or an object of type `<<error_capture,error_capture>>`, and calling `<<result::value,value>>`/`<<result::value,operator*>>` throws `leaf::<<bad_result,bad_result>>`.
To get a `result<T>` object in error state, initialize it with a `leaf::error` or a `leaf::error_capture` .
Otherwise a `result<T>` is initialized in value state using the default constructor of `T`, or by copying or moving from `v`.
IMPORTANT: A `result` that is in value state converts to `true` in boolean contexts. A `result` that is in error state converts to `false` in boolean contexts.
'''
[[result::operator_bool]]
==== Conversion to `bool`
.#include <boost/leaf/result.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class T>
result<T>::operator bool() const noexcept;
} }
----
Returns: :: If `*this` was initialized in value state, returns `true`, otherwise returns `false`. See `<<result::result,Constructors>>`.
'''
[[result::value]]
==== `value`/`operator*`
.#include <boost/leaf/result.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class T>
T const & result<T>::value() const;
template <class T>
T & result<T>::value();
template <class T>
T const & result<T>::operator*() const;
template <class T>
T & result<T>::operator*();
} }
----
Effects: :: If `*this` was initialized in value state, returns a reference to the stored value, otherwise throws `leaf::<<bad_result,bad_result>>`. See `<<result::result,Constructors>>`.
'''
[[result::error]]
==== `error`
.#include <boost/leaf/result.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class T>
template <class... E>
leaf::error result<T>::error( E && ... e ) noexcept;
} }
----
This member function is designed to be used in `return` statements in functions that return `result<T>` (or `leaf::<<error,error>>`) to return an error to the caller.
Effects: ::
* If `*this` is in value state, returns `leaf::<<error::error,error>>(std::forward<E>(e...))`, which begins propagating a new `error` value (as opposed to forwarding an existing `error` value);
* If `*this` is in error state, it stores either an `<<error_capture,error_capture>>` or a `leaf::<<error,error>>`:
** if `*this` stores an `<<error_capture,error_capture>> cap`, `*this` is converted to store the `leaf::<<error,error>>` value returned from `cap.<<error_capture::unload,unload>>()`, then
** if `*this` stores a `leaf::error` value `err`, returns `err.<<error::propagate,propagate>>(std::forward<E>(e...))`, which forwards the same `error` to the caller, augmenting it with the additional error objects `e...`.
'''
[[capture-result]]
==== `capture`
.#include <boost/leaf/result.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class... E, class T>
result<T> capture( expect<E...> & exp, result<T> const & r );
} }
----
Returns: ::
* If `*this` is in value state, returns `*this`.
* If `*this` is in error state and stores an `<<error_capture,erorr_capture>>` object, returns `*this`.
* If `*this` is in error state and stores a `leaf::<<error,error>>` value `err`, returns `<<capture-expect,capture>>(exp,err)`.
NOTE: For an example, see <<technique_transport-result,Transporting Errors between Threads using `result<T>`>>.
'''
[[handle_error-result]]
==== `handle_error`
.#include <boost/leaf/result.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class... E, class T, class... F>
<<deduced_return_value>> result::handle_error( expect<E...> const & exp, result<T> const & r, F && ... f ) noexcept;
} }
----
Preconditions: :: `!r`.
Returns: ::
* If `r` stores an `<<error_capture,error_capture>>` object `cap`, returns `<<handle_error-error_capture,handle_error>><E...>(cap,f...)`.
* If `r` stores a `leaf::<<error,error>>` value `err`, returns `<<handle_error-expect,handle_error>><E...>(exp,err,f...)`.
NOTE: The return type is deduced as described <<handle_error-expect,here>>.
Example: ::
+
If `g` succeeds, `f` consumes its return value and indicates success to its caller. Otherwise, if `g` fails with a `my_error`, `f` handles it and, again, returns success; all other error types are automatically forwarded to the caller, due to `<<uhnandled_error,unhandled_error>>`.
+
[source,c++]
----
leaf::result<void> f()
{
leaf::expect<my_error> exp;
if( leaf::result<int> r = g() )
{
//use r, then indicate success:
return { };
}
else
{
//handle my_error, forward all other errors to the caller:
return leaf::handle_error( exp, r,
[ ]( my_error const & err ) -> result<void>
{
//Deal with my_error:
....
//Indicate success:
return { };
} );
}
}
----
'''
[[peek-result]]
==== `peek`
.#include <boost/leaf/result.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class P, class... E, class T>
P const * peek( expect<E...> const & exp, result<T> const & r ) noexcept;
} }
----
Preconditions: :: `!r`.
Returns: ::
* If `r` stores an `<<error_capture,error_capture>>` object `cap`, returns `<<peek-error_capture,peek>><P>(cap)`.
* If `r` stores a `leaf::<<error,error>>` value `err`, returns `<<peek-expect,peek>><P>(exp,err)`.
'''
[[diagnostic_output-result]]
==== `diagnostic_output`
.#include <boost/leaf/result.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class... E, class T>
void diagnostic_output( std::ostream & os, expect<E...> const & exp, result<T> const & r );
} }
----
Preconditions: :: `!r`.
Returns: ::
* If `r` stores an `<<error_capture,error_capture>>` object `cap`, returns `<<diagnostic_output-error_capture,diagnostic_output>>(os,cap)`.
* If `r` stores a `leaf::<<error,error>>` value `err`, returns `<<diagnostic_output-expect,diagnostic_output>>(os,exp,err)`.
'''
[[bad_result]]
==== `bad_result`
.#include <boost/leaf/result.hpp>
[source,c++]
----
namespace boost { namespace leaf {
struct bad_result: std::exception { };
} }
----
This exception is thrown by `<<result::value,value>>()`/`<<result::value,operator*>>()` if they`re invoked for a `result` object that is in error state.
'''
[[LEAF_ERROR]]
==== `LEAF_ERROR`
.#include <boost/leaf/result.hpp>
[source,c++]
----
#define LEAF_ERROR(...) <<unspecified>>
----
Effects: :: `LEAF_ERROR(e...)` is equivalent to `leaf::<<error::error,error>>(e...)`, except the current source location is automatically passed to the `leaf::error` constructor, in addition to `e...`, in a `<<common,e_source_location>>` object.
NOTE: `LEAF_ERROR` is designed for use in `return` expressions, to automatically communicate the location of the error being reported (but see <<expect>>).
'''
[[LEAF_AUTO]]
==== `LEAF_AUTO`
.#include <boost/leaf/result.hpp>
[source,c++]
----
#define LEAF_AUTO(v,r) auto _r_##v = r; if( !_r_##v ) return _r_##v.error(); auto & v = *_r_##v
----
'''
[[LEAF_CHECK]]
==== `LEAF_CHECK`
.#include <boost/leaf/result.hpp>
[source,c++]
----
#define LEAF_CHECK(r) {auto _r = r; if( !_r ) return _r.error();}
----
'''
[[common]]
=== Common Error Types
include::synopses/common.adoc[]
This header defines some common error types which can be used directly.
'''
[[e_api_function]]
==== `e_api_function`
[source,c++]
----
namespace boost { namespace leaf {
struct e_api_function { char const * value; };
} }
----
The `e_api_function` type is designed to capture the name of the API function which returned an error. For example, if you're reporting an error from `fread`, you could use `leaf::e_api_function { "fread" }`.
WARNING: The passed value is stored as a C string (`char const *`), so you should only pass string literals for `value`.
'''
[[e_file_name]]
==== `e_file_name`
[source,c++]
----
namespace boost { namespace leaf {
struct e_file_name { std::string value; };
} }
----
When a file operation fails, you could use `e_file_name` to store the name of the file.
'''
[[e_errno]]
==== `e_errno`
[source,c++]
----
namespace boost { namespace leaf {
struct e_errno
{
int value;
friend std::ostream & operator<<( std::ostream & os, e_errno const & err );
};
} }
----
`e_errno` is suitable to capture `errno`. `e_errno` objects use `strerror` to convert the `errno` code to a friendlier error message when `<<diagnostic_output-expect,diagnostic_output>>` is invoked.
WARNING: It is a logic error to use `e_errno` with <<preload>>; it should be passed to the `error` <<error::error,constructor>> directly, or used with <<defer>>.
'''
[[e_LastError]]
==== `e_LastError`
[source,c++]
----
namespace boost { namespace leaf {
namespace windows
{
struct e_LastError
{
unsigned value;
};
}
} }
----
`e_LastError` is designed to communicate `GetLastError()` values on Windows.
'''
[[e_at_line]]
==== `e_at_line`
[source,c++]
----
namespace boost { namespace leaf {
struct e_at_line { int value; };
} }
----
`e_at_line` can be used to communicate the line number when communicating errors (for example parse errors) about a text file.
'''
[[e_type_info_name]]
==== `e_type_info_name`
[source,c++]
----
namespace boost { namespace leaf {
struct e_type_info_name { char const * value; };
} }
----
`e_type_info_name` is designed to store the return value of `std::type_info::name`.
'''
[[e_source_location]]
==== `e_source_location`
[source,c++]
----
namespace boost { namespace leaf {
struct e_source_location
{
char const * const file;
int const line;
char const * const function;
friend std::ostream & operator<<( std::ostream & os, e_source_location const & x );
};
} }
----
The <<LEAF_ERROR>>, <<LEAF_EXCEPTION>> and <<LEAF_THROW>> macros capture `pass:[__FILE__]`, `pass:[__LINE__]` and `pass:[__FUNCTION__]` into a `e_source_location` object, if there is currently `<<expect,expect>>` storage available for it. When `<<diagnostic_output-expect,diagnostic_output>>` is invoked, all three items are printed.
'''
[[e_unexpected]]
==== `e_unexpected`
[source,c++]
----
namespace boost { namespace leaf {
struct e_unexpected
{
char const * (*first_type)();
int count;
friend std::ostream & operator<<( std::ostream & os, e_unexpected const & x );
};
} }
----
Whenever an error object of unexpected type (a type for which there is currently no `<<expect,expect>>` storage available) is passed to the `leaf::error` <<error::error,constructor>> or other similar function, if there is currently `expect` storage available for `e_unexpected`, LEAF will communicate the type of the object in the first such occurrence, as well as the count of these occurrences regardless of type.
'''
[[e_unexpected_diagnostic_output]]
==== `e_unexpected_diagnostic_output`
[source,c++]
----
namespace boost { namespace leaf {
struct e_unexpected_diagnostic_output
{
<<unspecified>>
friend std::ostream & operator<<( std::ostream & os, e_unexpected_diagnostic_output const & x );
};
} }
----
Whenever an error object of unexpected type (a type for which there is currently no `<<expect,expect>>` storage available) is passed to the `leaf::error` <<error::error,constructor>> or other similar function, if there is currently `expect` storage available for `e_unexpected_diagnostic_output`, even though the error object itself is discarded, it will show in the text printed by <<diagnostic_output-expect>>.
WARNING: Using `e_unexpected_diagnostic_output` may allocate memory dynamically.
== Exception Handling Reference
=== Working with Exception Objects
include::synopses/exception.adoc[]
'''
[[exception]]
==== `exception`
[source,c++]
.#include <boost/leaf/exception.hpp>
----
namespace boost { namespace leaf {
template <class Ex, class... E>
<<unspecified>> exception( Ex && ex, E && ... e ) noexcept;
} }
----
Requirements: ::
* `Ex` must derive from `std::exception`.
* For all of E, `<<is_error_type,is_error_type>><E~i~>::value` is `true`.
Returns: :: An object of unspecified type which derives publicly from `Ex` *and* from class `<<error,error>>` such that:
* its `Ex` subobject is initialized by `std::forward<Ex>(ex)`;
* its `error` subojbect is initialized by `std::forward<E>(e)...`.
TIP: If thrown, the returned object can be caught as `Ex &` or as `leaf::<<error,error>>`.
NOTE: To automatically capture `pass:[__FILE__]`, `pass:[__LINE__]` and `pass:[__FUNCTION__]` with the returned object, use <<LEAF_EXCEPTION>> instead of `leaf::exception`.
'''
[[get_error]]
==== `get_error`
.#include <boost/leaf/exception.hpp>
[source,c++]
----
namespace boost { namespace leaf {
error get_error( std::exception const & ex ) noexcept;
} }
----
Returns: ::
* If `auto e = dynamic_cast<leaf::<<error,error>> const *>(&ex)` succeeds, returns `*e`.
* Othrewise, it returns an unspecified `leaf::error` value, which is "temporarily" associated with any and all currently unhandled exceptions.
+
IMPORTANT: A successful call to `<<handle_exception,handle_exception>>` breaks this association.
This function is designed to augment exceptions with additional error objects in exception-neutral contexts, when it is known that at least some of the intercepted exceptions are not thrown using `<<exception,leaf::exception>>`, and therefore do not derive from `leaf::error`. Example:
[source,c++]
----
try
{
f();
}
catch( std::exception const & ex )
{
leaf::get_error(ex).propagate( e_this{x}, e_that{y} );
throw;
}
----
If it is known that all intercepted exceptions are thrown using `leaf::exception`, the following would be more optimal:
[source,c++]
----
try
{
f();
}
catch( leaf::error e )
{
e.propagate( e_this{x}, e_that{y} );
throw;
}
----
'''
[[peek-exception]]
==== `peek`
.#include <boost/leaf/exception.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class P, class... E>
P const * peek( expect<E...> const & exp, std::exception const & ex ) noexcept;
} }
----
Effects: :: As if: `return leaf::<<peek-expect,peek>><P>( exp, <<get_error,get_error>>(ex) );`
'''
[[handle_exception]]
==== `handle_exception`
.#include <boost/leaf/exception.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class... E, class... F>
void handle_exception( expect<E...> & exp, std::exception const & ex, F && ... f );
} }
----
Effects: :: Equivalent to: `if( !<<handle_error-expect,handle_error>>( exp, <<get_error,get_error>>(ex), std::forward<F>(f)...) ) throw;`
IMPORTANT: In case the dynamic type of `ex` does not derive from `leaf::error` and the call to `handle_error` succeeds, the association between the `leaf::error` value returned by `<<get_error,get_error>>` and the currently unhandled exceptions is broken.
'''
[[diagnostic_output-exception]]
==== `diagnostic_output`
.#include <boost/leaf/exception.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class... E>
void diagnostic_output( std::ostream & os, expect<E...> const & exp, std::exception const & ex );
} }
----
Effects: :: Equivalent to: `<<diagnostic_output-expect,diagnostic_output>>( os, exp, <<get_error,get_error>>(ex) );`
'''
[[LEAF_EXCEPTION]]
==== `LEAF_EXCEPTION`
[source,c++]
.#include <boost/leaf/exception.hpp>
----
#define LEAF_EXCEPTION(...) <<unspecified>>
----
Effects: :: This is a variadic macro which forwards its arguments to the function template <<exception>>, in addition capturing `pass:[__FILE__]`, `pass:[__LINE__]` and `pass:[__FUNCTION__]`, in a `<<common,e_source_location>>` object.
'''
[[LEAF_THROW]]
==== `LEAF_THROW`
[source,c++]
.#include <boost/leaf/exception.hpp>
----
#define LEAF_THROW(...) throw LEAF_EXCEPTION(__VA_ARGS__)
----
Effects: :: Throws the exception object returned by <<LEAF_EXCEPTION>>.
'''
[[diagnostic_output_current_exception]]
=== Diagnostic Output for the Current Exception
include::synopses/diagnostic_output_current_exception.adoc[]
Effects: :: This function prints a developer-friendly (but not user-friendly) diagnostic information about the current exception to `os`.
Example: ::
+
[source,c++]
----
int main()
{
leaf::expect<e_unexpected_diagnostic_output> exp;
try
{
f();
return 0;
}
catch(...)
{
leaf::diagnostic_output_current_exception(std::cerr,exp);
return 1;
}
}
----
+
[.text-right]
<<expect>> | `<<common,e_unexpected_diagnostic_output>>`
'''
=== Transporting of Exceptions between Threads
include::synopses/exception_capture.adoc[]
This header defines functions that can be used to transport exceptions and associated error objects from one thread to another.
'''
[[capture_exception]]
==== `capture_exception`
.#include <boost/leaf/exception_capture.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class... E, class F>
<<unspecified-type>> capture_exception( F && f ) noexcept;
} }
----
Requirements: :: `F` must be a function type.
Returns: :: A function of unspecified type which wraps `f` and, when called, forwards all of the arguments to `f` and returns its return value to the caller, except if `f` throws, in which case the specified `E...` types and the exception object are captured and transported in another exception object.
NOTE: The `capture_exception` function is designed for use with `<<get,get>>`, to effectively transport error objects across thread boundaries (for example see <<technique_transport-exceptions,Transporting Errors between Threads using Exception Handling>>).
'''
[[get]]
==== `get`
.#include <boost/leaf/exception_capture.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class Future>
decltype(std::declval<Future>().get()) get( Future && f );
} }
----
Requirements: :: `Future` must be a `std::future` or other similar type used to recover future values by a member function `get()`.
Returns: :: `f.get()`.
Throws: :: Any exception thrown by `f.get()`. If the future function was launched using `<<capture_exception,capture_exception>><E...>`, all `E...` type objects captured in the worker thread are transported to the calling thread.
IMPORTANT: To store and to access the transported error objects, the calling thread must provide suitable `<<expect,expect>>` object(s).
[[techniques]]
== Programming Techniques
[[technique_preload]]
=== Preloading Errors
Consider the following exception type, designed for use without LEAF:
[source,c++]
----
class file_read_error: public std::exception
{
std::string file_name_;
public:
explicit file_read_error( std::string const & fn ): file_name_(fn) { }
std::string const & file_name() const noexcept { return file_name_; }
};
----
A catch statement that handles `file_read_error` exceptions:
[source,c++]
----
catch( file_read_error & e )
{
std::cerr << "Error reading \"" << e.file_name() << "\"\n";
}
----
Finally, a function that may throw `file_read_error` exceptions:
[source,c++]
----
void read_file( FILE * f ) {
....
size_t nr=fread(buf,1,count,f);
if( ferror(f) )
throw file_read_error(???); //File name not available here!
....
}
----
This is a problem: the `catch` needs a file name, but at the point of the `throw` a file name is not available (only a `FILE` pointer is). In general, the error might be detected in a library which can not assume that a meaningful name is available for any `FILE` it reads, even if a program that uses the library could reasonably make the same assumption.
Using LEAF, a file name may be associated with any exception after it has been thrown, while anything available at the point of the `throw` (e.g. `errno`) may be passed directly to the `leaf::<<exception,exception>>` function template:
[source,c++]
----
class file_read_error: public std::exception { };
struct e_file_name { std::string value; };
struct e_errno { int value; };
void read_file( FILE * f )
{
....
size_t nr=fread( buf,1,count,f );
if( ferror(f) )
throw leaf::exception( file_read_error(), e_errno{errno} );
....
}
void process_file( char const * name )
{
auto propagate = leaf::preload( e_file_name{name} );
if( FILE * fp=fopen(name,"rt")) {
std::shared_ptr<FILE> f(fp,fclose);
....
read_file(fp); //throws on error
....
}
else
throw leaf::exception(file_open_error());
}
----
[.text-right]
`<<exception,exception>>` | <<preload>>
The key is the call to `<<preload,preload>>`: it gets the file name ready to be associated with any exception that escapes `process_file`. This is fully automatic, and works regardless of whether the exception is thrown later in the same function, or by `read_file`, or by some third-party function we call.
Now, the `try...catch` that handles exceptions thrown by `process_file` may look like this:
[source,c++]
----
leaf::expect<e_errno,e_file_name> exp;
try
{
process_file("example.txt");
}
catch( file_io_error & e )
{
std::cerr << "I/O error!\n";
leaf::handle_exception( exp, e,
[ ]( e_file_name const & fn, e_errno const & errn )
{
std::cerr << "File name: " << fn.value << ", errno=" << errn << "\n";
}
);
}
----
[.text-right]
`<<expect,expect>>` | <<handle_exception>>
NOTE: This technique works exactly the same way when errors are reported using `leaf::<<result,result>>` rather than by throwing exceptions.
'''
[[technique_defer]]
=== Capturing `errno` with `defer`
Consider the following function:
[source,c++]
----
void read_file(FILE * f) {
....
size_t nr=fread(buf,1,count,f);
if( ferror(f) )
throw leaf::exception( file_read_error(), e_errno{errno} );
....
}
----
[.text-right]
`<<exception,exception>>`
It is pretty straight-forward, reporting `e_errno` as it detects a `ferror`. But what if it calls `fread` multiple times?
[source,c++]
----
void read_file(FILE * f) {
....
size_t nr1=fread(buf1,1,count1,f);
if( ferror(f) )
throw leaf::exception( file_read_error(), e_errno{errno} );
size_t nr2=fread(buf2,1,count2,f);
if( ferror(f) )
throw leaf::exception( file_read_error(), e_errno{errno} );
size_t nr3=fread(buf3,1,count3,f);
if( ferror(f) )
throw leaf::exception( file_read_error(), e_errno{errno} );
....
}
----
[.text-right]
`<<exception,exception>>`
Ideally, associating `e_errno` with each exception should be automated. One way to achieve this is to not call `fread` directly, but wrap it in another function which checks for `ferror` and associates the `e_errno` with the exception it throws.
<<technique_preload,Preloading Errors>> describes how to solve a very similar problem without a wrapper function, but that technique does not work for `e_errno` because `<<preload,preload>>` would capture `errno` before a `fread` call was attempted, at which point `errno` is probably `0` -- or, worse, leftover from a previous I/O failure.
The solution is to use `<<defer,defer>>`, so we don't have to remember to include `e_errno` with each exception; `errno` will be associated automatically with any exception that escapes `read_file`:
[source,c++]
----
void read_file(FILE * f) {
auto propagate = leaf::defer( [ ] { return e_errno{errno} } );
....
size_t nr1=fread(buf1,1,count1,f);
if( ferror(f) )
throw leaf::exception(file_read_error());
size_t nr2=fread(buf2,1,count2,f);
if( ferror(f) )
throw leaf::exception(file_read_error());
size_t nr3=fread(buf3,1,count3,f);
if( ferror(f) )
throw leaf::exception(file_read_error());
....
}
----
[.text-right]
<<defer>> | `<<exception,exception>>`
This works similarly to `preload`, except that capturing of the `errno` is deferred until the destructor of the `propagate` object is called, which calls the passed lambda function to obtain the `errno`.
'''
[[technique_disparate_error_types]]
=== Working with Disparate Error Codes
Because most libraries define their own mechanism for reporting errors, programmers often need to use multiple incompatible error-reporting interfaces in the same program. This led to the introduction of `boost::error_code` which later became `std::error_code`. Each `std::error_code` object is assigned an `error_category`. Libraries that communicate errors in terms of `std::error_code` define their own `error_category`. For libraries that do not, the user can "easily" define a custom `error_category` and still translate domain-specific error codes to `std::error_code`.
But let`s take a step back and consider _why_ do we have to express every error in terms of the same static type, `std::error_code`? We need this translation because the {CPP} static type-checking system makes it difficult to write functions that may return error objects of the disparate static types used by different libraries. Outside of this limitation, it would be preferable to be able to write functions that can communicate errors in terms of arbitrary {CPP} types, as needed.
To drive this point further, consider the real world problem of mixing `boost::error_code` and `std::error_code` in the same program. In theory, both systems are designed to be able to express one error code in terms of the other. In practice, describing a _generic_ system for error categorization in terms of another _generic_ system for error categorization is not trivial.
Ideally, functions should be able to communicate different error types without having to translate between them, and {CPP} does offer a mechanism that does just that, it's called exception handling. And it is not a coincidence that the attempt to bind {CPP} exception types with the interface of each function, A.K.A. exception specifications, was so problematic that it had to be abandoned (while I am an outspoken proponent of exception handling, I do acknowledge that in practice, often for good reasons, exception handling may not be available or permitted).
LEAF solves this problem without using exception handling: a scope that is able to handle either `std::error_code` or `boost::error_code` would look like this:
[source,c++]
----
{
leaf::expect<std::error_code, boost::error_code> exp;
if( leaf::result<T> r = f() )
{
//Success
}
else
{
bool matched = leaf::handle_error( exp, r,
[ ]( std::error_code const & e )
{
//Handle std::error_code
},
[ ]( boost::error_code const & e )
{
//Handle boost::error_code
}
);
}
}
----
[.text-right]
`<<expect,expect>>` | `<<result,result>>` | <<handle_error-expect>>
And here is a function which is able to report either `std::error_code` or `boost::error_code`:
[source,c++]
----
leaf::result<T> f()
{
if( std::error_code ec = g1() )
{
//Success
}
else
return leaf::error(ec);
if( boost::error_code ec = g2() )
{
//Success
}
else
return leaf::error(ec);
}
----
[.text-right]
`<<result,result>>` | `<<error,error>>`
Of course, under refactoring `f` can be changed to return any other error type as needed, without requiring a change in its static interface. With LEAF, such changes become transparent to any error-neutral function that calls and forwards errors from `f` to its caller; only the error handling scope needs to be updated to handle the new error types returned by `f`.
'''
[[technique_augment_in_catch]]
=== Augmenting Exceptions in a `catch`
What makes `<<preload,preload>>` and `<<defer,defer>>` useful (see <<technique_preload,Preloading Errors>> and <<technique_defer,Capturing `errno` with `defer`>>) is that they automatically include error objects with any exception or error reported by a function.
But what if we need to include some error object conditionally? When using exception handling, it would be nice to be able to do this in a `catch` statement which selectively augments passing exceptions.
LEAF supports the following approach, assuming all exceptions derive from `std::exception`:
[source,c++]
----
try
{
....
function_that_throws();
....
}
catch( std::exception const & e )
{
if( condition )
leaf::get_error(e).propagate( e_this{....}, e_that{....} );
throw;
}
----
[.text-right]
<<get_error>> | `<<error::propagate,error::propagate>>`
The reason we need to use `<<get_error,get_error>>` is that not all exception types derive from `leaf::<<error,error>>`. If the caught exception has a `leaf::error` subobject, `get_error` will return that `leaf::error` value. Also, such exceptions can be intercepted by `catch( error e )` if needed.
But if the caught exception doesn't have a `leaf::error` subobject, `get_error` returns an unspecified `leaf::error` value, which is temporarily associated with any and all current exceptions, until successfully handled by `<<handle_exception,handle_exception>>`. While this association is imperfect (because it does not pertain to a specific exception object) it is the best that can be done in this case.
'''
[[technique_preload_in_c_callbacks]]
=== Using `next_error_value` in C-callbacks
Communicating information pertaining to a failure detected in a C callback is tricky, because C callbacks are limited to a specific static signature, which may not use {CPP} types.
LEAF makes this easy. As an example, we'll write a program that uses Lua and reports a failure from a {CPP} function registered as a C callback, called from a Lua program. The failure will be propagated from {CPP}, through the Lua interpreter (written in C), back to the {CPP} function which called it.
C/{CPP} functions designed to be called from a Lua program must use the following signature:
[source,c]
----
int do_work( lua_State * L );
----
Arguments are passed on the Lua stack (which is accessible through `L`). Results too are pushed onto the Lua stack.
First, let's initialize the Lua interpreter and register `do_work` as a C callback, available for Lua programs to call:
[source,c++]
----
std::shared_ptr<lua_State> init_lua_state() noexcept
{
std::shared_ptr<lua_State> L(lua_open(),&lua_close); //<1>
lua_register( &*L, "do_work", &do_work ); //<2>
luaL_dostring( &*L, "\ //<3>
\n function call_do_work()\
\n return do_work()\
\n end" );
return L;
}
----
<1> Create a new `lua_State`. We'll use `std::shared_ptr` for automatic cleanup.
<2> Register the `do_work` {CPP} function as a C callback, under the global name `do_work`. With this, calls from Lua programs to `do_work` will land in the `do_work` {CPP} function.
<3> Pass some Lua code as a `C` string literal to Lua. This creates a global Lua function called `call_do_work`, which we will later ask Lua to execute.
Next, let's define our `enum` used to communicate `do_work` failures:
[source,c++]
----
enum do_work_error_code
{
ec1=1,
ec2
};
----
We're now ready to define the `do_work` callback function:
[source,c++]
----
int do_work( lua_State * L ) noexcept
{
bool success=rand()%2; //<1>
if( success )
{
lua_pushnumber(L,42); //<2>
return 1;
}
else
{
leaf::next_error_value().propagate(ec1); //<3>
return luaL_error(L,"do_work_error"); //<4>
}
}
----
[.text-right]
<<next_error_value>> | `<<error::propagate,error::propagate>>`
<1> "Sometimes" `do_work` fails.
<2> In case of success, push the result on the Lua stack, return back to Lua.
<3> Associate an `do_work_error_code` object with the *next* `leaf::error` object we will definitely return from the `call_lua` function (below)...
<4> ...once control reaches it, after we tell the Lua interpreter to abort the program.
Now we'll write the function that calls the Lua interpreter to execute the Lua function `call_do_work`, which in turn calls `do_work`. We'll return `<<result,result>><int>`, so that our caller can get the answer in case of success, or an error:
[source,c++]
----
leaf::result<int> call_lua( lua_State * L )
{
lua_getfield( L, LUA_GLOBALSINDEX, "call_do_work" );
if( int err=lua_pcall(L,0,1,0) ) //<1>
{
auto propagate = leaf::preload( e_lua_error_message{lua_tostring(L,1)} ); //<2>
lua_pop(L,1);
return leaf::error( e_lua_pcall_error{err} );
}
else
{
int answer=lua_tonumber(L,-1); //<3>
lua_pop(L,1);
return answer;
}
}
----
[.text-right]
`<<result,result>>` | <<preload>> | `<<error::error,error::error>>`
<1> Ask the Lua interpreter to call the global Lua function `call_do_work`.
<2> Something went wrong with the call, so we'll return a `leaf::<<error,error>>`. If this is a `do_work` failure, the `do_work_error_code` object prepared in `do_work` will become associated with this `leaf::error` value. If not, we will still need to communicate that the `lua_pcall` failed with an error code and an error message.
<3> Success! Just return the int answer.
Finally, here is the `main` function which handles all failures:
[source,c++]
----
int main() noexcept
{
std::shared_ptr<lua_State> L=init_lua_state();
leaf::expect<do_work_error_code,e_lua_pcall_error,e_lua_error_message> exp; //<1>
for( int i=0; i!=10; ++i )
if( leaf::result<int> r = call_lua(&*L) )
std::cout << "do_work succeeded, answer=" << *r << '\n'; //<2>
else
{
bool matched = handle_error( exp, r,
[ ]( do_work_error_code e ) //<3>
{
std::cout << "Got do_work_error_code = " << e << "!\n";
},
[ ]( e_lua_pcall_error const & err, e_lua_error_message const & msg ) //<4>
{
std::cout << "Got e_lua_pcall_error, Lua error code = " << err.value << ", " << msg.value << "\n";
}
);
assert(matched);
}
return 0;
}
----
[.text-right]
`<<expect,expect>>` | `<<result,result>>` | <<handle_error-result>>
<1> Tell LEAF what error objects are expected.
<2> If the call to `call_lua` succeeded, just print the answer.
<3> Handle `e_do_work` failures.
<4> Handle all other `lua_pcall` failures.
[NOTE]
--
Follow this link to see the complete program: https://github.com/zajo/leaf/blob/master/example/lua_callback_result.cpp?ts=3[lua_callback_result.cpp].
Remarkably, the Lua interpreter is {CPP} exception-safe, even though it is written in C. Here is the same program, this time using a {CPP} exception to report failures from `do_work`: https://github.com/zajo/leaf/blob/master/example/lua_callback_eh.cpp?ts=3[lua_callback_eh.cpp].
--
'''
[[technique_transport]]
=== Transporting Error Objects between Threads
With LEAF, error objects use automatic storage duration, stored inside `<<expect,expect>>` instances. When using concurrency, we need a mechanism to detach error objects from a worker thread and transport them to another thread where errors are handled.
LEAF offers two interfaces for this purpose, one using `result<T>`, and another designed for programs that use exception handling.
[[technique_transport-result]]
==== Using `result<T>`
Without exceptions, transporting error objects between threads is as easy as calling `<<capture-result,capture>>`, passing the `<<expect,expect>>` object whose contents needs to be transported, and a `<<result,result>><T>` which may be in either value state or error state. This gets us a new `<<result,result>><T>` object which can be safely sent across thread boundaries.
Let's assume we have a `task` which produces a result but could also fail:
[source,c++]
----
leaf::result<task_result> task();
----
To prepare the returned `result` to be sent across the thread boundary, when we launch the asynchronous task, we wrap it in a lambda function that captures its result:
[source,c++]
----
std::future<leaf::result<task_result>> launch_task()
{
return std::async( std::launch::async, [ ]
{
leaf::expect<E1,E2,E3> exp;
return capture(exp,task());
} );
}
----
[.text-right]
`<<result,result>>` | `<<expect,expect>>` | <<capture-result>>
That's it! Later when we `get` the `std::future`, we can process the returned `result<task_result>` as if it was generated locally:
[source,c++]
----
....
leaf::expect<E1,E2,E3> exp;
if( leaf::result<task_result> r = fut.get() )
{
//Success! Use *r to access task_result.
}
else
{
handle_error( exp, r,
[ ] ( E1 e1, E2 e2 )
{
//Deal with E1, E2
},
[ ] ( E3 e3 )
{
//Deal with E3
}
);
}
----
[.text-right]
`<<expect,expect>>` | `<<result,result>>` | <<handle_error-result>>
NOTE: Follow this link to see a complete example program: https://github.com/zajo/leaf/blob/master/example/capture_result.cpp?ts=3[capture_result.cpp].
'''
[[technique_transport-exceptions]]
==== Using Exception Handling
When using exception handling, we need to capture the exception using `std::exception_ptr`, then capture the current error objects in an `<<error_capture,error_capture>>` and wrap both into another exception. In the main thread we unwrap and throw the original exception.
This, of course, is done automatically by LEAF. Let's assume we have a `task` which produces a `task_result` and throws on errors:
[source,c++]
----
task_result task();
----
When we launch the asynchronous task, we wrap it in a simple lambda function which calls `<<capture_exception,capture_exception>>`, specifying which error types we need transported:
[source,c++]
----
std::future<task_result> launch_task()
{
return std::async( std::launch::async,
leaf::capture_exception<E1,E2,E3>( [ ]
{
return task();
} ) );
}
----
[.text-right]
<<capture_exception>>
Later, instead of using `std::future::get`, we use `leaf::<<get,get>>`, then catch exceptions as if the function was called locally:
[source,c++]
----
....
leaf::expect<E1,E2,E3> exp;
try
{
task_result r = leaf::get(fut);
//Success!
}
catch( my_exception & e )
{
handle_exception( exp, e,
[ ] ( E1 e1, E2 e2 )
{
//Deal with E1, E2
},
[ ] ( E3 e3 )
{
//Deal with E3
}
);
}
----
[.text-right]
`<<expect,expect>>` | <<get>> | <<handle_exception>>
NOTE: Follow this link to see a complete example program: https://github.com/zajo/leaf/blob/master/example/capture_eh.cpp?ts=3[capture_eh.cpp].
[[rationale]]
== Design Rationale
Definition: :: Objects that carry information about error conditions are called error objects. For example, objects of type `std::error_code` are error objects.
NOTE: The following reasoning is independent of what mechanics are used to transport error objects, whether it is exception handling or anything else.
Definition: :: Depending on their interaction with error objects, functions can be classified as follows:
* *Error-initiating*: functions that initiate error conditions by creating new error objects.
* *Error-neutral*: functions that forward to the caller error objects returned by functions they call.
* *Error-handling*: functions that dispose of error objects forwarded to them, recovering normal program operation.
A crucial observation is that _error-initiating_ functions are typically low level functions that lack any context and can not determine, much less dictate, the correct program behavior in response to errors they initiate. Error conditions which (correctly) lead to termination in some programs may (correctly) be ignored in others; yet other programs may recover from them and resume normal operation.
Stronger: authors of _error-initiating_ functions may not even reason about what information about the error is required to deal with it, by a specific _error-handling_ function in a specific program domain, except to pass any relevant information which is naturally available to them.
The same reasoning applies to _error-neutral_ functions, but in this case there is the additional problem that the errors they need to communicate, in general, are initiated by functions multiple levels removed from them in the call chain, functions which usually are -- and should be treated as -- implementation details. The _error-neutral_ function should not be coupled with any error object type used by _error-initiating_ functions, for the same reason it should not be coupled with any other aspect of their interface.
Finally, _error-handling_ functions, by definition, have the full context they need to deal with at least some, if not all, failures. In this scope it is an absolute necessity that the author knows exactly what information must be communicated by lower level functions in order to recover from each error condition. Specifically, none of this necessary information can be treated as implementation details; the coupling which is to be avoided by _error-neutral_ functions is unavoidable and even desirable here.
We're now ready to define our
Design goals: ::
* *Error-initiating* functions should be able to communicate all information available to them that is relevant to the failure being reported.
* *Error-neutral* functions should not interfere or be coupled with error-related information that passes through them. They should be able to augment it with any additional information available to them, which may be relevant to any error they forward to the caller.
* *Error-handling* functions should be able to access all the information communicated by _error-initiating_ or _error-neutral_ functions that is needed in their domain in order to deal with failures.
The difficulty in reaching these design goals is in that they seem to require that all error objects be allocated dynamically (the Boost Exception library meets these design goals at the cost of dynamic memory allocation).
As it turns out, dynamic memory allocation is not necessary with the following
Adjustment: ::
* *Error-handling* functions should specify which of the information _error-initiating_ and _error-neutral_ functions are [.underline]#able# to communicate is [.underline]#actually needed# in order to deal with failures in a particular program domain. Ideally, no resources should be [.line-through]#used# wasted storing or communicating information which is not currently needed, even if it is relevant to the failure.
The `leaf::<<expect,expect>><E...>` class template implements this idea: it provides local storage for error objects of the `E...` types. Users instantiate this template in _error-handling_ functions, knowing which types of error objects are needed. When an `expect` object is created, `thread_local` pointers of the `E...` types are set to point to the corresponding storage within it. Later on, _error-initiating_ or _error-neutral_ functions wanting to communicate an error object of type `E` access the corresponding `thread_local` pointer to see if there is currently storage available for this type:
* If the pointer is not null, storage is available and the object is moved into that storage, exactly once -- regardless of how many levels of function calls must unwind before an appropriate _error-handling_ function is reached (that's the function where the `expect` object resides).
* If the pointer is null, storage is not available and the error object is discarded, since there is no use for it -- saving resources.
This almost works, except we need to make sure that _error-handling_ functions are protected from accessing stale error objects stored in `expect` objects, left there from previous failures, which would be a serious logic error. To this end, each failure is assigned a unique serial number, which is transported inside `leaf::<<result,result>>` objects that communicate failures. Each of the `E...` objects stored in an `expect<E...>` object is assigned the same unique identifier, permanently associating it with a particular failure.
Lastly, in _error-handling_ functions it makes sense to be able to not only recognize individual error conditions, but match specific error-handling code with the complete set of error objects that is required in each case.
In terms of {CPP} exception handling, it would be nice to be able to say something like:
[source,c++]
----
try
{
T r = process_file();
//Success, use r:
....
}
catch( file_read_error<e_file_name,e_errno> & e )
{
std::cerr <<
"Could not read " << e.get<e_file_name>() <<
", errno=" << e.get<e_errno>() << std::endl;
}
catch( file_read_error<e_errno> & e )
{
std::cerr <<
"File read error, errno=" << e.get<e_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 objects available. Unfortunately this syntax is not possible and, even if it were, not all programs use exceptions to handle errors.
Below is the same snippet, expressed in terms of LEAF:
[source,c++]
----
leaf::expect<e_file_name, e_errno> exp;
try
{
T r = process_file(); //Throws in case of failure, error objects stored inside exp.
//Success, use r:
....
}
catch( file_read_error & e )
{
//Match available error objects stored in exp,
//associated with the specific failure communicated by e.
leaf::handle_exception( exp, e,
[ ]( e_file_name const & file_name, e_errno const & errno_ )
{
std::cerr <<
"Could not read " << file_name.value <<
", errno=" << errno_ << std::endl;
},
[ ]( e_errno const & errno_ )
{
std::cerr <<
"File read error, errno=" << errno_ << std::endl;
},
[ ]
{
std::cerr << "File read error!" << std::endl;
}
);
}
----
[.text-right]
`<<expect,expect>>` | <<handle_exception>>
Of course LEAF works without exception handling as well. Below is the same snippet, written using `<<result,result>><T>`:
[source,c++]
----
leaf::expect<e_what, e_file_name, e_errno> exp;
if( leaf::result<T> r = process_file() ) //In case of failure error objects are stored inside exp.
{
//Success, use r.value():
....
}
else
{
//e_what is used to dispatch between error conditions:
switch( *leaf::peek<e_what>(exp,r) )
{
case file_read_error:
{
//Match available error objects stored in exp,
//associated with the specific failure communicated by r.
bool handled = leaf::handle_error( exp, r,
[ ]( e_file_name const & file_name, e_errno const & errno_ )
{
std::cerr <<
"Could not read " << file_name.value <<
", errno=" << errno_ << std::endl;
},
[ ]( e_errno const & errno_ )
{
std::cerr <<
"File read error, errno=" << errno_ << std::endl;
},
[ ]
{
std::cerr << "File read error!" << std::endl;
}
);
if( handled )
break;
}
//fallthrough:
default:
return r; //Error not handled, forward to the caller.
}
----
[.text-right]
`<<expect,expect>>` | `<<result,result>>` | <<peek-result>> | <<handle_error-result>>
NOTE: Please post questions and feedback on the Boost Developers Mailing List (LEAF is not part of Boost).
'''
[[exception_specifications]]
=== Critique 1: LEAF Does Not Enforce Correct Error Handling at Compile Time
A common critique of the LEAF design is that it does not statically enforce correct error handling. One specific idea I've heard from multiple sources is to add `E...` parameter pack to `result<T>`.
The idea is to be able to write something along these lines:
[source,c++]
----
result<T, E1, E2, E3> f() noexcept; <1>
result<T, E1, E3> g() noexcept <2>
{
if( result<T, E1, E2, E3> r = f() )
{
return r; //Success, return the T
}
else
{
return r.handle_error<E2>( [ ] ( .... ) <3>
{
....
} );
}
}
----
<1> `f` may only return errors of type `E1`, `E2`, `E3`.
<2> `g` narrows that to only `E1` and `E3`.
<3> Because `g` may only return errors of type `E1` and `E3`, it uses `handle_error` to deal with `E2`. In case `r` contains `E1` or `E3`, `handle_error` simply returns `r`, narrowing the error type parameter pack from `E1, E2, E3` down to `E1, E3`. If `r` contains an `E2`, `handle_error` calls the supplied lambda, which is required to return one of `E1`, `E3` (or a valid `T`).
The motivation here is to help avoid bugs in functions that handle errors that pop out of `g`: as long as the programmer deals with `E1` and `E3`, he can rest assured that no error is left unhandled.
Congratulations, we've just discovered exception specifications. The difference is that exception specifications, before being removed from {CPP}, were enforced dynamically, while this idea is equivalent to statically-enforced exception specifications, like they are in Java.
Why not statically enforce exception specifications?
"The short answer is that nobody knows how to fix exception specifications in any language, because the dynamic enforcement C++ chose has only different (not greater or fewer) problems than the static enforcement Java chose. ... 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`)."
-- Herb Sutterfootnote:[https://herbsutter.com/2007/01/24/questions-about-exception-specifications/]
While it is possible to turn every error *type* into, for example, a `std::error_code` *object*, this would eliminate the static type checking benefits we sought. Worse, the excessive coupling between the signatures of <<rationale,error-neutral>> functions (read: most functions in a program) and the error types they need to forward up the call stack interferes with the lossless propagation of errors throughout the program.
Conclusion: :: Attempts to harness the {CPP} static type checking system to help enforce correct error handling based on the types of errors a function may return require dynamic error types in generic contexts.
'''
=== Critique 2: LEAF Does Not Facilitate Mapping Between Disparate Error Codes
Most {CPP} programs use multiple C and {CPP} libraries, and each library may provide its own system of error codes. But because it is difficult to define static interfaces that can communicate arbitrary error code types, a popular idea is to map each library-specific error code to a common program-wide enum.
For example, if we have --
[cols="1a,1a",stripes=none,frame="none",grid="none",width=1%]
|====
|
[source,c++,options="nowrap"]
----
namespace lib_a
{
enum error
{
ok,
ec1,
ec2,
....
};
}
----
|
[source,c++,options="nowrap"]
----
namespace lib_b
{
enum error
{
ok,
ec1,
ec2,
....
};
}
----
|====
-- we could define:
[source,c++]
----
namespace program
{
enum error
{
ok,
lib_a_ec1,
lib_a_ec2,
....
lib_b_ec1,
lib_b_ec2,
....
};
}
----
An error-handling library could provide a conversion API that uses the {CPP} static type system to automate the mapping between the different error enums. For example, it may define a class template `result<T,E>` with value-or-error variant semantics, so that:
* `lib_a` errors are transported in `result<T,lib_a::error>`,
* `lib_b` errors are transported in `result<T,lib_b::error>`,
* then both are automatically mapped to `result<T,program::error>` once control reaches the appropriate scope.
There are several problems with this idea:
* It is prone to errors, both during the initial implementation as well as under maintenance.
* It does not compose well. For example, if `lib_a` and `lib_b` use `lib_c`, errors that originate in `lib_c` would be obfuscated by the different APIs exposed by each of the higher level libraries.
* It presumes that all errors in the program can be specified by exaclty one error code, which is false.
Consider a program that attempts to read a configuration file from three different locations: in case all of the attempts fail, it should communicate each of the failures. In theory `result<T,E>` handles this case well:
[source,c++]
----
struct attempted_location
{
std::string path;
error ec;
};
struct config_error
{
attempted_location current_dir, user_dir, app_dir;
};
result<config,config_error> read_config();
----
This looks nice, until we realize what the `config_error` type means for the automatic mapping API we wanted to define: an enum can not represent a struct, and therefore we can not assume that all error conditions can be fully specified by an enum; it must support arbitrary static types.
Conclusion: :: Transporting error objects statically in return values works great if the failure is handled in the immediate caller of the function that reports it, but most error objects must be communicated across multiple layers of function calls and APIs, which leads to excessive physical coupling between these interfaces.
NOTE: While the `leaf::<<result,result<T>>>` class template does have value-or-error semantics, it does not carry the actual error objects. Instead, they are forwarded directly to the appropriate error-handling scope and their types do not participate in function signatures.
== Alternatives to LEAF
* https://www.boost.org/doc/libs/release/libs/exception/doc/boost-exception.html[Boost Exception]
* https://ned14.github.io/outcome[Boost Outcome]
* https://github.com/pdimov/variant2[variant2 / `expected<T,E...>`]
* https://zajo.github.io/boost-noexcept[Noexcept]
Below we offer a comparison of LEAF to Boost Exception and to Boost Outcome.
[[boost_exception]]
=== Comparison to Boost Exception
While LEAF can be used without exception handling, in the use case when errors are communicated by throwing exceptions, it can be viewed as a better, more efficient alternative to Boost Exception. LEAF has the following advantages over Boost Exception:
* LEAF does not allocate memory dynamically;
* LEAF does not waste system resources communicating error objects not used by specific error handling functions;
* LEAF does not store the error objects in the exception object, and therefore it is able to augment exceptions thrown by external libraries (Boost Exception can only augment exceptions of types that derive from `boost::exception`).
The following tables outline the differences between the two libraries which should be considered when code that uses Boost Exception is refactored to use LEAF instead:
.Defining a custom type for transporting values of type T
[cols="1a,1a",options="header",stripes=none]
|====
| Boost Exception | LEAF
|
[source,c++,options="nowrap"]
----
typedef error_info<struct my_info_,T> my_info;
----
[.text-right]
https://www.boost.org/doc/libs/release/libs/exception/doc/error_info.html[`boost::error_info`]
|
[source,c++,options="nowrap"]
----
struct my_info { T value; };
----
|====
.Passing arbitrary info at the point of the throw
[cols="1a,1a",options="header",stripes=none]
|====
| Boost Exception | LEAF
|
[source,c++,options="nowrap"]
----
throw my_exception() <<
my_info(x) <<
my_info(y);
----
[.text-right]
https://www.boost.org/doc/libs/release/libs/exception/doc/exception_operator_shl.html[`operator<<`]
|
[source,c++,options="nowrap"]
----
throw leaf::exception( my_exception(),
my_info{x},
my_info{y} );
----
[.text-right]
<<exception>>
|====
.Augmenting exceptions in exception-neutral contexts
[cols="1a,1a",options="header",stripes=none]
|====
| Boost Exception | LEAF
|
[source,c++,options="nowrap"]
----
try
{
f();
}
catch( boost::exception & e )
{
e << my_info(x);
throw;
}
----
[.text-right]
https://www.boost.org/doc/libs/release/libs/exception/doc/exception.html[`boost::exception`] \| https://www.boost.org/doc/libs/release/libs/exception/doc/exception_operator_shl.html[`operator<<`]
|
[source,c++,options="nowrap"]
----
auto propagate = leaf::preload( my_info{x} );
f();
----
[.text-right]
<<preload>>
|====
.Obtaining arbitrary info at the point of the catch
[cols="1a,1a",options="header",stripes=none]
|====
| Boost Exception | LEAF
|
[source,c++,options="nowrap"]
----
try
{
f();
}
catch( my_exception & e )
{
if( T * v = get_error_info<my_info>(e) )
{
//my_info is available in e.
}
}
----
[.text-right]
https://www.boost.org/doc/libs/release/libs/exception/doc/get_error_info.html[`boost::get_error_info`]
|
[source,c++,options="nowrap"]
----
leaf::expect<my_info> exp;
try
{
f();
}
catch( my_exception & e )
{
leaf::handle_exception( exp, e,
[ ]( my_info const & x )
{
//my_info is available in e.
} );
}
----
[.text-right]
`<<expect,expect>>` \| <<handle_exception>> \| <<peek-expect>>
|====
.Error object propagation
[cols="1a,1a",options="header",stripes=none]
|====
| Boost Exception | LEAF
| All supplied https://www.boost.org/doc/libs/release/libs/exception/doc/error_info.html[`boost::error_info`] objects are allocated dynamically, stored in the https://www.boost.org/doc/libs/release/libs/exception/doc/exception.html[`boost::exception`] object, and propagated.
| User-defined error objects are stored statically in `leaf::<<expect,expect>>` objects but only if their types are used in the instantiation the `expect` class template; otherwise they are discarded.
|====
.Transporting of error objects across thread boundaries
[cols="1a,1a",options="header",stripes=none]
|====
| Boost Exception | LEAF
| https://www.boost.org/doc/libs/release/libs/exception/doc/exception_ptr.html[`boost::exception_ptr`] automatically captures https://www.boost.org/doc/libs/release/libs/exception/doc/error_info.html[`boost::error_info`] objects stored in a `boost::exception` and can transport them across thread boundaries.
| Transporting error objects across thread boundaries requires the use of <<capture_exception>>.
|====
.Printing of error objects in automatically-generated diagnostic information messages
[cols="1a,1a",options="header",stripes=none]
|====
| Boost Exception | LEAF
| `boost::error_info` types may define conversion to `std::string` by providing `to_string` overloads *or* by overloading `operator<<` for `std::ostream`.
| LEAF does not use `to_string`. Error types may define `operator<<` overloads for `std::ostream`.
|====
[WARNING]
====
The fact that Boost Exception stores all supplied `boost::error_info` objects while LEAF discards them if they aren't needed affects the completeness of the message printed by `leaf::<<diagnostic_output-expect,diagnostic_output>>` compared to the string returned by https://www.boost.org/doc/libs/release/libs/exception/doc/diagnostic_information.html[`boost::diagnostic_information`].
By default, the `leaf::<<diagnostic_output-expect,diagnostic_output>>` includes only error objects of types used in the instantiation of the `<<expect,expect>>` class template. If the user requires a complete `diagnostic_output` message, the solution is to use <<e_unexpected_diagnostic_output>> with `expect`. In this case, before unused error objects are discarded by LEAF, they are converted to string and stored in the `e_unexpected_diagnostic_output` object. Note that this allocates memory dynamically.
====
'''
=== Comparison to Boost Outcome
==== 1. Design Differences
Like LEAF, the https://ned14.github.io/outcome[Boost Outcome] library is designed to work in low latency environments. It provides two class templates, `result<>` and `outcome<>`:
* `result<T,EC,NVP>` can be used as the return type in `noexcept` functions which may fail, where `T` specifies the type of the return value in case of success, while `EC` is an "error code" type. Semantically, `result<T,EC>` is similar to `std::variant<T,EC>`. Naturally, `EC` defaults to `std::error_code`.
* `outcome<T,EC,EP,NVP>` is similar to `result<>`, but in case of failure, in addition to the "error code" type `EC` it can hold a "pointer" object of type `EP`, which defaults to `std::exception_ptr`.
NOTE: `NVP` is a policy type used to customize the behavior of `.value()` when the `result<>` or the `outcome<>` object contains an error.
The idea is to use `result<>` to communicate failures which can be fully specified by an "error code", and `outcome<>` to communicate failures that require additional information.
Another way to describe this design is that `result<>` is used when it suffices to return an error object of some static type `EC`, while `outcome<>` can also transport a polymorphic error object, using the pointer type `EP`.
NOTE: In the default configuration of `outcome<T>` the additional information -- or the additional polymorphic object -- is an exception object held by `std::exception_ptr`. This targets the use case when an exception thrown by a lower-level library function needs to be transported through some intermediate contexts that are not exception-safe, to a higher-level context able to handle it.
Similar reasoning drives the design of LEAF as well. The difference is that while both libraries recognize the need to transport "something else" in addition to an "error code", LEAF provides an efficient solution to this problem, while Outcome shifts this burden to the user.
The `leaf::result<>` template deletes both `EC` and `EP`, which decouples it from the type of the error objects that are transported in case of a failure. This enables lower-level functions to freely communicate anything and everything they "know" about the failure: error code, even multiple error codes, file names, request IDs, etc. At the same time, the higher-level error-handling functions control which of this information is needed in a specific client program and which is not. This is ideal, because:
* Authors of lower-level library functions lack context to determine which of the information that is both relevant to the error _and_ naturally available to them needs to be communicated in order for a particular client program to recover from that error;
* Authors of higher-level error-handling functions can easily and confidently make this determination, and instantiate the `<<expect,leaf::expect>>` class template with the error types they do in fact need; LEAF automatically and efficiently discards error objects of all other types that error-reporting functions attempt to communicate, saving resources.
TIP: The LEAF examples include an adaptation of the program from the https://ned14.github.io/outcome/tutorial/result/[Boost Outcome `result<>` tutorial]. You can https://github.com/zajo/leaf/blob/master/example/print_half.cpp?ts=3[view it on GitHub].
==== 2. The Interoperability Problem
The Boost Outcome documentation discusses the important problem of bringing together multiple libraries -- each using its own error reporting mechanism -- and incorporating them in a robust error handling infrastructure in a client program.
Users are advised that whenever possible they should use a common error handling system throughout their entire codebase, but because this is not practical, both the `result<>` and the `outcome<>` templates can carry user-defined "payloads".
The following analysis is from the Boost Outcome documentation:
====
If library A uses `result<T, libraryA::failure_info>`, and library B uses `result<T, libraryB::error_info>` and so on, there becomes a problem for the application writer who is bringing in these third party dependencies and tying them together into an application. As a general rule, each third party library author will not have built in explicit interoperation support for unknown other third party libraries. The problem therefore lands with the application writer.
The application writer has one of three choices:
. In the application, the form of result used is `result<T, std::variant<E1, E2, ...>>` where `E1, E2 …` are the failure types for every third party library in use in the application. This has the advantage of preserving the original information exactly, but comes with a certain amount of use inconvenience and maybe excessive coupling between high level layers and implementation detail.
. One can translate/map the third partys failure type into the applications failure type at the point of the failure exiting the third party library and entering the application. One might do this, say, with a C preprocessor macro wrapping every invocation of the third party API from the application. This approach may lose the original failure detail, or mis-map under certain circumstances if the mapping between the two systems is not one-one.
. One can type erase the third partys failure type into some application failure type, which can later be reconstituted if necessary. This is the cleanest solution with the least coupling issues and no problems with mis-mapping, but it almost certainly requires the use of `malloc` which the previous two did not.
====
The analysis above is clear and precise, but LEAF and Boost Outcome tackle the interoperability problem differently:
* The Boost Outcome design asserts that the "cleanest" solution based on type-erasure is suboptimal ("almost certainly requires the use of `malloc`pass:[]"), and instead provides a system for injecting custom converters into the `outcome::convert` namespace, used to translate between library-specific and program-wide error types.
* The LEAF design asserts that coupling the signatures of <<rationale,error-neutral>> functions with the static types of the error objects they need to forward to the caller is not practical, and instead transports error objects directly to error-handling contexts where they are stored statically (without the use of `malloc`).
[[distribution]]
== Distribution
The source code is https://github.com/zajo/leaf[available] on GitHub.
Copyright (c) 2018 Emil Dotchevski. Distributed under the http://www.boost.org/LICENSE_1_0.txt[Boost Software License, Version 1.0].
Please post questions and feedback on the Boost Developers Mailing List (LEAF is not part of Boost).
[[portability]]
== Portability
LEAF requires a {CPP}11 compiler.
See unit test matrix at https://travis-ci.org/zajo/leaf[Travis-CI]. It has also been tested with Microsoft Visual Studio 2015 and 2017 (please install the latest patches from Microsoft).
[[building]]
== Building
LEAF is a header-only library and it requires no building. It does not depend on Boost or on any other library.
The unit tests can be run with Boost Build or with https://mesonbuild.com[Meson Build]. To run the unit tests:
. If using Boost Build:
.. Clone LEAF under your `boost/libs` directory.
.. Execute:
+
[source,sh]
----
cd leaf/test
../../../b2
----
. If using Meson Build:
.. Clone LEAF into any local directory.
.. Execute:
+
[source,sh]
----
cd leaf
meson bld/debug
cd bld/debug
meson test
----
== Acknowledgements
Thanks to Peter Dimov for the countless library design discussions. Ivo Belchev, Sean Palmer, Jason King, Vinnie Falco, Glen Fernandes, Nir Friedman -- thanks for your feedback.