diff --git a/doc/leaf.adoc b/doc/leaf.adoc index 90b397d4..9c84a3ba 100644 --- a/doc/leaf.adoc +++ b/doc/leaf.adoc @@ -75,9 +75,9 @@ What is a failure? It is simply the inability of a function to return a valid re A typical design is to return a variant type, e.g. `result`. Internally, such variant types must store a discriminant (in this case a boolean) to indicate whether the object holds a `T` or an `E`. -The design of LEAF is informed by the observation that the immediate caller must have access to the discriminant in order to determine the availability of a valid `T`, but otherwise it rarely needs to access the `E`. The error object is only needed once an error handling scope is reached. +The design of LEAF is informed by the observation that the immediate caller must have access to the discriminant in order to determine the availability of a valid `T`, but otherwise it is rare that it needs to access any error objects. They are only needed once an error handling scope is reached. -Therefore what would have been a `result` becomes `result`, which stores the discriminant and (optionally) a `T`, while the `E` is communicated directly to the error handling scope where it is needed. +Therefore what would have been a `result` becomes `result`, which stores the discriminant and (optionally) a `T`, while error objects are delivered directly to the error handling scope where it is needed. The benefit of this decomposition is that `result` becomes extremely lightweight, as it is not coupled with error types; further, error objects are communicated in constant time (independent of the call stack depth). Even very large objects are handled efficiently without dynamic memory allocation. @@ -209,11 +209,11 @@ leaf::result r = leaf::try_handle_some( [.text-right] <> | <> | <> -The first lambda passed to `try_handle_some` is executed first; it attempts to produce a `result`, but it may fail. +First, `try_handle_some` executes the first function passed to it; it attempts to produce a `result`, but it may fail. -The second lambda is an error handler: it will be called iff the first lambda fails and an error object of type `err1` was communicated to LEAF. That object is stored on the stack, local to the `try_handle_some` function (LEAF knows to allocate this storage because we gave it an error handler that takes an `err1`). Error handlers passed to `leaf::try_handle_some` can return a valid `leaf::result` but are allowed to fail. +The second lambda is an error handler: it will be called iff the first lambda fails, producing an error object of type `err1`. That object is stored on the stack, local to the `try_handle_some` function (LEAF knows to allocate this storage because we gave it an error handler that takes an `err1`). Error handlers passed to `leaf::try_handle_some` can return a valid `leaf::result` but are allowed to fail. -It is possible for an error handler to specify that it can only deal with some values of a given error type: +It is possible for an error handler to declare that it can only handle some specific values of a given error type: [source,c++] ---- @@ -246,9 +246,9 @@ LEAF considers the provided error handlers in order, and calls the first one for * Otherwise the second error handler will be called iff an error object of type `err1` is available, regardless of its value. -* Otherwise `leaf::try_handle_some` fails. +* Otherwise `leaf::try_handle_some` is unable to handle the error. -It is possible for an error handler to conditionally leave the current failure unhandled: +It is possible for an error handler to conditionally leave the failure unhandled: [source,c++] ---- @@ -390,7 +390,7 @@ leaf::result r = leaf::try_handle_some( []( io_error ec, e_file_name fn ) -> leaf::result { - // Handle I/O errors when a file name is available. + // Handle I/O errors when a file name is also available. }, []( io_error ec ) -> leaf::result @@ -422,7 +422,7 @@ leaf::result r = leaf::try_handle_some( []( io_error ec, e_file_name const * fn ) -> leaf::result { if( fn ) - .... // Handle I/O errors when a file name is available. + .... // Handle I/O errors when a file name is also available. else .... // Handle I/O errors when no file name is available. } ); @@ -469,7 +469,7 @@ leaf::result process_file( FILE * f ) [.text-right] <> | <> -Because `process_file` does not handle errors, it remains neutral to failures, except to attach the `current_line` if something goes wrong. The object returned by `on_error` holds a copy of the `current_line` wrapped in `struct e_line`. If `parse_line` succeeds, the `e_line` object is simply discarded; if it fails, the `e_line` object will be automatically "attached" to the failure. +Because `process_file` does not handle errors, it remains neutral to failures, except to attach the `current_line` if something goes wrong. The object returned by `on_error` holds a copy of `current_line` wrapped in `struct e_line`. If `parse_line` succeeds, the `e_line` object is simply discarded; if it fails, the `e_line` object will be automatically "attached" to the failure. Such failures can then be handled like so: @@ -646,7 +646,7 @@ If instead we want to use the legacy convention of throwing different types to i leaf::throw_exception(std::runtime_error("Error!"), err1::e1, err2::e2); ---- -In this case the returned object will be of type that derives from `std::runtime_error`, rather than from `std::exception`. +In this case the thrown exception object will be of type that derives from `std::runtime_error`, rather than from `std::exception`. Finally, `leaf::on_error` "just works" as well. Here is our `process_file` function rewritten to work with exceptions, rather than return a `leaf::result` (see <>): @@ -701,7 +701,7 @@ leaf::result f() [.text-right] <> | <> -Later we simply call `leaf::try_handle_some` passing an error handler for each type: +Later we simply call `leaf::try_handle_some`, passing an error handler for each type: [source,c++] ---- @@ -756,7 +756,7 @@ lib2::result bar(); int g( int a, int b ); -lib3::result f() +lib3::result f() // Note: return type is not leaf::result { auto a = foo(); if( !a ) @@ -806,7 +806,7 @@ Ideally, when an error is detected, a program using LEAF would always call <>. [[tutorial-loading]] === Loading of Error Objects -To load an error object is to move it into an active <> object, usually contained within a <>, a <> or a <> scope in the calling thread, where it becomes uniquely associated with a specific <> -- or discarded if storage is not available. +Recall that error objects communicated to LEAF are stored on the stack, local to the `try_handle_same`, `try_handle_all` or `try_catch` function used to handle errors. To _load_ an error object means to move it into such storage, if available. Various LEAF functions take a list of error objects to load. As an example, if a function `copy_file` that takes the name of the input file and the name of the output file as its arguments detects a failure, it could communicate an error code `ec`, plus the two relevant file names using <>: @@ -864,7 +864,7 @@ Besides error objects, `load` can take function arguments: * If we pass a function that takes no arguments, it is invoked, and the returned error object is loaded. + -Consider that if we pass to `load` an error object that is not needed by any error handler, it will be discarded. If the object is expensive to compute, it would be better if the computation can be skipped as well. Passing a function with no arguments to `load` is an excellent way to achieve this behavior: +Consider that if we pass to `load` an error object that is not used by an error handler, it will be discarded. If the object is expensive to compute, it would be better if the computation is only performed in case of an error. Passing a function with no arguments to `load` is an excellent way to achieve this behavior: + [source,c++] ---- @@ -893,7 +893,7 @@ leaf::result operation( char const * file_name ) noexcept <> | <> + <1> Success! Use `r.value()`. -<2> `try_something` has failed; `compute_info` will only be called if an error handler exists which takes a `info` argument. +<2> `try_something` has failed; `compute_info` will only be called if an error handler exists in the call stack which takes a `info` argument. + * If we pass a function that takes a single argument of type `E &`, LEAF calls the function with the object of type `E` currently loaded in an active `context`, associated with the error. If no such object is available, a new one is default-initialized and then passed to the function. + @@ -960,8 +960,8 @@ leaf::result parse_file( char const * file_name ) noexcept [.text-right] <> | <> | <> -<1> `parse_info` parses `f`, communicating errors using `leaf::result`. -<2> Using `on_error` ensures that the file name is included with any error reported out of `parse_file`. When the `load` object expires, if an error is being reported, the passed `e_file_name` value will be automatically associated with it. +<1> `parse_info` communicates errors using `leaf::result`. +<2> `on_error` ensures that the file name is included with any error reported out of `parse_file`. When the `load` object expires, if an error is being reported, the passed `e_file_name` value will be automatically associated with it. TIP: `on_error` -- like `new_error` -- can be passed any number of arguments. @@ -977,7 +977,7 @@ For example, if we want to use `on_error` to capture `errno`, we can't just pass ---- void read_file(FILE * f) { - auto load = leaf::on_error([]{ return e_errno{errno}; }); + auto load = leaf::on_error([]{ return leaf::e_errno{errno}; }); .... size_t nr1=fread(buf1,1,count1,f); @@ -997,7 +997,7 @@ void read_file(FILE * f) { Above, if an exception is thrown, LEAF will invoke the function passed to `on_error` and associate the returned `e_errno` object with the exception. -Finally, if `on_error` is passed a function that takes a single error object by mutable reference, the behavior is similar to how such functions are used by `load`; see <>. +Finally, if `on_error` is passed a function that takes a single error object by mutable reference, the behavior is similar to how such functions are handled by `load`; see <>. ''' @@ -1026,31 +1026,27 @@ return leaf::try_handle_some( [] { - return f(); // returns leaf::result + return f(); // Returns leaf::result }, - []( my_error e ) - { <1> + []( my_error e ) // handle my_error objects + { switch(e) { case my_error::e1: - ....; <2> + ....; // Handle e1 error values break; case my_error::e2: case my_error::e3: - ....; <3> + ....; // Handle e2 and e3 error values break; default: - ....; <4> + ....; // Handle bad my_error values break; } ); ---- -<1> This handler will be selected if we've got a `my_error` object. -<2> Handle `e1` errors. -<3> Handle `e2` and `e3` errors. -<4> Handle bad `my_error` values. -If `my_error` object is available, LEAF will call our error handler. If not, the failure will be forwarded to our caller. +If a `my_error` object is available, LEAF will call our error handler. If not, the failure will be forwarded to the caller. This can be rewritten using the <> predicate to organize the different cases in different error handlers. The following is equivalent: @@ -1064,25 +1060,22 @@ return leaf::try_handle_some( }, []( leaf::match m ) - { <1> + { assert(m.matched == my_error::e1); ....; }, []( leaf::match m ) - { <2> + { assert(m.matched == my_error::e2 || m.matched == my_error::e3); ....; }, []( my_error e ) - { <3> + { ....; } ); ---- -<1> We've got a `my_error` object that compares equal to `e1`. -<2> We`ve got a `my_error` object that compares equal to either `e2` or `e3`. -<3> Handle bad `my_error` values. The first argument to the `match` template generally specifies the type `E` of the error object `e` that must be available for the error handler to be considered at all. Typically, the rest of the arguments are values. The error handler is dropped if `e` does not compare equal to any of them. @@ -1095,7 +1088,7 @@ In particular, `match` works great with `std::error_code`. The following handler } ---- -This, however, requires {CPP}17 or newer, because it is impossible to infer the type of the error enum (in this case, `std::errc`) from the specified type `std::error_code`, and {CPP}11 does not allow `auto` template arguments. LEAF provides the following workaround, compatible with {CPP}11: +This, however, requires {CPP}17 or newer. LEAF provides the following workaround, compatible with {CPP}11: [source,c++] ---- @@ -1104,7 +1097,7 @@ This, however, requires {CPP}17 or newer, because it is impossible to infer the } ---- -In addition, it is possible to select a handler based on `std::error_category`. The following handler will match any `std::error_code` of the `std::generic_category` (requires {CPP}17 or newer): +It is also possible to select a handler based on `std::error_category`. The following handler will match any `std::error_code` of the `std::generic_category` (requires {CPP}17 or newer): [source,c++] ---- @@ -1123,7 +1116,7 @@ The following predicates are available: * `<>`: Similar to `match`, but checks whether the caught `std::exception` object can be `dynamic_cast` to any of the `Ex` types. * <> is a special predicate that takes any other predicate `Pred` and requires that an error object of type `E` is available and that `Pred` evaluates to `false`. For example, `if_not>` requires that an object `e` of type `E` is available, and that it does not compare equal to any of the specified `V...`. -Finally, the predicate system is easily extensible, see <>. +The predicate system is easily extensible, see <>. NOTE: See also <>. @@ -1162,7 +1155,7 @@ leaf::try_handle_all( [.text-right] <> | <> -Looks pretty simple, but what if we need to attempt a different set of operations yet use the same handlers? We could repeat the same thing with a different function passed as `TryBlock` for `try_handle_all`: +If we need to attempt a different set of operations yet use the same handlers, we could repeat the same thing with a different function passed as the `TryBlock` for `try_handle_all`: [source,c++] ---- @@ -1189,7 +1182,7 @@ leaf::try_handle_all( }); ---- -That works, but it may be better to bind the error handlers in a `std::tuple`: +That works, but it is also possible to bind the error handlers in a `std::tuple`: [source,c++] ---- @@ -1245,14 +1238,14 @@ Error handling functions accept a `std::tuple` of error handlers in place of any ''' [[tutorial-async]] -=== Transporting Error Objects Between Threads +=== Transporting Errors Between Threads -Like exceptions, error objects are local to a thread. When using concurrency, sometimes we need to collect error objects in one thread, then use them to handle the error in another thread. +Like exceptions, LEAF error objects are local to a thread. When using concurrency, sometimes we need to collect error objects in one thread, then use them to handle errors in another thread. -LEAF offers two interfaces for this purpose, one using `result`, and another using exception handling. +LEAF supports this functionality with or without exception handling. In both cases error objects are captured and transported in a `leaf::<>` object. [[tutorial-async_result]] -==== Using `result` +==== Transporting Errors Between Threads Without Exception Handling Let's assume we have a `task` that we want to launch asynchronously, which produces a `task_result` but could also fail: @@ -1261,37 +1254,7 @@ Let's assume we have a `task` that we want to launch asynchronously, which produ leaf::result task(); ---- -Because the task will run asynchronously, in case of a failure we need it to capture the relevant error objects but not handle errors. To this end, in the main thread we bind our error handlers in a `std::tuple`, which we will later use to handle errors from each completed asynchronous task (see <>): - -[source,c++] ----- -auto error_handlers = std::make_tuple( - - [](E1 e1, E2 e2) - { - //Deal with E1, E2 - .... - return { }; - }, - - [](E3 e3) - { - //Deal with E3 - .... - return { }; - } ); ----- - -Why did we start with this step? Because we need to create a <> object to collect the error objects we need. We could just instantiate the `context` template with `E1`, `E2` and `E3`, but that would be prone to errors, since it could get out of sync with the handlers we use. Thankfully LEAF can deduce the types we need automatically, we just need to show it our `error_handlers`: - -[source,c++] ----- -std::shared_ptr ctx = leaf::make_shared_context(error_handlers); ----- - -The `polymorphic_context` type is an abstract base class that has the same members as any instance of the `context` class template, allowing us to erase its exact type. In this case what we're holding in `ctx` is a `context`, where `E1`, `E2` and `E3` were deduced automatically from the `error_handlers` tuple we passed to `make_shared_context`. - -We're now ready to launch our asynchronous task: +Because the task will run asynchronously, in case of a failure we need to capture any produced error objects but not handle errors. We do this by invoking `try_handle_some`, passing an error handler that takes an argument of type <>: [source,c++] ---- @@ -1301,16 +1264,23 @@ std::future> launch_task() noexcept std::launch::async, [&] { - std::shared_ptr ctx = leaf::make_shared_context(error_handlers); - return leaf::capture(ctx, &task); + return leaf::try_handle_some( + [&]() -> leaf::result + { + return task(); + }, + []( leaf::dynamic_capture const & cap ) -> leaf::result + { + return cap; + }); } ); } ---- [.text-right] -<> | <> | <> +<> | <> -That's it! Later when we `get` the `std::future`, we can process the returned `result` in a call to <>, using the `error_handlers` tuple we created earlier: +The `dynamic_capture` object passed to the error handler can hold multiple error objects of different types, at the cost of dynamic memory allocations. Returning it from the error handler converts it to `leaf::result`, which now holds the captured error objects. The `result` object can then be stashed away or moved to another thread, and later passed to an error-handling function to unload its content and handle errors: [source,c++] ---- @@ -1326,30 +1296,6 @@ return leaf::try_handle_some( return { } }, - error_handlers ); ----- - -[.text-right] -<> | <> | <> - -NOTE: Follow this link to see a complete example program: https://github.com/boostorg/leaf/blob/master/example/capture_in_result.cpp?ts=4[capture_in_result.cpp]. - -[[tutorial-async_eh]] -==== Using Exception Handling - -Let's assume we have an asynchronous `task` which produces a `task_result` but could also throw: - -[source,c++] ----- -task_result task(); ----- - -Just like we saw in <>, first we will bind our error handlers in a `std::tuple`: - -[source,c++] ----- -auto handle_errors = std::make_tuple( - [](E1 e1, E2 e2) { //Deal with E1, E2 @@ -1365,7 +1311,22 @@ auto handle_errors = std::make_tuple( } ); ---- -Launching the task looks the same as before, except that we don't use `result`: +[.text-right] +<> | <> | <> + +NOTE: Follow this link to see a complete example program: https://github.com/boostorg/leaf/blob/master/example/dynamic_capture_result.cpp?ts=4[dynamic_capture_result.cpp]. + +[[tutorial-async_eh]] +==== Transporting Errors Between Threads With Exception Handling + +Let's assume we have an asynchronous `task` which produces a `task_result` but could also throw: + +[source,c++] +---- +task_result task(); +---- + +We use `try_catch` together with `dynamic_capture` to capture all error objects and the `std::current_exception()` in a `result`: [source,c++] ---- @@ -1375,37 +1336,55 @@ std::future launch_task() std::launch::async, [&] { - std::shared_ptr ctx = leaf::make_shared_context(&handle_error); - return leaf::capture(ctx, &task); + return leaf::try_catch( + [&]() -> leaf::result + { + return task(); + }, + []( leaf::dynamic_capture const & cap ) -> leaf::result + { + return cap; + }); } ); } ---- [.text-right] -<> | <> +<> -That's it! Later when we `get` the `std::future`, we can process the returned `task_result` in a call to <>, using the `error_handlers` we saved earlier, as if it was generated locally: +To handle errors after waiting on the future, we use `try_catch` as usual: [source,c++] ---- -//std::future fut; +//std::future> fut; fut.wait(); return leaf::try_catch( [&] { - task_result r = fut.get(); // Throws on error + leaf::result r = fut.get(); + task_result v = r.value(); // throws on error //Success! }, - error_handlers ); + [](E1 e1, E2 e2) + { + //Deal with E1, E2 + .... + }, + + [](E3 e3) + { + //Deal with E3 + .... + } ); ---- [.text-right] -<> +<> | <> -NOTE: Follow this link to see a complete example program: https://github.com/boostorg/leaf/blob/master/example/capture_in_exception.cpp?ts=4[capture_in_exception.cpp]. +NOTE: Follow this link to see a complete example program: https://github.com/boostorg/leaf/blob/master/example/dynamic_capture_eh.cpp?ts=4[dynamic_capture_eh.cpp]. ''' @@ -1444,7 +1423,7 @@ It will get called if the value of the `error_code` enum communicated with the f But what if later we add support for detecting and reporting a new type of input error, e.g. `permissions_error`? It is easy to add that to our `error_code` enum; but now our input error handler won't recognize this new input error -- and we have a bug. -If we can use exceptions, the situation is better because exception types can be organized in a hierarchy in order to classify failures: +Using exceptions is an improvement because exception types can be organized in a hierarchy in order to classify failures: [source,c++] ---- @@ -1667,7 +1646,7 @@ NOTE: The complete program illustrating this technique is available https://gith [[tutorial-on_error_in_c_callbacks]] === Using `error_monitor` to Report Arbitrary Errors from 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. +Communicating information pertaining to a failure detected in a C callback is tricky, because C callbacks are limited to a specific function 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. @@ -1858,7 +1837,7 @@ leaf::try_handle_all( ---- <1> We handle all failures that occur in this try block. <2> One or more error handlers that should handle all possible failures. -<3> The "catch all" error handler is required by `try_handle_all`. It will be called if LEAF is unable to use another error handler. +<3> This "catch all" error handler is required by `try_handle_all`. It will be called if LEAF is unable to use another error handler. The `verbose_diagnostic_info` output for the snippet above tells us that we got an `error_code` with value `1` (`write_error`), and an object of type `e_file_name` with `"file.txt"` stored in its `.value`: @@ -2276,7 +2255,8 @@ namespace boost { namespace leaf { template error_id load( Item && ... item ) const noexcept; - friend std::ostream & operator<<( std::ostream & os, error_id x ); + template + friend std::ostream & operator<<( std::basic_ostream &, error_id ); }; bool is_error_id( std::error_code const & ec ) noexcept; @@ -2288,29 +2268,6 @@ namespace boost { namespace leaf { ////////////////////////////////////////// - class polymorphic_context - { - protected: - - polymorphic_context() noexcept = default; - ~polymorphic_context() noexcept = default; - - public: - - virtual void activate() noexcept = 0; - virtual void deactivate() noexcept = 0; - virtual bool is_active() const noexcept = 0; - - virtual void propagate( error_id ) noexcept = 0; - - virtual void print( std::ostream & ) const = 0; - - template - friend std::ostream & operator<<( std::basic_ostream & os, polymorphic_context const & ctx ); - }; - - ////////////////////////////////////////// - template class context_activator { @@ -2359,7 +2316,7 @@ namespace boost { namespace leaf { ---- [.text-right] -Reference: <> | <> | <> | <> | <> | <> | <> | <> | <> | <> | <> | <> +Reference: <> | <> | <> | <> | <> | <> | <> | <> | <> | <> | <> ==== [[common.hpp]] @@ -2383,7 +2340,9 @@ namespace boost { namespace leaf { { int value; explicit e_errno(int value=errno); - friend std::ostream & operator<<(std::ostream &, e_errno const &); + + template + friend std::ostream & operator<<( std::basic_ostream &, e_errno const &); }; namespace windows @@ -2396,7 +2355,9 @@ namespace boost { namespace leaf { #if BOOST_LEAF_CFG_WIN32 e_LastError(); - friend std::ostream & operator<<(std::ostream &, e_LastError const &); + + template + friend std::ostream & operator<<( std::basic_ostream &, e_LastError const &); #endif }; } @@ -2431,9 +2392,6 @@ namespace boost { namespace leaf { template result( U && u, <> ); - result( error_id err ) noexcept; - result( std::shared_ptr && ctx ) noexcept; - template result( Enum e, typename std::enable_if::value, Enum>::type * = 0 ) noexcept; @@ -2466,9 +2424,11 @@ namespace boost { namespace leaf { template error_id load( Item && ... item ) noexcept; + + void unload(); template - friend std::ostream & operator<<( std::basic_ostream & os, result const & r ); + friend std::ostream & operator<<( std::basic_ostream &, result const & ); }; template <> @@ -2481,7 +2441,6 @@ namespace boost { namespace leaf { result() noexcept; result( error_id err ) noexcept; - result( std::shared_ptr && ctx ) noexcept; template result( Enum e, typename std::enable_if::value, Enum>::type * = 0 ) noexcept; @@ -2506,9 +2465,11 @@ namespace boost { namespace leaf { template error_id load( Item && ... item ) noexcept; + + void unload(); template - friend std::ostream & operator<<( std::basic_ostream & os, result const & r ); + friend std::ostream & operator<<( std::basic_ostream &, result const &); }; struct bad_result: std::exception { }; @@ -2594,25 +2555,6 @@ Reference: <> | <> <2> Only enabled if !std::is_base_of::value. ==== -==== `capture.hpp` - -==== -[source,c++] -.#include ----- -namespace boost { namespace leaf { - - template - decltype(std::declval()(std::forward(std::declval())...)) - capture(std::shared_ptr && ctx, F && f, A... a); - -} } ----- - -[.text-right] -Reference: <> | <> -==== - ''' [[tutorial-handling]] @@ -2644,12 +2586,12 @@ namespace boost { namespace leaf { void deactivate() noexcept; bool is_active() const noexcept; - void propagate( error_id ) noexcept; + void unload( error_id ) noexcept; void print( std::ostream & os ) const; template - friend std::ostream & operator<<( std::basic_ostream & os, context const & ctx ); + friend std::ostream & operator<<( std::basic_ostream &, context const & ); template R handle_error( R &, H && ... ) const; @@ -2666,17 +2608,11 @@ namespace boost { namespace leaf { template BOOST_LEAF_CONSTEXPR context_type_from_handlers make_context( H && ... ) noexcept; - template - context_ptr make_shared_context() noexcept; - - template - context_ptr make_shared_context( H && ... ) noexcept; - } } ---- [.text-right] -Reference: <> | <> | <> | <> +Reference: <> | <> | <> ==== [[handle_errors.hpp]] @@ -2702,6 +2638,22 @@ namespace boost { namespace leaf { ////////////////////////////////////////// +#if BOOST_LEAF_CFG_CAPTURE + class dynamic_capture + { + //No public constructors + + bool empty() const noexcept; + int size() const noexcept; + + template + operator result() const noexcept; + + template + friend std::ostream & operator<<( std::basic_ostream &, dynamic_capture const & ); + }; +#endif + class error_info { //No public constructors @@ -2713,28 +2665,31 @@ namespace boost { namespace leaf { bool exception_caught() const noexcept; std::exception const * exception() const noexcept; - friend std::ostream & operator<<( std::ostream & os, error_info const & x ); + template + friend std::ostream & operator<<( std::basic_ostream &, error_info const & ); }; class diagnostic_info: public error_info { //No public constructors - friend std::ostream & operator<<( std::ostream & os, diagnostic_info const & x ); + template + friend std::ostream & operator<<( std::basic_ostream &, diagnostic_info const & ); }; class verbose_diagnostic_info: public error_info { //No public constructors - friend std::ostream & operator<<( std::ostream & os, diagnostic_info const & x ); + template + friend std::ostream & operator<<( std::basic_ostream &, diagnostic_info const & ); }; } } ---- [.text-right] -Reference: <> | <> | <> | <> | <> | <> +Reference: <> | <> | <> | <> | <> | <> | <> ==== [[handle_errors.hpp]] @@ -2905,46 +2860,6 @@ leaf::context ctx; ''' -[[capture]] -=== `capture` - -.#include -[source,c++] ----- -namespace boost { namespace leaf { - - template - decltype(std::declval()(std::forward(std::declval())...)) - capture(std::shared_ptr && ctx, F && f, A... a); - -} } ----- - -[.text-right] -<> - -This function can be used to capture error objects stored in a <> in one thread and transport them to a different thread for handling, either in a `<>` object or in an exception. - -Returns: :: The same type returned by `F`. - -Effects: :: Uses an internal <> to <> `*ctx`, then invokes `std::forward(f)(std::forward(a)...)`. Then: -+ --- -* If the returned value `r` is not a `result` type (see <>), it is forwarded to the caller. -* Otherwise: -** If `!r`, the return value of `capture` is initialized with `ctx`; -+ -NOTE: An object of type `leaf::<>` can be initialized with a `std::shared_ptr`. -+ -** otherwise, it is initialized with `r`. --- -+ -In case `f` throws, `capture` catches the exception in a `std::exception_ptr`, and throws a different exception of unspecified type that transports both the `std::exception_ptr` as well as `ctx`. This exception type is recognized by <>, which automatically unpacks the original exception and propagates the contents of `*ctx` (presumably, in a different thread). - -TIP: See also <> from the Tutorial. - -''' - [[context_type_from_handlers]] === `context_type_from_handlers` @@ -3121,36 +3036,6 @@ auto ctx = leaf::make_context( <1> ''' -[[make_shared_context]] -=== `make_shared_context` - -.#include -[source,c++] ----- -namespace boost { namespace leaf { - - template - context_ptr make_shared_context() noexcept - { - return std::make_shared>>(); - } - - template - context_ptr make_shared_context( H && ... ) noexcept - { - return std::make_shared>>(); - } - -} } ----- - -[.text-right] -<> - -TIP: See also <> from the tutorial. - -''' - [[new_error]] === `new_error` @@ -3427,6 +3312,7 @@ Requires: :: ** must return a type that can be used to initialize an object of the type `R`; in case R is a `result` (that is, in case of success it does not communicate a value), handlers that return `void` are permitted. If such a handler is selected, the `try_handle_some` return value is initialized by `{}`; ** may take any error objects, by value, by (`const`) reference, or as pointer (to `const`); ** may take arguments, by value, of any predicate type: <>, <>, <>, <>, <>, or of any user-defined predicate type `Pred` for which `<>::value` is `true`; +** may take a <> argument by `const &`; ** may take an <> argument by `const &`; ** may take a <> argument by `const &`; ** may take a <> argument by `const &`. @@ -3520,6 +3406,31 @@ try_handle_some( + NOTE: See also: <>. + +* If `a~i~` is of type `dynamic_capture const &`, `try_handle_some` is always able to produce it. It captures error objects which would otherwise have been discarded. ++ +.Example: +[source,c++] +---- +.... +try_handle_some( + + [] + { + return f(); // returns leaf::result + }, + + [](leaf::dynamic_capture const & cap) -> leaf::result <1> + { + return cap; <2> + } ); +---- ++ +<1> This handler matches any error. +<2> In this typical use, the contents of `cap` are captured in the returned `leaf::result`. ++ +[.text-right] +<> | <> ++ * If `a~i~` is of type `error_info const &`, `try_handle_some` is always able to produce it. + .Example: @@ -3633,7 +3544,7 @@ namespace boost { namespace leaf { void deactivate() noexcept; bool is_active() const noexcept; - void propagate( error_id ) noexcept; + void unload( error_id ) noexcept; void print( std::ostream & os ) const; @@ -3648,7 +3559,7 @@ namespace boost { namespace leaf { } } ---- [.text-right] -<> | <> | <> | <> | <> | <> | <> | <> +<> | <> | <> | <> | <> | <> | <> | <> The `context` class template provides storage for each of the specified `E...` types. Typically, `context` objects are not used directly; they're created internally when the <>, <> or <> functions are invoked, instantiated with types that are automatically deduced from the types of the arguments of the passed handlers. @@ -3658,7 +3569,7 @@ Even in that case it is recommended that users do not instantiate the `context` To be able to load up error objects in a `context` object, it must be activated. Activating a `context` object `ctx` binds it to the calling thread, setting thread-local pointers of the stored `E...` types to point to the corresponding storage within `ctx`. It is possible, even likely, to have more than one active `context` in any given thread. In this case, activation/deactivation must happen in a LIFO manner. For this reason, it is best to use a <>, which relies on RAII to activate and deactivate a `context`. -When a `context` is deactivated, it detaches from the calling thread, restoring the thread-local pointers to their pre-`activate` values. Typically, at this point the stored error objects, if any, are either discarded (by default) or moved to corresponding storage in other `context` objects active in the calling thread (if available), by calling <>. +When a `context` is deactivated, it detaches from the calling thread, restoring the thread-local pointers to their pre-`activate` values. Typically, at this point the stored error objects, if any, are either discarded (by default) or moved to corresponding storage in other `context` objects active in the calling thread (if available), by calling <>. While error handling typically uses <>, <> or <>, it is also possible to handle errors by calling the member function <>. It takes an <>, and attempts to select an error handler based on the error objects stored in `*this`, associated with the passed `error_id`. @@ -3796,7 +3707,7 @@ namespace boost { namespace leaf { void context::print( std::ostream & os ) const; template - friend std::ostream & context::operator<<( std::basic_ostream & os, context const & ctx ) + friend std::ostream & context::operator<<( std::basic_ostream &, context const & ) { ctx.print(os); return os; @@ -3809,8 +3720,8 @@ Effects: :: Prints all error objects currently stored in `*this`, together with ''' -[[context::propagate]] -==== `propagate` +[[context::unload]] +==== `unload` .#include [source,c++] @@ -3818,7 +3729,7 @@ Effects: :: Prints all error objects currently stored in `*this`, together with namespace boost { namespace leaf { template - void context::propagate( error_id id ) noexcept; + void context::unload( error_id id ) noexcept; } } ---- @@ -3880,7 +3791,8 @@ namespace boost { namespace leaf { { //Constructors unspecified - friend std::ostream & operator<<( std::ostream & os, diagnostic_info const & x ); + template + friend std::ostream & operator<<( std::basic_ostream &, diagnostic_info const & ); }; } } @@ -3902,84 +3814,181 @@ The behavior of `diagnostic_info` (and <>) is affected ''' -[[error_id]] -=== `error_id` +[[dynamic_capture]] +=== `dynamic_capture` -.#include +.#include [source,c++] ---- +#if BOOST_LEAF_CFG_CAPTURE + namespace boost { namespace leaf { - class error_id + class dynamic_capture { + //Constructors unspecified + public: - error_id() noexcept; + bool empty() const noexcept; + + int size() const noexcept; - template - result( Enum e, typename std::enable_if::value, Enum>::type * = 0 ) noexcept; + template + operator result() const noexcept; - error_id( std::error_code const & ec ) noexcept; + template + friend std::ostream & operator<<( std::basic_ostream &, dynamic_capture const & ); + }; - int value() const noexcept; - explicit operator bool() const noexcept; +} } - std::error_code to_error_code() const noexcept; +#endif +---- - friend bool operator==( error_id a, error_id b ) noexcept; - friend bool operator!=( error_id a, error_id b ) noexcept; - friend bool operator<( error_id a, error_id b ) noexcept; +By default, error objects reported via LEAF which are not taken as arguments by any active handler are discarted. If an active handler exists which takes an argument of type `dynamic_capture const &`, that object collects the error objects that would otherwise have been discarded, storing them on the heap. - template - error_id load( Item && ... item ) const noexcept; +The message printed by `operator<<` includes diagnostic information about the captured error objects, except if `BOOST_LEAF_CFG_DIAGNOSTICS=0` is defined, in which case it does not. - friend std::ostream & operator<<( std::ostream & os, error_id x ); - }; +When an object of type `dynamic_capture const &` is returned from a handler that produces a `leaf::<>`, its contents are captured (together with the `std::current_exception`) by the returned `result` object, which can later be used with <> to handle the captured error objects. See <>. - bool is_error_id( std::error_code const & ec ) noexcept; +Under `BOOST_LEAF_CFG_CAPTURE=0`, `dynamic_capture` is unavailable. - template - error_id new_error( E && ... e ) noexcept; +See also: :: <>. - error_id current_error() noexcept; +''' -} } +[[dynamic_capture::empty]] +==== `empty` + +.#include +[source,c++] ---- +namespace boost { namespace leaf { -[.text-right] -<> | <> | <> | <> | <> | <> | <> | <> | <> + bool dynamic_capture::empty() const noexcept; -Values of type `error_id` identify a specific occurrence of a failure across the entire program. They can be copied, moved, assigned to, and compared to other `error_id` objects. They're as efficient as an `int`. +} } +---- + +Returns: :: `true` if the `dynamic_capture` objects is empty, `false` otherwise. ''' -[[error_id::error_id]] -==== Constructors +[[dynamic_capture::size]] +==== `size` -.#include +.#include [source,c++] ---- namespace boost { namespace leaf { - error_id::error_id() noexcept = default; - - template - error_id::error_id( Enum e, typename std::enable_if::value, Enum>::type * = 0 ) noexcept; - - error_id::error_id( std::error_code const & ec ) noexcept; + int dynamic_capture::size() const noexcept; } } ---- -A default-initialized `error_id` object does not represent a specific failure. It compares equal to any other default-initialized `error_id` object. All other `error_id` objects identify a specific occurrence of a failure. - -CAUTION: When using an object of type `error_id` to initialize a `result` object, it will be initialized in error state, even when passing a default-initialized `error_id` value. - -Converting an `error_id` object to `std::error_code` uses an unspecified `std::error_category` which LEAF recognizes. This allows an `error_id` to be transported through interfaces that work with `std::error_code`. The `std::error_code` constructor allows the original `error_id` to be restored. +Returns: :: The number of error objects captured by `*this`. -TIP: To check if a given `std::error_code` is actually carrying an `error_id`, use <>. +''' -Typically, users create new `error_id` objects by invoking <>. The constructor that takes `std::error_code`, and the one that takes a type `Enum` for which `std::is_error_code_enum::value` is `true`, have the following effects: +[[dynamic_capture::operator_result]] +==== `operator result` + +.#include +[source,c++] +---- +namespace boost { namespace leaf { + + template + operator result() const noexcept; + +} } +---- + +Effects: :: Implicit conversion to `leaf::<>`. The contents of `*this`, together with the `std::current_exception()` (if available) are moved and stored in the returned `result` object, which can be stashed away to be used at a later time with <> / <> / <> to handle the captured errors. + +''' + +[[error_id]] +=== `error_id` + +.#include +[source,c++] +---- +namespace boost { namespace leaf { + + class error_id + { + public: + + error_id() noexcept; + + template + result( Enum e, typename std::enable_if::value, Enum>::type * = 0 ) noexcept; + + error_id( std::error_code const & ec ) noexcept; + + int value() const noexcept; + explicit operator bool() const noexcept; + + std::error_code to_error_code() const noexcept; + + friend bool operator==( error_id a, error_id b ) noexcept; + friend bool operator!=( error_id a, error_id b ) noexcept; + friend bool operator<( error_id a, error_id b ) noexcept; + + template + error_id load( Item && ... item ) const noexcept; + + template + friend std::ostream & operator<<( std::basic_ostream &, error_id ); + }; + + bool is_error_id( std::error_code const & ec ) noexcept; + + template + error_id new_error( E && ... e ) noexcept; + + error_id current_error() noexcept; + +} } +---- + +[.text-right] +<> | <> | <> | <> | <> | <> | <> | <> | <> + +Values of type `error_id` identify a specific occurrence of a failure across the entire program. They can be copied, moved, assigned to, and compared to other `error_id` objects. They're as efficient as an `int`. + +''' + +[[error_id::error_id]] +==== Constructors + +.#include +[source,c++] +---- +namespace boost { namespace leaf { + + error_id::error_id() noexcept = default; + + template + error_id::error_id( Enum e, typename std::enable_if::value, Enum>::type * = 0 ) noexcept; + + error_id::error_id( std::error_code const & ec ) noexcept; + +} } +---- + +A default-initialized `error_id` object does not represent a specific failure. It compares equal to any other default-initialized `error_id` object. All other `error_id` objects identify a specific occurrence of a failure. + +CAUTION: When using an object of type `error_id` to initialize a `result` object, it will be initialized in error state, even when passing a default-initialized `error_id` value. + +Converting an `error_id` object to `std::error_code` uses an unspecified `std::error_category` which LEAF recognizes. This allows an `error_id` to be transported through interfaces that work with `std::error_code`. The `std::error_code` constructor allows the original `error_id` to be restored. + +TIP: To check if a given `std::error_code` is actually carrying an `error_id`, use <>. + +Typically, users create new `error_id` objects by invoking <>. The constructor that takes `std::error_code`, and the one that takes a type `Enum` for which `std::is_error_code_enum::value` is `true`, have the following effects: * If `ec.value()` is `0`, the effect is the same as using the default constructor. * Otherwise, if `<>(ec)` is `true`, the original `error_id` value is used to initialize `*this`; @@ -4220,7 +4229,9 @@ namespace boost { namespace leaf { { int value; explicit e_errno(int value=errno); - friend std::ostream & operator<<( std::ostream & os, e_errno const & err ); + + template + friend std::ostream & operator<<( std::basic_ostream &, e_errno const & ); }; } } @@ -4267,7 +4278,9 @@ namespace boost { namespace leaf { #if BOOST_LEAF_CFG_WIN32 e_LastError(); - friend std::ostream & operator<<(std::ostream &, e_LastError const &); + + template + friend std::ostream & operator<<( std::basic_ostream &, e_LastError const & ); #endif }; } @@ -4293,7 +4306,8 @@ namespace boost { namespace leaf { int line; char const * function; - friend std::ostream & operator<<( std::ostream & os, e_source_location const & x ); + template + friend std::ostream & operator<<( std::basic_ostream &, e_source_location const & ); }; } } @@ -4339,7 +4353,8 @@ namespace boost { namespace leaf { bool exception_caught() const noexcept; std::exception const * exception() const noexcept; - friend std::ostream & operator<<( std::ostream & os, error_info const & x ); + template + friend std::ostream & operator<<( std::basic_ostream &, error_info const & ); }; } } @@ -4359,46 +4374,6 @@ The `operator<<` overload prints diagnostic information about each error object ''' -[[polymorphic_context]] -=== `polymorphic_context` - -.#include -[source,c++] ----- -namespace boost { namespace leaf { - - class polymorphic_context - { - protected: - - polymorphic_context() noexcept; - ~polymorphic_context() noexcept; - - public: - - virtual void activate() noexcept = 0; - virtual void deactivate() noexcept = 0; - virtual bool is_active() const noexcept = 0; - - virtual void propagate( error_id ) noexcept = 0; - - virtual void print( std::ostream & ) const = 0; - - template - friend std::ostream & operator<<( std::basic_ostream & os, polymorphic_context const & ctx ) - { - ctx.print(os); - return os; - } - }; - -} } ----- - -The `polymorphic_context` class is an abstract base type which can be used to erase the type of the exact instantiation of the <> class template used. See <>. - -''' - [[result]] === `result` @@ -4422,7 +4397,6 @@ namespace boost { namespace leaf { result( U &&, <> ); result( error_id err ) noexcept; - result( std::shared_ptr && ctx ) noexcept; template result( Enum e, typename std::enable_if::value, Enum>::type * = 0 ) noexcept; @@ -4468,7 +4442,6 @@ namespace boost { namespace leaf { result() noexcept; result( error_id err ) noexcept; - result( std::shared_ptr && ctx ) noexcept; template result( Enum e, typename std::enable_if::value, Enum>::type * = 0 ) noexcept; @@ -4509,9 +4482,9 @@ The `result` type can be returned by functions which produce a value of type Requires: :: `T` must be movable, and its move constructor may not throw. Invariant: :: A `result` object is in one of three states: -* Value state, in which case it contains an object of type `T`, and <>/<>/<> can be used to access the contained value. +* Value state, in which case it contains an object of type `T`, and <> / <> / <> can be used to access the contained value. * Error state, in which case it contains an error ID, and calling <> throws `leaf::bad_result`. -* Error capture state, which is the same as the Error state, but in addition to the error ID, it holds a `std::shared_ptr<<>>`. +* Dynamic capture state, which is the same as the Error state, but in addition to the error ID, it holds a list of dynamically captured error objects; see <>. `result` objects are nothrow-moveable but are not copyable. @@ -4548,9 +4521,6 @@ namespace boost { namespace leaf { template result::result( std::error_code const & ec ) noexcept; - template - result::result( std::shared_ptr && ctx ) noexcept; - template result::result( result && ) noexcept; @@ -4579,7 +4549,7 @@ CAUTION: Initializing a `result` with a default-initialized `error_id` object + ** a `std::error_code` object. ** an object of type `Enum` for which `std::is_error_code_enum::value` is `true`. -* To get a `result` in <>, initialize it with a `std::shared_ptr<<>>` (which can be obtained by calling e.g. <>). +* To get a `result` in <>, write an error handler that takes a <> object and returns a `result` object, and return the capture object from the error handler. -- + When a `result` object is initialized with a `std::error_code` object, it is used to initialize an `error_id` object, then the behavior is the same as if initialized with `error_id`. @@ -4743,6 +4713,14 @@ namespace boost { namespace leaf { } } ---- +Effects: + +* If `*this` is in <>, returns a reference to the stored value. +* If `*this` is in <>, the captured error objects are unloaded, and: +** If `*this` contains a captured exception object `ex`, the behavior is equivalent to `<>(ex)`. +** Otherwise, the behavior is equivalent to `<>(bad_result{})`. +* If `*this` is in any other state, the behavior is equivalent to `<>(bad_result{})`. + ''' [[result::value_type]] @@ -4813,7 +4791,8 @@ namespace boost { namespace leaf { { //Constructors unspecified - friend std::ostream & operator<<( std::ostream & os, verbose_diagnostic_info const & x ); + template + friend std::ostream & operator<<( std::basic_ostream &, verbose_diagnostic_info const & ); }; } } @@ -5385,7 +5364,7 @@ The error handling functionality provided by <> and <` type R, you must specialize the `is_result_type` template so that `is_result_type::value` evaluates to `true`. -Naturally, the provided `leaf::<>` class template satisfies these requirements. In addition, it allows error objects to be transported across thread boundaries, using a `std::shared_ptr<<>>`. +Naturally, the provided `leaf::<>` class template satisfies these requirements. In addition, it allows error objects to be transported across thread boundaries, using a <>. [[macros]] == Reference: Macros @@ -5552,10 +5531,129 @@ Effects: :: `BOOST_LEAF_THROW_EXCEPTION(e...)` is equivalent to `leaf::<>(e...)`, except the current source location is automatically passed, in a `<>` object (in addition to all `e...` objects). -[[rationale]] -== Design +[[configuration]] +== Configuration + +The following configuration macros are recognized: + +* `BOOST_LEAF_CFG_DIAGNOSTICS`: Defining this macro as `0` stubs out both <> and <> (if the macro is left undefined, LEAF defines it as `1`). + +* `BOOST_LEAF_CFG_STD_SYSTEM_ERROR`: Defining this macro as `0` disables the `std::error_code` / `std::error_condition` integration. In this case LEAF does not `#include `, which may be too heavy for embedded platforms (if the macro is left undefined, LEAF defines it as `1`). + +* `BOOST_LEAF_CFG_STD_STRING`: Defining this macro as `0` disables all use of `std::string` (this requires `BOOST_LEAF_CFG_DIAGNOSTICS=0` as well). In this case LEAF does not `#include ` which may be too heavy for embedded platforms (if the macro is left undefined, LEAF defines it as `1`). + +* `BOOST_LEAF_CFG_CAPTURE`: Defining this macro as `0` disables the <> functionality, which (only if used) allocates memory dynamically (if the macro is left undefined, LEAF defines it as `1`). + +* `BOOST_LEAF_CFG_GNUC_STMTEXPR`: This macro controls whether or not <> is defined in terms of a https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html[GNU C statement expression], which enables its use to check for errors similarly to how the questionmark operator works in some languages (see <>). By default the macro is defined as `1` under `pass:[__GNUC__]`, otherwise as `0`. + +* `BOOST_LEAF_CFG_WIN32`: Defining this macro as 1 enables the default constructor in <>, and the automatic conversion to string (via `FormatMessageA`) when <> is printed. If the macro is left undefined, LEAF defines it as `0` (even on windows, since including `windows.h` is generally not desirable). Note that the `e_LastError` type itself is available on all platforms, there is no need for conditional compilation in error handlers that use it. + +* `BOOST_LEAF_NO_EXCEPTIONS`: Disables all exception handling support. If left undefined, LEAF defines it automatically based on the compiler configuration (e.g. `-fno-exceptions`). + +* `BOOST_LEAF_NO_THREADS`: Disables all thread safety in LEAF. + +[[configuring_tls_access]] +=== Configuring TLS Access + +LEAF requires support for thread-local `void` pointers. By default, this is implemented by means of the {CPP}11 `thread_local` keyword, but in order to support <>, it is possible to configure LEAF to use an array of thread local pointers instead, by defining `BOOST_LEAF_USE_TLS_ARRAY`. In this case, the user is required to define the following two functions to implement the required TLS access: + +[source,c++] +---- +namespace boost { namespace leaf { + +namespace tls +{ + void * read_void_ptr( int tls_index ) noexcept; + void write_void_ptr( int tls_index, void * p ) noexcept; +} + +} } +---- + +TIP: For efficiency, `read_void_ptr` and `write_void_ptr` should be defined `inline`. + +Under `BOOST_LEAF_USE_TLS_ARRAY` the following additional configuration macros are recognized: + +* `BOOST_LEAF_CFG_TLS_ARRAY_START_INDEX` specifies the start TLS array index available to LEAF (if the macro is left undefined, LEAF defines it as `0`). + +* `BOOST_LEAF_CFG_TLS_ARRAY_SIZE` may be defined to specify the size of the TLS array. In this case TLS indices are validated via `BOOST_LEAF_ASSERT` before being passed to `read_void_ptr` / `write_void_ptr`. + +* `BOOST_LEAF_CFG_TLS_INDEX_TYPE` may be defined to specify the integral type used to store assigned TLS indices (if the macro is left undefined, LEAF defines it as `unsigned char`). + +TIP: Reporting error objects of types that are not used by the program to handle failures does not consume TLS pointers. The minimum size of the TLS pointer array required by LEAF is the total number of different types used as arguments to error handlers (in the entire program), plus one. + +WARNING: Beware of `read_void_ptr`/`write_void_ptr` accessing thread local pointers beyond the static boundaries of the thread local pointer array; this will likely result in undefined behavior. + +[[embedded_platforms]] +=== Embedded Platforms + +Defining `BOOST_LEAF_EMBEDDED` is equivalent to the following: + +[source,c++] +---- +#ifndef BOOST_LEAF_CFG_DIAGNOSTICS +# define BOOST_LEAF_CFG_DIAGNOSTICS 0 +#endif + +#ifndef BOOST_LEAF_CFG_STD_SYSTEM_ERROR +# define BOOST_LEAF_CFG_STD_SYSTEM_ERROR 0 +#endif + +#ifndef BOOST_LEAF_CFG_STD_STRING +# define BOOST_LEAF_CFG_STD_STRING 0 +#endif + +#ifndef BOOST_LEAF_CFG_CAPTURE +# define BOOST_LEAF_CFG_CAPTURE 0 +#endif +---- + +LEAF supports FreeRTOS out of the box, please define `BOOST_LEAF_TLS_FREERTOS` (in which case LEAF automatically defines `BOOST_LEAF_EMBEDDED`, if it is not defined already). + +For other embedded platforms, please define `BOOST_LEAF_USE_TLS_ARRAY`, see <>. + +If your program does not use concurrency at all, simply define `BOOST_LEAF_NO_THREADS`, which requires no TLS support at all (but is NOT thread-safe). + +[[portability]] +== Portability + +The source code is compatible with {CPP}11 or newer. + +LEAF uses thread-local storage (only for pointers). By default, this is implemented via the {CPP}11 `thread_local` storage class specifier, but the library is easily configurable to use any platform-specific TLS API instead (it ships with built-in support for FreeRTOS). See <>. + +== Running the Unit Tests + +The unit tests can be run with https://mesonbuild.com[Meson Build] or with Boost Build. To run the unit tests: + +=== Meson Build + +Clone LEAF into any local directory and execute: + +[source,sh] +---- +cd leaf +meson bld/debug +cd bld/debug +meson test +---- -=== Rationale +See `meson_options.txt` found in the root directory for available build options. + +=== Boost Build + +Assuming the current working directory is `/libs/leaf`: + +[source,sh] +---- +../../b2 test +---- + +== Benchmark + +https://github.com/boostorg/leaf/blob/master/benchmark/benchmark.md[This benchmark] compares the performance of LEAF, Boost Outcome and `tl::expected`. + +[[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. @@ -5699,185 +5797,21 @@ return leaf::try_handle_some( [.text-right] <> | <> | <> | <> | <> -NOTE: Please post questions and feedback on the Boost Developers Mailing List. - -''' - -[[exception_specifications]] -=== Critique 1: Error Types Do Not Participate in Function Signatures - -A knee-jerk critique of the LEAF design is that it does not statically enforce that each possible error condition is recognized and handled by the program. One idea I've heard from multiple sources is to add `E...` parameter pack to `result`, essentially turning it into `expected`, so we could write something along these lines: - -[source,c++] ----- -expected f() noexcept; <1> - -expected g() noexcept <2> -{ - if( expected r = f() ) - { - return r; //Success, return the T - } - else - { - return r.handle_error( [] ( .... ) <3> - { - .... - } ); - } -} ----- -<1> `f` may only return error objects of type `E1`, `E2`, `E3`. -<2> `g` narrows that to only `E1` and `E3`. -<3> Because `g` may only return error objects 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 use the equivalent of exception specifications, even if they are enforced statically? - -"The short answer is that nobody knows how to fix exception specifications in any language, because the dynamic enforcement {CPP} 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`).footnote:[https://herbsutter.com/2007/01/24/questions-about-exception-specifications/]" --- Herb Sutter - -Consider again the example above: assuming we don't want important error-related information to be lost, values of type `E1` and/or `E3` must be able to encode any `E2` value dynamically. But like Sutter points out, in generic contexts we don't know what errors may result in calling a user-supplied function. The only way around that is to specify a single type (e.g. `std::error_code`) that can communicate any and all errors, which ultimately defeats the idea of using static type checking to enforce correct error handling. - -That said, in every program there are certain _error handling_ functions (e.g. `main`) which are required to handle any error, and it is highly desirable to be able to enforce this requirement at compile-time. In LEAF, the `try_handle_all` function implements this idea: if the user fails to supply at least one handler that will match any error, the result is a compile error. This guarantees that the scope invoking `try_handle_all` is prepared to recover from any failure. - -''' - -[[translation]] -=== Critique 2: LEAF Does Not Facilitate Mapping Between Different Error Types - -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 -- - -[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 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` with value-or-error variant semantics, so that: - -* `lib_a` errors are transported in `result`, -* `lib_b` errors are transported in `result`, -* then both are automatically mapped to `result` 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 both of `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 `lib_a` and `lib_b`. - -* It presumes that all errors in the program can be specified by exactly one error code, which is false. +== Limitations -To elaborate on the last point, 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` handles this case well: +When using dynamic linking, it is required that error types are declared with `default` visibility, e.g.: [source,c++] ---- -struct attempted_location -{ - std::string path; - error ec; -}; - -struct config_error +struct __attribute__ ((visibility ("default"))) my_error_info { - attempted_location current_dir, user_dir, app_dir; + int value; }; - -result 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`. It is a fact that we can not assume that all error conditions can be fully specified by an `enum`; an error handling library must be able to transport arbitrary static types efficiently. - -[[errors_are_not_implementation_details]] -=== Critique 3: LEAF Does Not Treat Low Level Error Types as Implementation Details - -This critique is a combination of <> and <>, but it deserves special attention. Let's consider this example using LEAF: - -[source,c++] ----- -leaf::result read_line( reader & r ); - -leaf::result parse_line( std::string const & line ); - -leaf::result read_and_parse_line( reader & r ) -{ - BOOST_LEAF_AUTO(line, read_line(r)); <1> - BOOST_LEAF_AUTO(parsed, parse_line(line)); <2> - return parsed; -} ---- -[.text-right] -<> | <> - -<1> Read a line, forward errors to the caller. -<2> Parse the line, forward errors to the caller. - -The objection is that LEAF will forward verbatim the errors that are detected in `read_line` or `parse_line` to the caller of `read_and_parse_line`. The premise of this objection is that such low-level errors are implementation details and should be treated as such. Under this premise, `read_and_parse_line` should act as a translator of sorts, in both directions: - -* When called, it should translate its own arguments to call `read_line` and `parse_line`; -* If an error is detected, it should translate the errors from the error types returned by `read_line` and `parse_line` to a higher-level type. - -The motivation is to isolate the caller of `read_and_parse_line` from its implementation details `read_line` and `parse_line`. - -There are two possible ways to implement this translation: - -*1)* `read_and_parse_line` understands the semantics of *all possible failures* that may be reported by both `read_line` and `parse_line`, implementing a non-trivial mapping which both _erases_ information that is considered not relevant to its caller, as well as encodes _different_ semantics in the error it reports. In this case `read_and_parse_line` assumes full responsibility for describing precisely what went wrong, using its own type specifically designed for the job. -*2)* `read_and_parse_line` returns an error object that essentially indicates which of the two inner functions failed, and also transports the original error object without understanding its semantics and without any loss of information, wrapping it in a new error type. - -The problem with *1)* is that typically the caller of `read_and_parse_line` is not going to handle the error, but it does need to forward it to its caller. In our attempt to protect the *one* error handling function from "implementation details", we've coupled the interface of *all* intermediate error neutral functions with the static types of errors they do not understand and do not handle. - -Consider the case where `read_line` communicates `errno` in its errors. What is `read_and_parse_line` supposed to do with e.g. `EACCESS`? Turn it into `READ_AND_PARSE_LINE_EACCESS`? To what end, other than to obfuscate the original (already complex and platform-specific) semantics of `errno`? - -And what if the call to `read` is polymorphic, which is also typical? What if it involves a user-supplied function object? What kinds of errors does it return and why should `read_and_parse_line` care? +This works as expected except on Windows, where thread-local storage is not shared between the individual binary modules. For this reason, to transport error objects across DLL boundaries, it is required that they're captured in a <>, just like when <>. -Therefore, we're left with *2)*. There's almost nothing wrong with this option, since it passes any and all error-related information from lower level functions without any loss. However, using a wrapper type to grant (presumably dynamic) access to any lower-level error type it may be transporting is cumbersome and (like Niall Douglas <>) in general probably requires dynamic allocations. It is better to use independent error types that communicate the additional information not available in the original error object, while error handlers rely on LEAF to provide efficient access to any and all low-level error types, as needed. +TIP: When using dynamic linking, it is always best to define module interfaces in terms of C (and implement them in {CPP} if appropriate). == Alternatives to LEAF @@ -6103,143 +6037,6 @@ Further, consider that Outcome aims to hopefully become _the_ one error handling In contrast, the design of LEAF acknowledges that {CPP} programmers don't even agree on what a string is. If your project uses 10 different libraries, this probably means 15 different ways to report errors, sometimes across uncooperative interfaces (e.g. C APIs). LEAF helps you get the job done. -== Benchmark - -https://github.com/boostorg/leaf/blob/master/benchmark/benchmark.md[This benchmark] compares the performance of LEAF, Boost Outcome and `tl::expected`. - -== Running the Unit Tests - -The unit tests can be run with https://mesonbuild.com[Meson Build] or with Boost Build. To run the unit tests: - -=== Meson Build - -Clone LEAF into any local directory and execute: - -[source,sh] ----- -cd leaf -meson bld/debug -cd bld/debug -meson test ----- - -See `meson_options.txt` found in the root directory for available build options. - -=== Boost Build - -Assuming the current working directory is `/libs/leaf`: - -[source,sh] ----- -../../b2 test ----- - -[[configuration]] -== Configuration - -The following configuration macros are recognized: - -* `BOOST_LEAF_CFG_DIAGNOSTICS`: Defining this macro as `0` stubs out both <> and <> (if the macro is left undefined, LEAF defines it as `1`). - -* `BOOST_LEAF_CFG_STD_SYSTEM_ERROR`: Defining this macro as `0` disables the `std::error_code` / `std::error_condition` integration. In this case LEAF does not `#include `, which may be too heavy for embedded platforms (if the macro is left undefined, LEAF defines it as `1`). - -* `BOOST_LEAF_CFG_STD_STRING`: Defining this macro as `0` disables all use of `std::string` (this requires `BOOST_LEAF_CFG_DIAGNOSTICS=0` as well). In this case LEAF does not `#include ` which may be too heavy for embedded platforms (if the macro is left undefined, LEAF defines it as `1`). - -* `BOOST_LEAF_CFG_CAPTURE`: Defining this macro as `0` disables the ability of `leaf::result` to transport errors between threads. In this case LEAF does not `#include `, which may be too heavy for embedded platforms (if the macro is left undefined, LEAF defines it as `1`). - -* `BOOST_LEAF_CFG_GNUC_STMTEXPR`: This macro controls whether or not <> is defined in terms of a https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html[GNU C statement expression], which enables its use to check for errors similarly to how the questionmark operator works in some languages (see <>). By default the macro is defined as `1` under `pass:[__GNUC__]`, otherwise as `0`. - -* `BOOST_LEAF_CFG_WIN32`: Defining this macro as 1 enables the default constructor in <>, and the automatic conversion to string (via `FormatMessageA`) when <> is printed. If the macro is left undefined, LEAF defines it as `0` (even on windows, since including `windows.h` is generally not desirable). Note that the `e_LastError` type itself is available on all platforms, there is no need for conditional compilation in error handlers that use it. - -* `BOOST_LEAF_NO_EXCEPTIONS`: Disables all exception handling support. If left undefined, LEAF defines it automatically based on the compiler configuration (e.g. `-fno-exceptions`). - -* `BOOST_LEAF_NO_THREADS`: Disables all thread safety in LEAF. - -[[configuring_tls_access]] -=== Configuring TLS Access - -LEAF requires support for thread-local `void` pointers. By default, this is implemented by means of the {CPP}11 `thread_local` keyword, but in order to support <>, it is possible to configure LEAF to use an array of thread local pointers instead, by defining `BOOST_LEAF_USE_TLS_ARRAY`. In this case, the user is required to define the following two functions to implement the required TLS access: - -[source,c++] ----- -namespace boost { namespace leaf { - -namespace tls -{ - void * read_void_ptr( int tls_index ) noexcept; - void write_void_ptr( int tls_index, void * p ) noexcept; -} - -} } ----- - -TIP: For efficiency, `read_void_ptr` and `write_void_ptr` should be defined `inline`. - -Under `BOOST_LEAF_USE_TLS_ARRAY` the following additional configuration macros are recognized: - -* `BOOST_LEAF_CFG_TLS_ARRAY_START_INDEX` specifies the start TLS array index available to LEAF (if the macro is left undefined, LEAF defines it as `0`). - -* `BOOST_LEAF_CFG_TLS_ARRAY_SIZE` may be defined to specify the size of the TLS array. In this case TLS indices are validated via `BOOST_LEAF_ASSERT` before being passed to `read_void_ptr` / `write_void_ptr`. - -* `BOOST_LEAF_CFG_TLS_INDEX_TYPE` may be defined to specify the integral type used to store assigned TLS indices (if the macro is left undefined, LEAF defines it as `unsigned char`). - -TIP: Reporting error objects of types that are not used by the program to handle failures does not consume TLS pointers. The minimum size of the TLS pointer array required by LEAF is the total number of different types used as arguments to error handlers (in the entire program), plus one. - -WARNING: Beware of `read_void_ptr`/`write_void_ptr` accessing thread local pointers beyond the static boundaries of the thread local pointer array; this will likely result in undefined behavior. - -[[embedded_platforms]] -=== Embedded Platforms - -Defining `BOOST_LEAF_EMBEDDED` is equivalent to the following: - -[source,c++] ----- -#ifndef BOOST_LEAF_CFG_DIAGNOSTICS -# define BOOST_LEAF_CFG_DIAGNOSTICS 0 -#endif - -#ifndef BOOST_LEAF_CFG_STD_SYSTEM_ERROR -# define BOOST_LEAF_CFG_STD_SYSTEM_ERROR 0 -#endif - -#ifndef BOOST_LEAF_CFG_STD_STRING -# define BOOST_LEAF_CFG_STD_STRING 0 -#endif - -#ifndef BOOST_LEAF_CFG_CAPTURE -# define BOOST_LEAF_CFG_CAPTURE 0 -#endif ----- - -LEAF supports FreeRTOS out of the box, please define `BOOST_LEAF_TLS_FREERTOS` (in which case LEAF automatically defines `BOOST_LEAF_EMBEDDED`, if it is not defined already). - -For other embedded platforms, please define `BOOST_LEAF_USE_TLS_ARRAY`, see <>. - -If your program does not use concurrency at all, simply define `BOOST_LEAF_NO_THREADS`, which requires no TLS support at all (but is NOT thread-safe). - -[[portability]] -== Portability - -The source code is compatible with {CPP}11 or newer. - -LEAF uses thread-local storage (only for pointers). By default, this is implemented via the {CPP}11 `thread_local` storage class specifier, but the library is easily configurable to use any platform-specific TLS API instead (it ships with built-in support for FreeRTOS). See <>. - -== Limitations - -When using dynamic linking, it is required that error types are declared with `default` visibility, e.g.: - -[source,c++] ----- -struct __attribute__ ((visibility ("default"))) my_error_info -{ - int value; -}; ----- - -This works as expected except on Windows, where thread-local storage is not shared between the individual binary modules. For this reason, to transport error objects across DLL boundaries, it is required that they're captured in a <>, just like when <>. - -TIP: When using dynamic linking, it is always best to define module interfaces in terms of C (and implement them in {CPP} if appropriate). - == Acknowledgements Special thanks to Peter Dimov and Sorin Fetche.