diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 1205b3a..5977849 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -107,6 +107,12 @@ "command": "cd ${workspaceRoot}/bld/debug && meson test function_traits_test", "problemMatcher": { "base": "$gcc", "fileLocation": ["relative","${workspaceRoot}/bld/debug"] } }, + { + "label": "is_error_type_test", + "type": "shell", + "command": "cd ${workspaceRoot}/bld/debug && meson test is_error_type_test", + "problemMatcher": { "base": "$gcc", "fileLocation": ["relative","${workspaceRoot}/bld/debug"] } + }, { "label": "multiple_errors_test", "type": "shell", diff --git a/adoc/README.adoc b/adoc/README.adoc index d01313f..47568ca 100644 --- a/adoc/README.adoc +++ b/adoc/README.adoc @@ -73,7 +73,7 @@ Let's assume that to handle errors from a given function `f` we need an error co <5> Handle the case where we only received `error_code` and `e_file_name` (associated with the failure reported in `r`). [.text-right] -`<>` | `<>` | <> | <> +`<>` | `<>` | <> ==== The `error_code` can be a simple `enum`: @@ -179,7 +179,7 @@ Let's assume that to handle exceptions from a given function `f` we need a file <4> Handle the case where we only received `e_file_name` (associated with the failure reported by `e`). [.text-right] -`<>` | <> | <> +`<>` | <> ==== Because both the file name and the request ID are strings, we will wrap each of them in a simple `struct`, so we can tell them apart: @@ -257,6 +257,28 @@ enum error_code We don't need an enumerated value that indicates success. That's because we will use the handy class template `<>` 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::<>`. +To enable `leaf::error` to work our `error_code` `enum`, we need to specialize the <> template: + +[source,c++] +---- +namespace boost { namespace leaf { + + template<> struct is_error_type: std::true_type { }; + +} } +---- + +[TIP] +-- +The `is_error_type` template needs not be specialized for: + +* types that define an accessible data member `value`, + +* `std::error_code`, + +* `boost::system::error_code`. +-- + Here is a function that reads data from a `FILE` into a buffer and reports the various errors which may occur (it returns `result` because in case of success it doesn't return a value): ==== @@ -474,7 +496,7 @@ int main( int argc, char const * argv[ ] ) ---- [.text-right] -`<>` | <> | <> | <> | <> +`<>` | <> | <> | <> ==== To summarize, when using LEAF without exception handling: @@ -661,7 +683,7 @@ int main( int argc, char const * argv[ ] ) ---- [.text-right] -`<>` | <> | <> | <> +`<>` | <> | <> ==== To summarize, when using LEAF with exception handling: @@ -681,7 +703,7 @@ NOTE: The complete program from this tutorial is available https://github.com/za include::synopses/expect.adoc[] [.text-right] -<> | <> | <> | <> | <> | <> | <> | <> | <> +<> | <> | <> | <> | <> | <> | <> | <> ==== *Class `error`* @@ -689,7 +711,7 @@ include::synopses/expect.adoc[] include::synopses/error.adoc[] [.text-right] -<> | <> | <> | <> | <> | <> | <> +<> | <> | <> | <> | <> | <> | <> ==== *Automatic Propagation* @@ -730,9 +752,9 @@ include::synopses/exception.adoc[] [[wrapping]] === Wrapping Error Objects -With LEAF, users can efficiently associate with errors or with exceptions any number of values that pertain to a failure. These values may be of arbitrary movable types. +With LEAF, users can efficiently associate with errors or with exceptions any number of values that pertain to a failure. These values may be of any no-throw movable type for which the <> template is properly specialized. -However, when using types that are not unique to error handling, each value should be enclosed in a C-`struct`, which acts as its compile-time identifier and gives it semantic meaning. Examples: +However, when using general types that are not specific to error handling, each value should be enclosed in a C-`struct` that acts as its compile-time identifier and gives it semantic meaning. Examples: [source,c++] ---- @@ -743,8 +765,6 @@ struct e_minimum_temperature { int value; }; struct e_maximum_temperature { int value; }; ---- -By convention these structs use the `e_` prefix and define a single data member called `value`, whough any `noexcept`-movable type may be used. - Various LEAF functions take a list of error objects to associate with an `<>` value. For example, to indicate an error, a function that returns a `<>` may use something like: [source,c++] @@ -752,6 +772,8 @@ Various LEAF functions take a list of error objects to associate with an `<> | <> | <> | <> | <> | <> | <> | <> +<> | <> | <> | <> | <> | <> | <> ==== All `expect` objects must use automatic storage duration. They are not copyable and are not movable. @@ -975,7 +997,7 @@ NOTE: The printing of each individual object is done by the rules described <> | <> | <> | <> | <> | <> +<> | <> | <> | <> | <> | <> ==== Objects of class `error` are values that identify an error across the entire program. They can be copied, moved, assigned to, and compared to other error objects. They occupy as much memory, and are as efficient as `unsigned int`. @@ -998,6 +1020,8 @@ namespace boost { namespace leaf { } } ---- +Requirements: :: `<>::value` must be `true` for each `E`. + Effects: :: Each of the `e...` objects is either moved into the corresponding storage provided by `expect` instances (where it is associated with `*this`), or discarded. See `<>`. 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. @@ -1073,27 +1097,77 @@ Effects: :: Prints an `unsigned int` value that uniquely identifies the value `e ''' -[[peek_next_error]] -==== `peek_next_error` +[[next_error_value]] +==== `next_error_value` .#include [source,c++] ---- namespace boost { namespace leaf { - error peek_next_error() noexcept; + error next_error_value() noexcept; } } ---- Returns: :: The `error` value which will be returned the next time the `<>` constructor is invoked from the calling thread. + -This function can be used to associate error objects with the next `error` value to be reported. Use with caution, only when restricted to reporting errors using specific third-party types, incompatible with LEAF -- for example when reporting an error from a C callback. As soon as control exits this critical path, you should create and return a `leaf::error` object (which will be equal to the `error` object returned by `peek_next_error`). +This function can be used to associate error objects with the next `error` value to be reported. Use with caution, only when restricted to reporting errors using specific third-party types, incompatible with LEAF -- for example when reporting an error from a C callback. As soon as control exits this critical path, you should create and return a `leaf::error` object (which will be equal to the `error` object returned by `next_error_value`). IMPORTANT: `error` values are unique across the entire program. ''' +[[is_error_type]] +==== `is_error_type` + +.#include +[source,c++] +---- +namespace boost { namespace leaf { + + template + struct is_error_type + { + static constexpr bool value = <>; + }; + +} } +---- + +The `is_error_type` template should be specialized for each user-defined type which the user desires to be used as an error type with LEAF, for example: + +[source,c++] +---- +enum my_error +{ + error1, + error2, + .... +}; + +namespace boost { namespace leaf { + + template<> struct is_error_type: std::true_type { }; + +} } +---- + +This requirement is designed to trigger a diagnostic in case the user unintentionally passes some random object to the `leaf::error` constructor. + +[TIP] +-- +The `is_error_type` template needs not be specialized for: + +* types that define an accessible data member `value`, + +* `std::error_code`, + +* `boost::system::error_code`. +-- + +''' + === Automatic Propagation ==== @@ -2168,7 +2242,7 @@ But if the caught exception doesn't have a `leaf::error` subobject, `get_error` ''' [[technique_preload_in_c_callbacks]] -=== Using `peek_next_error` in C-callbacks +=== Using `next_error_value` in C-callbacks Communicating information pertaining to a failure detected in a C callback is tricky, because C callbacks are limited to a specific static signature, which may not use {CPP} types. @@ -2237,7 +2311,7 @@ int do_work( lua_State * L ) noexcept } else { - leaf::peek_next_error().propagate(ec1); //<3> + leaf::next_error_value().propagate(ec1); //<3> return luaL_error(L,"do_work_error"); //<4> } } @@ -2248,7 +2322,7 @@ int do_work( lua_State * L ) noexcept <4> ...once control reaches it, after we tell the Lua interpreter to abort the program. [.text-right] -<> | `<>` +<> | `<>` ==== 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 `<>`, so that our caller can get the answer in case of success, or an error: @@ -2320,7 +2394,7 @@ int main() noexcept <4> Handle all other `lua_pcall` failures. [.text-right] -`<>` | `<>` | <> | <> +`<>` | `<>` | <> ==== [NOTE] @@ -2403,7 +2477,7 @@ else ---- [.text-right] -`<>` | `<>` | <> | <> +`<>` | `<>` | <> ==== NOTE: Follow this link to see a complete example program: https://github.com/zajo/leaf/blob/master/example/capture_result.cpp?ts=3[capture_result.cpp]. @@ -2475,7 +2549,7 @@ catch( my_exception & e ) ---- [.text-right] -`<>` | <> | <> | <> +`<>` | <> | <> ==== NOTE: Follow this link to see a complete example program: https://github.com/zajo/leaf/blob/master/example/capture_eh.cpp?ts=3[capture_eh.cpp]. @@ -2598,7 +2672,7 @@ catch( file_read_error & e ) ---- [.text-right] -`<>` | <> | <> +`<>` | <> ==== Of course LEAF works without exception handling as well. Below is the same snippet, written using `<>`: @@ -2653,7 +2727,7 @@ else ---- [.text-right] -`<>` | `<>` | <> | <> | <> +`<>` | `<>` | <> | <> ==== NOTE: Please post questions and feedback on the Boost Developers Mailing List (LEAF is not part of Boost). diff --git a/adoc/synopses/error.adoc b/adoc/synopses/error.adoc index 26214d5..e247b02 100644 --- a/adoc/synopses/error.adoc +++ b/adoc/synopses/error.adoc @@ -19,7 +19,13 @@ namespace boost { namespace leaf { friend std::ostream & operator<<( std::ostream & os, error const & e ) }; - error peek_next_error() noexcept; + error next_error_value() noexcept; + + template + struct is_error_type + { + static constexpr bool value = <>; + }; } } ---- diff --git a/example/capture_eh.cpp b/example/capture_eh.cpp index 26e0327..140a404 100644 --- a/example/capture_eh.cpp +++ b/example/capture_eh.cpp @@ -19,6 +19,7 @@ namespace leaf = boost::leaf; //Define several e-types. +struct e_thread_id { std::thread::id value; }; struct e_failure_info1 { std::string value; }; struct e_failure_info2 { int value; }; struct e_failure_info3 { long value; }; @@ -38,7 +39,7 @@ task_result task( bool succeed ) return task_result(); //Simulate successful result. else throw leaf::exception( - std::this_thread::get_id(), + e_thread_id{std::this_thread::get_id()}, e_failure_info1{"info"}, e_failure_info2{42}, e_failure_info4{42} ); @@ -67,7 +68,7 @@ int main() //Launch tasks, transport the specified e-types. For demonstration, note that the task provides //failure_info4 which we don't care about, and that we say we could use failure_info3, but which the //task doesn't provide. So, we'll only get failed_thread_id, failure_info1 and failure_info2. - auto fut = launch_async_tasks(42); + auto fut = launch_async_tasks(42); //Collect results or deal with failures. for( auto & f : fut ) @@ -75,7 +76,7 @@ int main() f.wait(); //Storage for e-objects. - leaf::expect exp; + leaf::expect exp; try { @@ -90,9 +91,9 @@ int main() { //Failure! Handle the error, print failure info. handle_exception( exp, e, - [ ] ( e_failure_info1 const & v1, e_failure_info2 const & v2, std::thread::id const & tid ) + [ ] ( e_failure_info1 const & v1, e_failure_info2 const & v2, e_thread_id const & tid ) { - std::cerr << "Error in thread " << tid << "! failure_info1: " << v1.value << ", failure_info2: " << v2.value << std::endl; + std::cerr << "Error in thread " << tid.value << "! failure_info1: " << v1.value << ", failure_info2: " << v2.value << std::endl; } ); } } diff --git a/example/capture_result.cpp b/example/capture_result.cpp index 7cd36b1..0fabf15 100644 --- a/example/capture_result.cpp +++ b/example/capture_result.cpp @@ -18,6 +18,7 @@ namespace leaf = boost::leaf; //Define several e-types. +struct e_thread_id { std::thread::id value; }; struct e_failure_info1 { std::string value; }; struct e_failure_info2 { int value; }; struct e_failure_info3 { long value; }; @@ -33,7 +34,7 @@ leaf::result task( bool succeed ) return task_result(); //Simulate successful result. else return leaf::error( - std::this_thread::get_id(), + e_thread_id{std::this_thread::get_id()}, e_failure_info1{"info"}, e_failure_info2{42}, e_failure_info4{42} ); @@ -61,7 +62,7 @@ int main() //Launch tasks, transport the specified e-types. For demonstration, note that the task provides //failure_info4 which we don't care about, and that we say we could use failure_info3, but which //the task doesn't provide. So, we'll only get failed_thread_id, failure_info1 and failure_info2. - auto fut = launch_async_tasks(42); + auto fut = launch_async_tasks(42); //Collect results or deal with failures. for( auto & f : fut ) @@ -69,7 +70,7 @@ int main() f.wait(); //Storage for e-objects. - leaf::expect exp; + leaf::expect exp; //Get the task result, check for success. if( leaf::result r = f.get() ) @@ -81,9 +82,9 @@ int main() { //Failure! Handle error, print failure info. bool matched = handle_error( exp, r, - [ ] ( e_failure_info1 const & v1, e_failure_info2 const & v2, std::thread::id const & tid ) + [ ] ( e_failure_info1 const & v1, e_failure_info2 const & v2, e_thread_id const & tid ) { - std::cerr << "Error in thread " << tid << "! failure_info1: " << v1.value << ", failure_info2: " << v2.value << std::endl; + std::cerr << "Error in thread " << tid.value << "! failure_info1: " << v1.value << ", failure_info2: " << v2.value << std::endl; } ); assert(matched); } diff --git a/example/lua_callback_eh.cpp b/example/lua_callback_eh.cpp index 233eb6a..ba769af 100644 --- a/example/lua_callback_eh.cpp +++ b/example/lua_callback_eh.cpp @@ -25,6 +25,9 @@ enum do_work_error_code ec1=1, ec2 }; +namespace boost { namespace leaf { + template<> struct is_error_type: std::true_type { }; +} } struct e_lua_pcall_error { int value; }; struct e_lua_error_message { std::string value; }; diff --git a/example/lua_callback_result.cpp b/example/lua_callback_result.cpp index 83a711c..3c63fdb 100644 --- a/example/lua_callback_result.cpp +++ b/example/lua_callback_result.cpp @@ -23,6 +23,9 @@ enum do_work_error_code ec1=1, ec2 }; +namespace boost { namespace leaf { + template<> struct is_error_type: std::true_type { }; +} } struct e_lua_pcall_error { int value; }; struct e_lua_error_message { std::string value; }; @@ -46,7 +49,7 @@ int do_work( lua_State * L ) noexcept { //Associate an do_work_error_code object with the *next* leaf::error object we will //definitely return from the call_lua function... - leaf::peek_next_error().propagate(ec1); + leaf::next_error_value().propagate(ec1); //...once control reaches it, after we tell the Lua interpreter to abort the program. return luaL_error(L,"do_work_error"); diff --git a/example/print_file_result.cpp b/example/print_file_result.cpp index 61be7dc..442fdb2 100644 --- a/example/print_file_result.cpp +++ b/example/print_file_result.cpp @@ -31,6 +31,9 @@ enum error_code input_eof_error, cout_error }; +namespace boost { namespace leaf { + template<> struct is_error_type: std::true_type { }; +} } leaf::result> file_open( char const * file_name ) diff --git a/include/boost/leaf/detail/throw.hpp b/include/boost/leaf/detail/throw.hpp index 636c142..70208d1 100644 --- a/include/boost/leaf/detail/throw.hpp +++ b/include/boost/leaf/detail/throw.hpp @@ -10,7 +10,7 @@ #include #include -#define LEAF_THROW ::boost::leaf::peek_next_error().propagate(::boost::leaf::e_source_location{__FILE__,__LINE__,__FUNCTION__}),throw::boost::leaf::exception +#define LEAF_THROW ::boost::leaf::next_error_value().propagate(::boost::leaf::e_source_location{__FILE__,__LINE__,__FUNCTION__}),throw::boost::leaf::exception namespace boost { namespace leaf { diff --git a/include/boost/leaf/error.hpp b/include/boost/leaf/error.hpp index c992f0f..195b121 100644 --- a/include/boost/leaf/error.hpp +++ b/include/boost/leaf/error.hpp @@ -12,8 +12,12 @@ #include #include #include +#include +#include -#define LEAF_ERROR ::boost::leaf::peek_next_error().propagate(::boost::leaf::e_source_location{__FILE__,__LINE__,__FUNCTION__}),::boost::leaf::error +#define LEAF_ERROR ::boost::leaf::next_error_value().propagate(::boost::leaf::e_source_location{__FILE__,__LINE__,__FUNCTION__}),::boost::leaf::error + +namespace boost { namespace system { class error_code; } } namespace boost { namespace leaf { @@ -29,15 +33,40 @@ namespace boost { namespace leaf { } }; + namespace leaf_detail + { + template + struct has_data_member_value + { + static constexpr bool value=false; + }; + + template + struct has_data_member_value().value, void())> + { + static constexpr bool value=std::is_member_object_pointer::value; + }; + } + + template + struct is_error_type + { + static constexpr bool value = leaf_detail::has_data_member_value::value; + }; + + template <> struct is_error_type: std::true_type { }; + template <> struct is_error_type: std::true_type { }; + template <> struct is_error_type: std::true_type { }; + //////////////////////////////////////// class error; - error peek_next_error() noexcept; + error next_error_value() noexcept; class error { - friend error leaf::peek_next_error() noexcept; + friend error leaf::next_error_value() noexcept; unsigned id_; @@ -119,7 +148,7 @@ namespace boost { namespace leaf { return os; } - static error peek_next_error() noexcept + static error next_error_value() noexcept { return error(id_factory::tl_instance().peek()); } @@ -128,7 +157,7 @@ namespace boost { namespace leaf { error propagate( E && ... ) const noexcept; }; - inline error peek_next_error() noexcept + inline error next_error_value() noexcept { return error(error::id_factory::tl_instance().peek()); } @@ -153,6 +182,7 @@ namespace boost { namespace leaf { slot & operator=( slot const & ) = delete; typedef optional> base; slot * prev_; + static_assert(is_error_type::value,"All types passed to leaf::expect must be error types"); public: slot() noexcept; ~slot() noexcept; diff --git a/include/boost/leaf/exception.hpp b/include/boost/leaf/exception.hpp index 5335c51..53754a8 100644 --- a/include/boost/leaf/exception.hpp +++ b/include/boost/leaf/exception.hpp @@ -17,7 +17,7 @@ namespace boost { namespace leaf { if( auto e = dynamic_cast(&ex) ) return *e; else - return peek_next_error(); + return next_error_value(); } template diff --git a/include/boost/leaf/exception_capture.hpp b/include/boost/leaf/exception_capture.hpp index e0dc37f..c9b6a51 100644 --- a/include/boost/leaf/exception_capture.hpp +++ b/include/boost/leaf/exception_capture.hpp @@ -37,7 +37,7 @@ namespace boost { namespace leaf { { if( !has_error_ ) { - set_error(peek_next_error()); + set_error(next_error_value()); has_error_ = true; } unload(); diff --git a/include/boost/leaf/preload.hpp b/include/boost/leaf/preload.hpp index e1fc5d2..9e7baf1 100644 --- a/include/boost/leaf/preload.hpp +++ b/include/boost/leaf/preload.hpp @@ -66,7 +66,7 @@ namespace boost { namespace leaf { explicit preloaded( E && ... e ) noexcept: p_(preloaded_item(std::forward(e))...), - e_(peek_next_error()), + e_(next_error_value()), moved_(false) { } @@ -81,7 +81,7 @@ namespace boost { namespace leaf { ~preloaded() noexcept { - if( !moved_ && (e_!=peek_next_error() || std::uncaught_exception()) ) + if( !moved_ && (e_!=next_error_value() || std::uncaught_exception()) ) leaf_detail::tuple_for_each_preload::trigger(p_,e_); } }; @@ -131,7 +131,7 @@ namespace boost { namespace leaf { explicit deferred( F && ... f ) noexcept: d_(deferred_item(std::forward(f))...), - e_(peek_next_error()), + e_(next_error_value()), moved_(false) { } @@ -146,7 +146,7 @@ namespace boost { namespace leaf { ~deferred() noexcept { - if( !moved_ && (e_!=peek_next_error() || std::uncaught_exception()) ) + if( !moved_ && (e_!=next_error_value() || std::uncaught_exception()) ) leaf_detail::tuple_for_each_preload::trigger(d_,e_); } }; diff --git a/meson.build b/meson.build index a18b29e..19c24f8 100644 --- a/meson.build +++ b/meson.build @@ -49,6 +49,7 @@ tests = [ 'expect_test.2', 'expect_test.3', 'function_traits_test', + 'is_error_type_test', 'multiple_errors_test', 'optional_test', 'preload_test.1', diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index cea1a4f..13a9232 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -39,6 +39,7 @@ run expect_test.2.cpp ; run expect_test.3.cpp ; run multiple_errors_test.cpp ; run function_traits_test.cpp ; +run is_error_type_test.cpp ; run optional_test.cpp ; run preload_test.1.cpp ; run preload_test.2.cpp ; diff --git a/test/is_error_type_test.cpp b/test/is_error_type_test.cpp new file mode 100644 index 0000000..b44ab1b --- /dev/null +++ b/test/is_error_type_test.cpp @@ -0,0 +1,27 @@ +//Copyright (c) 2018 Emil Dotchevski +//Copyright (c) 2018 Second Spectrum, Inc. + +//Distributed under the Boost Software License, Version 1.0. (See accompanying +//file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include + +struct t0 { int value; }; +struct t1 { int value(); }; +struct t2 { }; +struct t3 { }; + +namespace boost { namespace leaf { + template <> struct is_error_type { static constexpr bool value = true; }; +} } + +namespace leaf = boost::leaf; + +int main() +{ + static_assert(leaf::is_error_type::value,"t0"); + static_assert(!leaf::is_error_type::value,"t1"); + static_assert(!leaf::is_error_type::value,"t2"); + static_assert(leaf::is_error_type::value,"t3"); + return 0; +}