// Copyright (c) 2018-2019 Emil Dotchevski // Copyright (c) 2018-2019 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) // This example demonstrates how to transport exceptions thrown by a low level function // through an intermediate context that is not exception-safe, to be handled in a high level // function which may or may not be exception-safe. // An real-world example for this use case is when a C API (which may not throw) is implemented // using a C++ library that throws exceptions. As demonstrated below, these exception objects are // intercepted and reported by leaf::result<>. #include #include #include #include #include namespace leaf = boost::leaf; class error_base: public virtual std::exception { }; class error_a: public virtual error_base { }; class error_b: public virtual error_base { }; class error_c: public virtual error_base { }; // Lower-level library function which throws exceptions. int compute_answer_throws() { switch( rand()%4 ) { default: return 42; case 1: throw error_a(); case 2: throw error_b(); case 3: throw error_c(); } } // Calls compute_answer_throws, switches to result for error handling. leaf::result compute_answer() noexcept { // Convert exceptions of types error_a and error_b to be communicated by leaf::result. // Any other exception will be communicated as a std::exception_ptr. return leaf::exception_to_result( [ ] { return compute_answer_throws(); } ); } // Print the answer if the call to compute_answer is successful. leaf::result print_answer() noexcept { LEAF_AUTO( answer, compute_answer()); std::cout << "Answer: " << answer << std::endl; return { }; } int main() { // Exercise print_answer a few times and handle errors. Note that the exception objects that // compute_answer_throws throws are handled as regular LEAF error objects... for( int i=0; i!=42; ++i ) { leaf::try_handle_all( [ ]() -> leaf::result { LEAF_CHECK(print_answer()); return { }; }, [ ]( error_a const & e ) { std::cerr << "Error A!" << std::endl; }, [ ]( error_b const & e ) { std::cerr << "Error B!" << std::endl; }, //...except for error_c errors, which (for demonstration) are captured as exceptions // into std::exception_ptr as "unknown" exception. Presumably this should not // happen, therefore at this point we treat this situation a logic error: we print // diagnostic information and bail out. [ ]( std::exception_ptr const * ep ) { std::cerr << "Got unknown error!" << std::endl; // Why do we take std::exception_ptr as a pointer? Because handle_all requires // that the last handler matches any error -- taken as a pointer, if there isn't a // std::exception_ptr associated with the error, the handler will still be matched //(with 0 passed for ep). Had we taken it by value or by const &, the program // would not compile. if( ep ) leaf::try_catch( [&] { std::rethrow_exception(*ep); }, [ ]( leaf::error_info const & unmatched ) { std::cerr << unmatched; } ); } ); } return 0; }