mirror of
https://github.com/boostorg/leaf.git
synced 2026-01-30 07:52:13 +00:00
2114 lines
73 KiB
Plaintext
2114 lines
73 KiB
Plaintext
:sourcedir: .
|
|
:last-update-label!:
|
|
:icons: font
|
|
= LEAF
|
|
Low-latency Error Augmentation Framework for C++11
|
|
:toclevels: 3
|
|
:toc: left
|
|
:toc-title:
|
|
|
|
[abstract]
|
|
== Abstract
|
|
|
|
LEAF is a non-intrusive {CPP}11 error handling library capable of transporting arbitrary error information from contexts that detect and report failures, as well as from intermediate error-neutral contexts, to scopes where the error is ultimately handled.
|
|
|
|
LEAF does not allocate dynamic memoryfootnote:[Except when transporting error info between threads, see <<capture-expect,`capture`>>.], which makes it suitable for low-latency and other performance-critical environments. It is equally applicable to programs that use exception handling and programs that do not.
|
|
|
|
[[tutorial]]
|
|
== Tutorial
|
|
|
|
We'll write a program that reads a text file in a buffer and prints it to `std::cout`, 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[print_file_result.cpp] (without exception handling)
|
|
* https://github.com/zajo/leaf/blob/master/example/print_file_eh.cpp[print_file_eh.cpp] (with exception handling)
|
|
|
|
First, let's see how to use LEAF without exception handling.
|
|
|
|
[[tutorial-noexcept]]
|
|
=== Using LEAF without Exception Handling
|
|
|
|
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 different error codes, and a simple type `e_error_code` to help LEAF tell error codes apart from other `int` values:
|
|
|
|
====
|
|
[source,c++]
|
|
----
|
|
enum
|
|
{
|
|
input_file_open_error,
|
|
input_file_size_error,
|
|
input_file_read_error,
|
|
input_eof_error,
|
|
cout_error
|
|
};
|
|
|
|
struct e_error_code { int value; };
|
|
----
|
|
====
|
|
|
|
We don't need an enumerated value that indicates success. That's because we will use the convenient 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>>`.
|
|
|
|
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( e_error_code{input_file_read_error}, e_errno{errno} ); <1>
|
|
|
|
if( n!=size )
|
|
return leaf::error( e_error_code{input_eof_error} ); <2>
|
|
|
|
return { }; <3>
|
|
}
|
|
----
|
|
<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: The `e_error_code` and `e_errno` structs are examples of types that may be passed to the `leaf::error` constructor. The requirement for such types is that they define an accessible data member `value` and `noexcept` move constructor. These types allow us to assign different error-related semantics to different valies of otherwise identical static types. +
|
|
+
|
|
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`. +
|
|
+
|
|
In this text we refer to such types as <<e-types,e-types>>, because by convention they use the `e_` prefix.
|
|
|
|
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( e_error_code{cout_error} ); <6>
|
|
|
|
return { }; <7>
|
|
}
|
|
----
|
|
<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 <<e-types>> and prepares them to become associated (automatically, at the time the returned object expires) with the first `leaf::<<error,error>>` value created thereafter. 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 `result<int>`, but now it 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( e_error_code{cout_error} );
|
|
|
|
return { };
|
|
}
|
|
----
|
|
<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;
|
|
}
|
|
|
|
leaf::expect<e_error_code, e_file_name, e_errno> exp; <1>
|
|
|
|
if( auto r = print_file(fn) )
|
|
{
|
|
return 0; <2>
|
|
}
|
|
else
|
|
{
|
|
switch( auto ec = *leaf::peek<e_error_code>(exp,r) ) <3>
|
|
{
|
|
case input_file_open_error:
|
|
{
|
|
bool matched = handle_error( exp, r, <4>
|
|
|
|
leaf::match<e_file_name,e_errno>( [ ] ( std::string const & fn, int errn )
|
|
{
|
|
if( errn==ENOENT )
|
|
std::cerr << "File not found: " << fn << std::endl;
|
|
else
|
|
std::cerr << "Failed to open " << fn << ", errno=" << errn << std::endl;
|
|
} )
|
|
|
|
);
|
|
assert(matched);
|
|
return 2;
|
|
}
|
|
|
|
case input_file_size_error:
|
|
case input_file_read_error:
|
|
case input_eof_error:
|
|
{
|
|
bool matched = handle_error( exp, r, <5>
|
|
|
|
leaf::match<e_file_name,e_errno>( [ ] ( std::string const & fn, int errn )
|
|
{
|
|
std::cerr << "Failed to access " << fn << ", errno=" << errn << std::endl;
|
|
} ),
|
|
|
|
leaf::match<e_errno>( [ ] ( int errn )
|
|
{
|
|
std::cerr << "I/O error, errno=" << errn << std::endl;
|
|
} ),
|
|
|
|
leaf::match<>( [ ]
|
|
{
|
|
std::cerr << "I/O error" << std::endl;
|
|
} )
|
|
|
|
);
|
|
assert(matched);
|
|
return 3;
|
|
}
|
|
|
|
case cout_error:
|
|
{
|
|
bool matched = handle_error( exp, r, <6>
|
|
|
|
leaf::match<e_errno>( [ ] ( int errn )
|
|
{
|
|
std::cerr << "Output error, errno=" << errn << std::endl;
|
|
} )
|
|
|
|
);
|
|
assert(matched);
|
|
return 4;
|
|
}
|
|
|
|
default:
|
|
std::cerr << "Unknown error code " << int(ec) << ", cryptic information follows." << std::endl; <7>
|
|
diagnostic_print(std::cerr,exp,r);
|
|
return 5;
|
|
}
|
|
}
|
|
}
|
|
----
|
|
<1> We expect `e_error_code`, `e_file_name` and `e_errno` objects to be associated with errors handled in this function. They will be stored inside `exp`.
|
|
<2> Success, we're done!
|
|
<3> Probe `exp` for objects associated with the error stored in `r`.
|
|
<4> `<<handle_error-expect,handle_error>>` takes a list of match objects (in this case only one), each given a set of <<e-types>>. It attempts to match each set (in order) to objects of e-types available in `exp`, which are associated with the `<<error,error>>` value stored in `r`. If no set can be matched, `handle_error` returns false. When a match is found, `handle_error` calls the corresponding lambda function, passing the `.value` of each of the e-types from the matched set.
|
|
<5> In this case `handle_error` is given 3 match sets. 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 (empty) set will always match to print a generic error message.
|
|
<6> Report failure to write to `std::cout`, print the relevant `errno`.
|
|
<7> This catch-all case helps diagnose logic errors (presumably, missing case labels in the `switch` statement).
|
|
====
|
|
|
|
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 <<e-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 <<e-types,e-type>> 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 that type.
|
|
* Using `<<handle_error-expect,handle_error>>`, available <<e-types,e-type>> 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[here]. There is also https://github.com/zajo/leaf/blob/master/example/print_file_eh.cpp[another] version of the same program that uses exception handling to report errors (see <<tutorial-eh,tutorial below>>).
|
|
|
|
|
|
'''
|
|
|
|
[[tutorial-eh]]
|
|
=== Using LEAF with 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. 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 { };
|
|
----
|
|
====
|
|
|
|
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) )
|
|
leaf::throw_exception( input_file_read_error(), e_errno{errno} ); <1>
|
|
|
|
if( n!=size )
|
|
throw input_eof_error(); <2>
|
|
}
|
|
----
|
|
<1> If `ferror` indicates an error, we throw `input_file_read_error` and, because there is a relevant `errno` code, we pass that to `<<throw_exception,throw_exception>>` _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: The `e_error_code` and `e_errno` structs are examples of types that may be passed to `<<throw_exception,throw_exception>>` (and to the `leaf::error` constructor). The requirement for such types is that they define an accessible data member `value` and `noexcept` move constructor. These types allow us to assign different error-related semantics to different valies of otherwise identical static types. +
|
|
+
|
|
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`. +
|
|
+
|
|
In this text we refer to such types as <<e-types,e-types>>, because by convention they use the `e_` prefix.
|
|
|
|
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();
|
|
}
|
|
----
|
|
<1> `std::shared_ptr<FILE> file_open( char const * file_name)` throws on error.
|
|
<2> `<<preload,preload>>` takes any number of <<e-types,e-type>> objects and prepares them to become associated (automatically, at the time the returned object expires) with the first exception thrown thereafter. The effect is that from this point on, any exception escaping `print_file` will report the file name, in addition to everything else passed to `<<throw_exception,throw_exception>>` 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 e-type object to become associated with the first exception thrown thereafter, but instead of taking the e-type 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[ ] )
|
|
{
|
|
std::cout.exceptions ( std::ostream::failbit | std::ostream::badbit ); <1>
|
|
|
|
leaf::expect<e_file_name, e_errno> exp; <2>
|
|
|
|
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 & ex )
|
|
{
|
|
handle_exception( exp, ex, <3>
|
|
|
|
leaf::match<e_file_name, e_errno>( [ ] ( std::string const & fn, int errn )
|
|
{
|
|
if( errn==ENOENT )
|
|
std::cerr << "File not found: " << fn << std::endl;
|
|
else
|
|
std::cerr << "Failed to open " << fn << ", errno=" << errn << std::endl;
|
|
} )
|
|
|
|
);
|
|
return 2;
|
|
}
|
|
catch( input_error const & ex )
|
|
{
|
|
handle_exception( exp, ex, <4>
|
|
|
|
leaf::match<e_file_name, e_errno>( [ ] ( std::string const & fn, int errn )
|
|
{
|
|
std::cerr << "Input error, " << fn << ", errno=" << errn << std::endl;
|
|
} ),
|
|
|
|
leaf::match<e_errno>( [ ] ( int errn )
|
|
{
|
|
std::cerr << "Input error, errno=" << errn << std::endl;
|
|
} ),
|
|
|
|
leaf::match<>( [ ]
|
|
{
|
|
std::cerr << "Input error" << std::endl;
|
|
} )
|
|
|
|
);
|
|
return 3;
|
|
}
|
|
catch( std::ostream::failure const & ex )
|
|
{
|
|
//Report failure to write to std::cout, print the relevant errno, if available.
|
|
handle_exception( exp, ex,
|
|
|
|
leaf::match<e_errno>( [ ] ( int errn )
|
|
{
|
|
std::cerr << "Output error, errno=" << errn << std::endl;
|
|
} )
|
|
|
|
);
|
|
return 4;
|
|
}
|
|
catch(...) <5>
|
|
{
|
|
std::cerr << "Unknown error, cryptic information follows." << std::endl;
|
|
current_exception_diagnostic_print(std::cerr,exp);
|
|
return 5;
|
|
}
|
|
}
|
|
----
|
|
<1> Configure `std::cout` to throw on error.
|
|
<2> We expect `e_file_name` and `e_errno` objects to arrive with errors handled in this function. They will be stored inside `exp`.
|
|
<3> `<<handle_exception,handle_exception>>` takes a list of match objects (in this case only one), each given a set of <<e-types>>. It attempts to match each set (in order) to objects of e-types, associated with `ex`, available in `exp`. If no set can be matched, `handle_exception` rethrows the current exception. When a match is found,
|
|
`handle_exception` calls the corresponding lambda, passing the `.value` of each of the e-types from the matched set.
|
|
<4> In this case `handle_exception` is given 3 match sets. It will first check if both `e_file_name` and `e_errno`, associated with `ex`, are avialable in `exp`; if not, it will next check if just `e_errno` is available; and if not, the last (empty) set will always match to print a generic error message.
|
|
<5> This catch-all is designed to help diagnose logic errors (main should be able to deal with any failures).
|
|
====
|
|
|
|
To summarize, when using LEAF with exception handling:
|
|
|
|
* In case a function detects a failure, it may use `<<throw_exception,throw_exception>>`, passing (in addition to the exception object) any number of <<e-types,e-type>> objects, to associate with the exception any information it has that is relevant to the failure. Alternatively it may use `<<preload,preload>>` to associate <<e-types,e-type>> objects with any exception thrown later on, including exceptions thrown by third-party code.
|
|
* In order for any e-type object passed to `<<throw_exception,throw_exception>>` 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 <<e-types,e-type>> 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[here]. There is also https://github.com/zajo/leaf/blob/master/example/print_file_result.cpp[another] version of the same program that does not use exception handling to report errors (see <<tutorial-noexcept,previous tutorial>>).
|
|
|
|
[[reference]]
|
|
== Reference
|
|
|
|
[[e-types]]
|
|
=== e-types
|
|
|
|
With LEAF, users can efficiently associate with errors or with exceptions any number of values that pertain to a failure. Each such value is enclosed in a C-`struct`, which 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; };
|
|
----
|
|
|
|
This text refers to such types as e-types because, by convention, they use the `e_` prefix. Similarly, instances of e-types are called e-objects.
|
|
|
|
The formal requirements for e-types are:
|
|
|
|
* They must define an accessible data member `value`, and
|
|
* They must be movable, and the move constructor may not throw.
|
|
|
|
LEAF itself never creates e-objects and generally only moves the e-objects it is given. Therefore, users are free to define any constructors as needed to enforce invariants for their e-types, but the typical case is to simply enclose a `value` in a C-`struct`.
|
|
|
|
Various functions in LEAF take a list of e-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( e_error_code{42}, e_input_name{n1}, e_output_name{n2} );
|
|
----
|
|
|
|
*Diagnostic Information*
|
|
|
|
LEAF will attempt to print e-objects in various `diagnostic_print` overloads it defines. It will first attempt to use `operator<<` overload that takes the enclosing `struct`. If such overload does not exist, the fallback is to attempt to use `operator<<` overload that takes the `.value`. If that also doesn't exist, LEAF is unable to print values of that particular e-type (this is permissible, not an error).
|
|
|
|
The `diagnostic_print` functions in LEAF can use the e-types defined in the snippet above by default, because `int` and `std::string` values are printable. But even with printable values, 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_print` overloads (LEAF defines `e_errno` in `<boost/leaf/common.hpp>`, together with other commonly-used e-types).
|
|
|
|
TIP: The output from `diagnostic_print` overloads is developer-friendly but not user-friendly. Therefore, `operator<<` overloads for e-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 <boost/leaf/expect.hpp>
|
|
[source,c++]
|
|
----
|
|
namespace boost { namespace leaf {
|
|
|
|
template <class... E>
|
|
class expect
|
|
{
|
|
public:
|
|
|
|
expect() noexcept;
|
|
~expect() noexcept;
|
|
|
|
template <class... M>
|
|
friend bool handle_error( expect & exp, error const & e, M && ... m ) noexcept;
|
|
|
|
friend void diagnostic_print( std::ostream & os, expect const & exp );
|
|
friend void diagnostic_print( std::ostream & os, expect const & exp, error const & e );
|
|
|
|
friend error_capture capture( expect & exp, error const & e );
|
|
|
|
void propagate() noexcept;
|
|
|
|
};
|
|
|
|
template <class P,class... E>
|
|
decltype(P::value) const * peek( expect<E...> const & exp, error const & e ) noexcept;
|
|
|
|
} }
|
|
----
|
|
====
|
|
|
|
All `expect<E...>` objects must use automatic storage duration. They are not copyable and are not movable.
|
|
|
|
The specified `E...` types must be user-defined (e.g. structs), with `noexcept` move semantics, that define accessible data member called `value`. For example:
|
|
|
|
[source,c++]
|
|
----
|
|
struct e_file_name { std::string value; };
|
|
----
|
|
|
|
In this text such types are referred to as <<e-types>>, because by convention they use the `e_` prefix. Similarly, instances of e-types are called e-objects.
|
|
|
|
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 <<e-types,e-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 e-object passed to the `leaf::error` constructor is discarded.
|
|
|
|
An e-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 e-type, this association guarantees that the returned e-object pertains to that specific `error` value.
|
|
|
|
Iff an error was successfully handled (a call to `<<handle_error-expect,handle_error>>` returned `true`), then `~expect` discards all e-objects stored in `*this`. Otherwise, each stored e-object is moved to the corresponding slot one level below the top of the stack formed by the slots of the same e-type across different `expect` objects. If that stack is empty, the e-object is discarded.
|
|
|
|
'''
|
|
|
|
[[expect::expect]]
|
|
==== Constructor
|
|
|
|
.#include <boost/leaf/expect.hpp>
|
|
[source,c++]
|
|
----
|
|
namespace boost { namespace leaf {
|
|
|
|
template <class... E>
|
|
expect<E...>::expect() noexcept;
|
|
|
|
} }
|
|
----
|
|
|
|
Description: :: Initializes an empty `expect` instance.
|
|
|
|
Postcondition: :: `<<peek-expect,peek>><P>(*this,e)` returns `0` for any `P` and any `<<error,error>>` value `e`.
|
|
|
|
'''
|
|
|
|
[[expect-dtor]]
|
|
==== Destructor
|
|
|
|
.#include <boost/leaf/expect.hpp>
|
|
[source,c++]
|
|
----
|
|
namespace boost { namespace leaf {
|
|
|
|
template <class... E>
|
|
expect<E...>::~expect() noexcept;
|
|
|
|
} }
|
|
----
|
|
|
|
Effects: :: By default, each stored <<e-types,e-object>> is moved to a corresponding slot in other existing `expect` instances according to the rules described `<<expect,here>>`, but if a call to `<<handle_error-expect,handle_error>>` for `*this` has succeeded, all objects currently stored in `*this` are discarded.
|
|
|
|
NOTE: A call to `<<propagate,propagate>>` restores the default behavior of `~expect` after a successful call to `handle_error`.
|
|
|
|
'''
|
|
|
|
[[peek-expect]]
|
|
==== `peek`
|
|
|
|
.#include <boost/leaf/expect.hpp>
|
|
[source,c++]
|
|
----
|
|
namespace boost { namespace leaf {
|
|
|
|
template <class P,class... E>
|
|
decltype(P::value) 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 `0`.
|
|
|
|
'''
|
|
|
|
[[handle_error-expect]]
|
|
==== `handle_error`
|
|
|
|
.#include <boost/leaf/expect.hpp>
|
|
[source,c++]
|
|
----
|
|
namespace boost { namespace leaf {
|
|
|
|
template <class... M>
|
|
friend bool handle_error( expect & exp, error const & e, M && ... m ) noexcept;
|
|
|
|
} }
|
|
----
|
|
|
|
Effects: :: Each of the `m...` objects must have been obtained by a separate call to the function template `<<match,match>>`, each time instantiated with a different set of <<e-types>>, and passed a different function. +
|
|
+
|
|
The call to `handle_error` attempts to match the set of e-types from each of the `m...` objects, in order, to the types of <<e-types,e-objects>>, associated with the `<<error,error>>` value `e`, currently stored in `exp`. +
|
|
+
|
|
If a complete match is found among `m...`:
|
|
+
|
|
--
|
|
* Its function is called with the `.value` members of the entire set of matching e-objects from `exp` (the function may not modify those values);
|
|
* `exp` is marked so that `<<expect_dtor,~expect>>` will destroy all of the stored e-objects (this can be undone by a later call to `<<propagate,propagate>>`);
|
|
* `handle_error` returns true.
|
|
--
|
|
+
|
|
Otherwise, `handle_error` returns false and `exp` is not modified.
|
|
|
|
Example: ::
|
|
+
|
|
[source,c++]
|
|
----
|
|
bool matched = handle_error( exp, e,
|
|
|
|
leaf::match<e_file_name,e_errno>( [ ] ( std::string const & fn, int errn )
|
|
{
|
|
std::cerr << "Failed to access " << fn << ", errno=" << errn << std::endl;
|
|
} ),
|
|
|
|
leaf::match<e_errno>( [ ] ( int 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 lambda function passed in the first call to `<<match,match>>`, then return `true`;
|
|
* Otherwise if it contains just `e_errno`, it will pass it to the lambda function passed in the second call to `match`, then return `true`;
|
|
* Otherwise, `handle_error` returns `false`.
|
|
|
|
'''
|
|
|
|
[[diagnostic_print-expect]]
|
|
==== `diagnostic_print`
|
|
|
|
.#include <boost/leaf/expect.hpp>
|
|
[source,c++]
|
|
----
|
|
namespace boost { namespace leaf {
|
|
|
|
template <class... E>
|
|
void diagnostic_print( std::ostream & os, expect<E...> const & exp );
|
|
|
|
template <class... E>
|
|
void diagnostic_print( std::ostream & os, expect<E...> const & exp, error const & e );
|
|
|
|
} }
|
|
----
|
|
|
|
Effects: :: Prints diagnostic information about the <<e-types,e-type>> objects stored in `exp`. The second overload will only print diagnostic information about e-objects stored in `exp` which are associated with the `leaf::<<error,error>>` value `e`.
|
|
|
|
NOTE: The printing of each individual e-object is done by the rules described <<e-types,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 <<e-types,e-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 is immutable and allocated on the heap.
|
|
|
|
NOTE: `error_capture` objects are useful for transporting e-objects to a different thread.
|
|
|
|
'''
|
|
|
|
[[expect::propagate]]
|
|
==== `propagate`
|
|
|
|
.#include <boost/leaf/expect.hpp>
|
|
[source,c++]
|
|
----
|
|
namespace boost { namespace leaf {
|
|
|
|
template <class... E>
|
|
void expect<E...>::propagate() noexcept;
|
|
|
|
} }
|
|
----
|
|
|
|
Effects: :: This function can be used to restore the default behavior of `<<expect-dtor,~expect>>` after a successful call to `<<handle_error-expect,handle_error>>`.
|
|
|
|
'''
|
|
|
|
[[error]]
|
|
=== Class `error`
|
|
|
|
====
|
|
.#include <boost/leaf/error.hpp>
|
|
[source,c++]
|
|
----
|
|
namespace boost { namespace leaf {
|
|
|
|
class error
|
|
{
|
|
public:
|
|
|
|
template <class... E>
|
|
explicit error( E && ... e ) noexcept:
|
|
|
|
template <class... E>
|
|
error propagate( E && ... e ) const noexcept;
|
|
|
|
friend bool operator==( error const & e1, error const & e2 ) noexcept;
|
|
friend bool operator!=( error const & e1, error const & e2 ) noexcept;
|
|
|
|
friend std::ostream & operator<<( std::ostream & os, error const & e )
|
|
|
|
};
|
|
|
|
} }
|
|
----
|
|
====
|
|
|
|
Objects of class `error` are values that identify a errors across the entire program. They can be copied, moved, assigned to, and compared to other error objects. They occupy as much memory as `unsigned int`, and are as fast.
|
|
|
|
Whenever an `e...` sequence is passed `error` functions, these objects are moved into matching storage provided by `<<expect,expect>>` instances and 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>
|
|
explicit error::error( E && ... e ) noexcept;
|
|
|
|
} }
|
|
----
|
|
|
|
Effects: :: Each of the `e...` objects is either moved into the corresponding storage provided by `expect` instances 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.
|
|
|
|
'''
|
|
|
|
[[operator_eq-error]]
|
|
==== `operator==`
|
|
|
|
.#include <boost/leaf/error.hpp>
|
|
[source,c++]
|
|
----
|
|
namespace boost { namespace leaf {
|
|
|
|
friend 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 {
|
|
|
|
friend 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 {
|
|
|
|
friend std::ostream & operator<<( std::ostream & os, error const & e )
|
|
|
|
} }
|
|
----
|
|
|
|
Effects: :: Prints an `unsigned int` value that uniquely identifies the value `e`.
|
|
|
|
'''
|
|
|
|
[[error_capture]]
|
|
=== Class `error_capture`
|
|
|
|
====
|
|
.#include <boost/leaf/error_capture.hpp>
|
|
[source,c++]
|
|
----
|
|
|
|
namespace boost { namespace leaf {
|
|
|
|
class error_capture
|
|
{
|
|
public:
|
|
|
|
error_capture() noexcept;
|
|
|
|
explicit operator bool() const noexcept;
|
|
|
|
template <class... M>
|
|
friend bool handle_error( error_capture const & ec, M && ... m ) noexcept;
|
|
|
|
friend void diagnostic_print( std::ostream & os, error_capture const & ec );
|
|
|
|
error unload() noexcept;
|
|
|
|
};
|
|
|
|
template <class P>
|
|
decltype(P::value) const * peek( error_capture const & ec ) noexcept;
|
|
|
|
} }
|
|
|
|
----
|
|
====
|
|
|
|
Objects of class `error_capture` are similar to `<<expect,expect>>` instances in that they contain <<e-types,e-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 their e-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 e-objects associated with a given `error` value from a given `expect` object.
|
|
|
|
[NOTE]
|
|
--
|
|
Typical use of `error_capture` objects is to transport e-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_captere]]
|
|
==== 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 `0` 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.
|
|
|
|
'''
|
|
|
|
[[peek-error_capture]]
|
|
==== `peek`
|
|
|
|
.#include <boost/leaf/error_capture.hpp>
|
|
[source,c++]
|
|
----
|
|
|
|
namespace boost { namespace leaf {
|
|
|
|
template <class P>
|
|
decltype(P::value) 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 `0`.
|
|
|
|
'''
|
|
|
|
[[handle_error-error_capture]]
|
|
==== `handle_error`
|
|
|
|
.#include <boost/leaf/error_capture.hpp>
|
|
[source,c++]
|
|
----
|
|
|
|
namespace boost { namespace leaf {
|
|
|
|
template <class... M>
|
|
friend bool error_capture::handle_error( error_capture const & ec, M && ... m ) noexcept;
|
|
|
|
} }
|
|
----
|
|
|
|
Effects: :: Each of the `m...` objects must have been obtained by a separate call to the function template `<<match,match>>`, each time instantiated with a different set of <<e-types>>, and passed a different function. +
|
|
+
|
|
The call to `handle_error` attempts to match the set of e-types from each of the `m...` objects, in order, to the types of <<e-types,e-objects>> currently stored in `ec`. +
|
|
+
|
|
If a complete match is found among `m...`:
|
|
+
|
|
--
|
|
* Its function is called with the `.value` members of the entire set of matching e-objects from `ec` (the function may not modify those values);
|
|
* `handle_error` returns true.
|
|
--
|
|
+
|
|
Otherwise, `handle_error` returns false.
|
|
|
|
Example: ::
|
|
+
|
|
[source,c++]
|
|
----
|
|
bool matched = handle_error( ec,
|
|
|
|
leaf::match<e_file_name,e_errno>( [ ] ( std::string const & fn, int errn )
|
|
{
|
|
std::cerr << "Failed to access " << fn << ", errno=" << errn << std::endl;
|
|
} ),
|
|
|
|
leaf::match<e_errno>( [ ] ( int 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 lambda function passed in the first call to `<<match,match>>`, then return `true`;
|
|
* Otherwise if it contains just `e_errno`, it will pass it to the lambda function passed in the second call to `match`, then return `true`;
|
|
* Otherwise, `handle_error` returns `false`.
|
|
|
|
'''
|
|
|
|
[[diagnostic_print-error_capture]]
|
|
==== `diagnostic_print`
|
|
|
|
.#include <boost/leaf/error_capture.hpp>
|
|
[source,c++]
|
|
----
|
|
|
|
namespace boost { namespace leaf {
|
|
|
|
friend void diagnostic_print( std::ostream & os, error_capture const & ec );
|
|
|
|
} }
|
|
----
|
|
|
|
Effects: :: Prints diagnostic information about the <<e-types,e-type>> objects stored in `ec`.
|
|
|
|
NOTE: The printing of each individual e-object is done by the rules described <<e-types,here>>.
|
|
|
|
'''
|
|
|
|
[[error_capture::unload]]
|
|
==== `unload`
|
|
|
|
.#include <boost/leaf/error_capture.hpp>
|
|
[source,c++]
|
|
----
|
|
|
|
namespace boost { namespace leaf {
|
|
|
|
error error_capture::unload() noexcept;
|
|
|
|
} }
|
|
----
|
|
|
|
Effects: :: The <<e-types>> stored in `*this` are moved into storage provided by `<<expect,expect>>` objects in the calling thread, as if each e-object is passed to the constructor of `<<error,error>>`.
|
|
|
|
Postcondition: :: `!(*this)`.
|
|
|
|
'''
|
|
|
|
[[result]]
|
|
=== Class template `result`
|
|
|
|
.#include <boost/leaf/result.hpp>
|
|
[source,c++]
|
|
----
|
|
namespace boost { namespace leaf {
|
|
|
|
template <class T>
|
|
result
|
|
{
|
|
public:
|
|
|
|
result() noexcept;
|
|
result( T const & v );
|
|
result( T && v ) noexcept;
|
|
result( leaf::error const & e ) noexcept;
|
|
result( leaf::error_capture const & ec ) noexcept;
|
|
|
|
explicit operator bool() const noexcept;
|
|
|
|
T const & value() const;
|
|
T & value();
|
|
T const & operator*() const;
|
|
T & operator*();
|
|
|
|
template <class... E>
|
|
leaf::error error( E && ... e ) noexcept;
|
|
|
|
template <class... M,class... E>
|
|
friend bool handle_error( expect<E...> & exp, result & r, M && ... m ) noexcept;
|
|
|
|
template <class... E>
|
|
friend void diagnostic_print( std::ostream & os, expect<E...> const & exp, result const & r );
|
|
|
|
template <class... E>
|
|
friend result capture( expect<E...> & exp, result const & r );
|
|
|
|
};
|
|
|
|
template <class P,class... E,class T>
|
|
decltype(P::value) const * peek( expect<E...> const &, result<T> const & ) noexcept;
|
|
|
|
struct bad_result: std::exception { };
|
|
|
|
} }
|
|
----
|
|
|
|
'''
|
|
|
|
[[result::result]]
|
|
==== Constructors
|
|
|
|
.#include <boost/leaf/result.hpp>
|
|
[source,c++]
|
|
----
|
|
namespace boost { namespace leaf {
|
|
|
|
result::result() noexcept;
|
|
result::result( T const & v );
|
|
result::result( T && v ) noexcept;
|
|
result::result( leaf::error const & e ) noexcept;
|
|
result::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`.
|
|
|
|
NOTE: 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 {
|
|
|
|
result::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 {
|
|
|
|
T const & result::value() const;
|
|
T & result::value();
|
|
T const & result::operator*() const;
|
|
T & result::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... E>
|
|
leaf::error result::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 <<e-types,e-type>> objects `e...`.
|
|
|
|
'''
|
|
|
|
[[peek-result]]
|
|
==== `peek`
|
|
|
|
.#include <boost/leaf/result.hpp>
|
|
[source,c++]
|
|
----
|
|
namespace boost { namespace leaf {
|
|
|
|
template <class P,class... E,class T>
|
|
decltype(P::value) const * peek( expect<E...> const & exp, result<T> const & r ) noexcept;
|
|
|
|
} }
|
|
----
|
|
|
|
Preconditions: :: `!(*this)`.
|
|
|
|
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)`.
|
|
|
|
'''
|
|
|
|
[[handle_error-result]]
|
|
==== `handle_error`
|
|
|
|
.#include <boost/leaf/result.hpp>
|
|
[source,c++]
|
|
----
|
|
namespace boost { namespace leaf {
|
|
|
|
template <class... M,class... E>
|
|
friend bool result::handle_error( expect<E...> & exp, result & r, M && ... m ) noexcept;
|
|
|
|
} }
|
|
----
|
|
|
|
Preconditions: :: `!(*this)`.
|
|
|
|
Returns: ::
|
|
* If `r` stores an `<<error_capture,error_capture>>` object `cap`, returns `<<handle_error-error_capture,handle_error>><E...>(cap,m...)`.
|
|
* If `r` stores a `leaf::<<error,error>>` value `err`, returns `<<handle_error-expect,handle_error>><E...>(exp,err,m...)`.
|
|
|
|
'''
|
|
|
|
[[diagnostic_print-result]]
|
|
==== `diagnostic_print`
|
|
|
|
.#include <boost/leaf/result.hpp>
|
|
[source,c++]
|
|
----
|
|
namespace boost { namespace leaf {
|
|
|
|
template <class... E>
|
|
friend void result::diagnostic_print( std::ostream & os, expect<E...> const & exp, result const & r );
|
|
|
|
} }
|
|
----
|
|
|
|
Preconditions: :: `!(*this)`.
|
|
|
|
Returns: ::
|
|
* If `r` stores an `<<error_capture,error_capture>>` object `cap`, returns `<<diagnostic_print-error_capture,diagnostic_print>>(os,cap)`.
|
|
* If `r` stores a `leaf::<<error,error>>` value `err`, returns `<<diagnostic_print-expect,diagnostic_print>>(os,exp,err)`.
|
|
|
|
'''
|
|
|
|
[[capture-result]]
|
|
==== `capture`
|
|
|
|
.#include <boost/leaf/result.hpp>
|
|
[source,c++]
|
|
----
|
|
namespace boost { namespace leaf {
|
|
|
|
template <class... E>
|
|
friend result result::capture( expect<E...> & exp, result 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>`>>.
|
|
|
|
'''
|
|
|
|
[[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.
|
|
|
|
'''
|
|
|
|
[[preload]]
|
|
=== `preload`
|
|
|
|
[source,c++]
|
|
.#include <boost/leaf/error.hpp>
|
|
----
|
|
namespace boost { namespace leaf {
|
|
|
|
template <class... E>
|
|
<<unspecified-type>> preload( E && ... e ) noexcept;
|
|
|
|
} }
|
|
----
|
|
|
|
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 it was created, the stored `e...` objects are propagated and become associated with the _first_ `leaf::error` value created after `preload` was called;
|
|
* Otherwise, the stored `e...` objects are discarded.
|
|
|
|
'''
|
|
|
|
[[defer]]
|
|
=== `defer`
|
|
|
|
[source,c++]
|
|
.#include <boost/leaf/error.hpp>
|
|
----
|
|
namespace boost { namespace leaf {
|
|
|
|
template <class... F>
|
|
<<unspecified-type>> defer( F && ... f ) noexcept;
|
|
|
|
} }
|
|
----
|
|
|
|
Requirements: :: All `f...` objects must be functions that do not throw exceptions, take no arguments and return an <<e-types,e-type>> object.
|
|
|
|
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 it was created, each of the stored `f...` is called, and the returned <<e-types,e-value>> is propagated and becomes associated with the _first_ `leaf::error` value created after `defer` was called;
|
|
* Otherwise, the stored `f...` objects are discarded.
|
|
|
|
'''
|
|
|
|
[[eh]]
|
|
=== Exception Handling Functions
|
|
|
|
====
|
|
[source,c++]
|
|
.#include <boost/leaf/exception.hpp>
|
|
----
|
|
#define LEAF_THROW(e) ::boost::leaf::throw_exception(e,LEAF_SOURCE_LOCATION)
|
|
|
|
namespace boost { namespace leaf {
|
|
|
|
template <class... E,class Ex>
|
|
[[noreturn]] void throw_exception( Ex && ex, E && ... e );
|
|
|
|
template <class... E,class Ex>
|
|
[[noreturn]] void throw_exception( Ex && ex, error const & err, E && ... e );
|
|
|
|
error get_error( std::exception const & ex ) noexcept;
|
|
|
|
template <class P,class... E>
|
|
decltype(P::value) const * peek( expect<E...> const & exp, std::exception const & ex ) noexcept;
|
|
|
|
template <class... M,class... E>
|
|
void handle_exception( expect<E...> & exp, std::exception const & ex, M && ... m );
|
|
|
|
template <class... E>
|
|
void diagnostic_print( std::ostream & os, expect<E...> const & exp, std::exception const & ex );
|
|
|
|
} }
|
|
----
|
|
.#include <boost/leaf/exception_capture.hpp>
|
|
----
|
|
namespace boost { namespace leaf {
|
|
|
|
template <class... E,class F>
|
|
<<unspecified-type>> capture_exception( F && f ) noexcept;
|
|
|
|
template <class Future>
|
|
decltype(std::declval<Future>().get()) get( Future && f );
|
|
|
|
} }
|
|
----
|
|
====
|
|
|
|
The two headers `<boost/leaf/exception.hpp>` and `<boost/leaf/exception_capture>` define functions designed for programs that use exception handling.
|
|
|
|
'''
|
|
|
|
[[throw_exception]]
|
|
==== `throw_exception`
|
|
|
|
[source,c++]
|
|
.#include <boost/leaf/exception.hpp>
|
|
----
|
|
#define LEAF_THROW(e) ::boost::leaf::throw_exception(e,LEAF_SOURCE_LOCATION)
|
|
|
|
namespace boost { namespace leaf {
|
|
|
|
template <class... E,class Ex>
|
|
[[noreturn]] void throw_exception( Ex && ex, E && ... e );
|
|
|
|
template <class... E,class Ex>
|
|
[[noreturn]] void throw_exception( Ex && ex, error const & err, E && ... e );
|
|
|
|
} }
|
|
----
|
|
|
|
Requirements: :: `Ex` must derive from `std::exception`.
|
|
|
|
Effects: ::
|
|
* The first overload throws an exception object of unspecified type which derives publicly from both `Ex` and `leaf::<<error,error>>`, its `Ex` sub-object initialized by moing from `ex`, its `error` sub-object initialized by `<<error::error,error>>(std::forward<E>(e...))`;
|
|
|
|
* The second overload throws an exception object of unspecified type which derives publicly from both `Ex` and `leaf::<<error,error>>`, its `Ex` sub-object initialized by moing from `ex`, its `error` sub-object initialized by `err.<<error::propagate,propagate>>(std::forward<E>(e...))`.
|
|
|
|
NOTE: The thrown exception object can be caught as `Ex &` or as `leaf::<<error,error>>`.
|
|
|
|
TIP: Use `LEAF_THROW` to automatically pass the current source location in an instance of `<<common,e_source_location>>` object to `throw_exception`.
|
|
|
|
'''
|
|
|
|
[[get_error]]
|
|
==== `get_error`
|
|
|
|
[source,c++]
|
|
.#include <boost/leaf/exception.hpp>
|
|
----
|
|
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.
|
|
+
|
|
NOTE: A successful call to `<<handle_exception,handle_exception>>` breaks this association.
|
|
|
|
'''
|
|
|
|
[[peek-exception]]
|
|
==== `peek`
|
|
|
|
[source,c++]
|
|
.#include <boost/leaf/exception.hpp>
|
|
----
|
|
namespace boost { namespace leaf {
|
|
|
|
template <class P,class... E>
|
|
decltype(P::value) 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`
|
|
|
|
[source,c++]
|
|
.#include <boost/leaf/exception.hpp>
|
|
----
|
|
namespace boost { namespace leaf {
|
|
|
|
template <class... M,class... E>
|
|
void handle_exception( expect<E...> & exp, std::exception const & ex, M && ... m );
|
|
|
|
} }
|
|
----
|
|
|
|
Effects: :: Equivalent to: `if( !<<handle_error-expect,handle_error>>( exp, <<get_error,get_error>>(ex), std::forward<M>(m)...) ) throw;`
|
|
|
|
NOTE: 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_print-exception]]
|
|
==== `diagnostic_print`
|
|
|
|
[source,c++]
|
|
.#include <boost/leaf/exception.hpp>
|
|
----
|
|
namespace boost { namespace leaf {
|
|
|
|
template <class... E>
|
|
void diagnostic_print( std::ostream & os, expect<E...> const & exp, std::exception const & ex );
|
|
|
|
} }
|
|
----
|
|
|
|
Effects: :: Equivalent to: `<<diagnostic_print-expect,diagnostic_print>>( os, exp, <<get_error,get_error>>(ex) );`
|
|
|
|
'''
|
|
|
|
[[capture_exception]]
|
|
==== `capture_exception`
|
|
|
|
[source,c++]
|
|
.#include <boost/leaf/exception_capture.hpp>
|
|
----
|
|
namespace boost { namespace leaf {
|
|
|
|
template <class... E,class F>
|
|
<<unspecified-type>> capture_exception( F && f ) noexcept;
|
|
|
|
} }
|
|
----
|
|
|
|
NOTE: For an, example see <<technique_transport-exceptions,Transporting Errors between Threads using Exception Handling>>.
|
|
|
|
'''
|
|
|
|
Requirements: :: `F` must be a function type.
|
|
|
|
Returns: :: A function of unspecified type which wraps `f` and, when called, forwards all its arguments to `f`, capturing the specified `E...` <<e-types>> in case it throws.
|
|
|
|
NOTE: The `capture_exception` function is designed for use with `<<get,get>>`, to effectively transport <<e-types,e-objects>> across thread boundaries.
|
|
|
|
'''
|
|
|
|
[[get]]
|
|
==== `get`
|
|
|
|
[source,c++]
|
|
.#include <boost/leaf/exception_capture.hpp>
|
|
----
|
|
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 <<e-types,e-objects>> captured in the worker thread are transported to the calling thread.
|
|
|
|
NOTE: To store and to access the transported <<e-types,e-objects>>, the calling thread must provide a suitable `<<expect,expect>>` object.
|
|
|
|
'''
|
|
|
|
[[common]]
|
|
=== Common e-types
|
|
|
|
====
|
|
.#include <boost/leaf/common.hpp>
|
|
[source,c++]
|
|
----
|
|
#define LEAF_SOURCE_LOCATION ::boost::leaf::e_source_location{::boost::leaf::e_source_location::loc(__FILE__,__LINE__,__FUNCTION__)}
|
|
|
|
namespace boost { namespace leaf {
|
|
|
|
struct e_api_function { char const * value; };
|
|
struct e_file_name { std::string value; };
|
|
|
|
struct e_errno
|
|
{
|
|
int value;
|
|
friend std::ostream & operator<<( std::ostream & os, e_errno const & err );
|
|
};
|
|
|
|
e_errno get_errno() noexcept
|
|
{
|
|
return e_errno { errno };
|
|
}
|
|
|
|
struct e_source_location
|
|
{
|
|
struct loc
|
|
{
|
|
char const * const file;
|
|
int const line;
|
|
char const * const function;
|
|
loc( char const * file, int line, char const * function ) noexcept;
|
|
};
|
|
loc value;
|
|
|
|
friend std::ostream & operator<<( std::ostream & os, e_source_location const & x );
|
|
};
|
|
|
|
} }
|
|
----
|
|
====
|
|
|
|
This header defines some common <<e-types,e-type>> objects which can be used directly:
|
|
|
|
- The `e_api_function` type is designed to capture the name of the function for which a failure is reported. For example, if you're reporting an error detected by `fread`, you could use `leaf::e_api_function { "fread" }`.
|
|
+
|
|
WARNING: The passed value is stored as a C string, so you should only pass string literals for `value`.
|
|
- When a file operation fails, you could use `e_file_name` to store the name of the file.
|
|
- `e_errno` is suitable to capture `errno`. `e_errno` objects use `strerror` to convert the `errno` code to a friendlier error message when `<<diagnostic_print-expect,diagnostic_print>>` is invoked.
|
|
- The `LEAF_SOURCE_LOCATION` macro captures `pass:[__FILE__]`, `pass:[__LINE__]` and `pass:[__FUNCTION__]` into a `e_source_location` object. When `<<diagnostic_print-expect,diagnostic_print>>` is invoked, all three items are printed.
|
|
|
|
[[techniques]]
|
|
== Programming Techniques
|
|
|
|
[[technique_preload]]
|
|
=== Preloading Errors
|
|
|
|
Consider the following exception type:
|
|
|
|
[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 `<<throw_exception,throw_exception>>`:
|
|
|
|
[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) )
|
|
leaf::throw_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
|
|
leaf::throw_exception( file_open_error() );
|
|
}
|
|
----
|
|
|
|
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,
|
|
leaf::match<e_file_name,e_errno>( [ ]( std::string const & fn, int errn )
|
|
{
|
|
std::cerr << "File name: " << fn << ", errno=" << errn << "\n";
|
|
} )
|
|
);
|
|
}
|
|
----
|
|
|
|
NOTE: This technique works exacly 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) )
|
|
leaf::throw_exception( file_read_error(), e_errno{errno} );
|
|
....
|
|
}
|
|
----
|
|
|
|
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) )
|
|
leaf::throw_exception( file_read_error(), e_errno{errno} );
|
|
|
|
size_t nr2=fread(buf2,1,count2,f);
|
|
if( ferror(f) )
|
|
leaf::throw_exception( file_read_error(), e_errno{errno} );
|
|
|
|
size_t nr3=fread(buf3,1,count3,f);
|
|
if( ferror(f) )
|
|
leaf::throw_exception( file_read_error(), e_errno{errno} );
|
|
....
|
|
}
|
|
----
|
|
|
|
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) )
|
|
leaf::throw_exception( file_read_error() );
|
|
|
|
size_t nr2=fread(buf2,1,count2,f);
|
|
if( ferror(f) )
|
|
leaf::throw_exception( file_read_error() );
|
|
|
|
size_t nr3=fread(buf3,1,count3,f);
|
|
if( ferror(f) )
|
|
leaf::throw_exception( file_read_error() );
|
|
....
|
|
}
|
|
----
|
|
|
|
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_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 <<e-types,e-type>> objects with any exception or error reported by a function.
|
|
|
|
But what if we need to include some e-object conditionally? When using excption 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;
|
|
}
|
|
----
|
|
|
|
The reason we need to use `<<get_error,get_error>>` is that not all exceptions have a `leaf::<<error,error>>` value associated with them. If the exception we're augmenting was thrown using `<<throw_exception,throw_exception>>`, it includes a `leaf::error` sub-object, and in this case `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 was not thrown by `throw_exception` (and therefore doesn't derive from `leaf::error`), `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 `preload` 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 <<e-types,e-type>> used to communicate `do_work` failures:
|
|
|
|
[source,c++]
|
|
----
|
|
struct e_do_work_error { int value; };
|
|
----
|
|
|
|
We're now ready to define the `do_work` 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
|
|
{
|
|
auto propagate = leaf::preload( e_do_work_error{-42} ); <3>
|
|
return luaL_error(L,"do_work_error"); <4>
|
|
}
|
|
}
|
|
----
|
|
<1> "Sometimes" `do_work` fails.
|
|
<2> In case of success, push the result on the Lua stack, return back to Lua.
|
|
<3> In case of failure, use `<<preload,preload>>` to get a `e_do_work_error` ready to be associated with the next `leaf::<<error,error>>` value created.
|
|
<4> Tell Lua that `do_work` failed. It will abort the Lua program and pop back into the {CPP} code which called the Lua interpreter.
|
|
|
|
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;
|
|
}
|
|
}
|
|
----
|
|
<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 `e_do_work` object preloaded 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<e_do_work_error,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,
|
|
|
|
leaf::match<e_do_work_error>( [ ]( int v ) <3>
|
|
{
|
|
std::cout << "Got e_do_work_error, value = " << v << "!\n";
|
|
} ),
|
|
|
|
leaf::match<e_lua_pcall_error,e_lua_error_message>( [ ]( int err, std::string const & msg ) <4>
|
|
{
|
|
std::cout << "Got e_lua_pcall_error, Lua error code = " << err << ", " << msg << "\n";
|
|
} )
|
|
);
|
|
assert(matched);
|
|
}
|
|
return 0;
|
|
}
|
|
----
|
|
<1> Tell LEAF what <<e-types,e-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[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[lua_callback_eh.cpp].
|
|
--
|
|
|
|
'''
|
|
|
|
[[technique_transport]]
|
|
=== Transporting Errors between Threads
|
|
|
|
With LEAF, <<e-types,e-objects>> use automatic storage duration, stored inside `<<expect,expect>>` instances. When using concurrency, we need a mechanism to detach e-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 for programs that use exception handling.
|
|
|
|
[[technique_transport-result]]
|
|
==== Using `result<T>`
|
|
|
|
Without exceptions, transporting <<e-types,e-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 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());
|
|
} );
|
|
}
|
|
----
|
|
|
|
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,
|
|
|
|
leaf::match<E1,E2>( [ ] ( .... )
|
|
{
|
|
//Deal with E1, E2
|
|
} ),
|
|
|
|
leaf::match<E3>( [ ] ( .... )
|
|
{
|
|
//Deal with E3
|
|
} )
|
|
|
|
);
|
|
}
|
|
----
|
|
|
|
NOTE: Follow this link to see a complete example program: https://github.com/zajo/leaf/blob/master/example/capture_result.cpp[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 <<e-types,e-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 <<e-types,e-objects>> 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();
|
|
} ) );
|
|
}
|
|
----
|
|
|
|
Later, instead of using `std::future::get`, we use `leaf::<<get-capture_exception,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,
|
|
|
|
leaf::match<E1,E2>( [ ] ( .... )
|
|
{
|
|
//Deal with E1, E2
|
|
} ),
|
|
|
|
leaf::match<E3>( [ ] ( .... )
|
|
{
|
|
//Deal with E3
|
|
} )
|
|
|
|
);
|
|
}
|
|
----
|
|
|
|
NOTE: Follow this link to see a complete example program: https://github.com/zajo/leaf/blob/master/example/capture_eh.cpp[capture_eh.cpp].
|
|
|
|
|
|
'''
|
|
|
|
== Design Rationale
|
|
|
|
The first observation driving the LEAF design is that unless a specific type of info (e.g. a file name) is used at the time an error is being handled, there is no need for it to be reported. On the other hand, if the error handling context can use or requires some info, it would not be burdened by having to explicitly declare that need. The end result of this reasoning is `<<expect,expect>>`.
|
|
|
|
The second observation is that ideally, like any other communication mechanism, it makes sense to formally define an interface for the error info that can be used by the error handling code. In terms of {CPP} exception handling, it would be nice to be able to say something like:
|
|
|
|
[source,c++]
|
|
----
|
|
try {
|
|
|
|
process_file();
|
|
|
|
} 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 info available. Unfortunately this syntax is not possible and, even if it were, not all programs use exceptions to handle errors. The result of this train of thought is `<<handle_error-expect,handle_error>>`/`<<handle_exception,handle_exception>>`.
|
|
|
|
Last but not least, there is certain redundancy and repetition in error-neutral contexts that simply forward errors to their caller. What is the point in receiving some error info from a lower level function (e.g. a file name), when at this point we can't do anything with it, except forward it to our caller, until we reach a scope that can actually make use of the data? Even with move semantics, why bother move such data one level at a time, from one stack location to another immediately above, only to move it again when we `return` again?
|
|
|
|
It is more correct for such information to be passed from a context where it is available, _directly to the exact stack location where it would be accessed by the error handling code_. This is another reason why the storage for <<e-types,e-objects>> is provided by `<<expect,expect>>` instances, which all use automatic storage duration.
|
|
|
|
[[distribution]]
|
|
== Distribution
|
|
|
|
Copyright (c) 2018 Emil Dotchevski.
|
|
|
|
LEAF is distributed under the http://www.boost.org/LICENSE_1_0.txt[Boost Software License, Version 1.0].
|
|
|
|
The source code is available in https://github.com/zajo/leaf[this GitHub repository].
|
|
|
|
NOTE: LEAF is not part of Boost. Please post questions and feedback on the Boost Developers Mailing List.
|
|
|
|
[[building]]
|
|
== Building
|
|
|
|
LEAF is a header-only library and it requires no building. The unit tests use Boost Build, but the library itself has no dependency on Boost or any other library.
|
|
|
|
[[portability]]
|
|
== Portability
|
|
|
|
LEAF requires a {CPP}11 compiler.
|
|
|
|
See unit test matrix at https://travis-ci.org/zajo/leaf[Travis-CI]. It has also been tested with Microsoft Visual Studio 2015 and 2017.
|