From 620639b7cea42084c2b14e2c1558d6a190cb2ebe Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 27 Sep 2023 23:17:56 +0200 Subject: [PATCH] =?UTF-8?q?Library:=20augment=20the=20=C2=BBEither=C2=AB?= =?UTF-8?q?=20wrapper=20to=20funciton=20invocation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This relieves the Thread policy from a lot of technicalities, while also creating a generally useful tool: the ability to invoke /anything callable/ (thanks to std::invoke) in a fail-safe way and transform the exception into an Either type --- src/lib/result.hpp | 91 +++++++++++++++++++++++++++++++---- tests/library/result-test.cpp | 51 +++++++++++++++++++- wiki/thinkPad.ichthyo.mm | 45 +++++++++++++++-- 3 files changed, 173 insertions(+), 14 deletions(-) diff --git a/src/lib/result.hpp b/src/lib/result.hpp index 2f5f065e9..3ef4f3df9 100644 --- a/src/lib/result.hpp +++ b/src/lib/result.hpp @@ -29,7 +29,15 @@ ** It will be copyable iff the result value is copyable. There is an implicit ** valid or failure state, which can be tested. Any attempt to get the value ** of an invalid result token will cause an exception to be thrown. - ** + ** - `Result(bool)` can be used as a success marker + ** - a `Result` instance can be created by _perfect forwarding_ from any type + ** - any exception is supported for failure, wile direct construction is limited + ** to lumiera::Error (to avoid ambiguities in ctor overload resolution) + ** - an arbitrary functor or _callable_ can be invoked, capturing the result. + ** @todo an _option-style_ interface could be provided for the »right value« + ** (i.e. the exception caught), in case this turns out to be of any use; + ** this kind of API design however is anything than trivial, given that + ** any value can be thrown as exception in C++ ** @see vault::ThreadJoinable usage example */ @@ -40,8 +48,10 @@ #include "lib/error.hpp" #include "lib/wrapper.hpp" -#include "lib/util.hpp" +#include "lib/meta/util.hpp" +#include "lib/null-value.hpp" +#include #include #include @@ -49,9 +59,39 @@ namespace lib { - using util::isnil; namespace error = lumiera::error; + /** + * Helper to invoke an arbitrary callable in a failsafe way. + * @param capturedFailure *reference* to a std::exeption_ptr served by side-effect + * @param callable anything std::invoke can handle + * @return _if_ the invokable has a return type, the result is returned, + * _otherwise_ this is a void function + * @todo with C++20 the body of the implementation can be replaced by std::invoke_r //////////////////////TICKET #1245 + */ + template + inline auto + failsafeInvoke (std::exception_ptr& capturedFailure + ,FUN&& callable + ,ARGS&& ...args) + { + using Res = std::invoke_result_t; + try { + capturedFailure = nullptr; + if constexpr (std::is_void_v) + std::invoke (std::forward(callable), std::forward(args)...); + else + return std::invoke (std::forward(callable), std::forward(args)...); + } + catch(...) + { + capturedFailure = std::current_exception(); + if constexpr (not std::is_void_v) + return lib::NullValue::get(); + } + } + + /** * Representation of the result of some operation, _EITHER_ a value or a failure. @@ -73,6 +113,7 @@ namespace lib { template<> class Result { + protected: std::exception_ptr failure_; public: @@ -83,9 +124,20 @@ namespace lib { /** failed result, with reason given.*/ Result (lumiera::Error const& reason) - : failure_{std::make_exception_ptr (reason)} - { } + : failure_{std::make_exception_ptr (reason)} + { } + + /** invoke a _callable_ and mark success or failure */ + template>> + Result (FUN&& callable, ARGS&& ...args) + : failure_{} + { + failsafeInvoke (failure_ + ,std::forward (callable) + ,std::forward(args)...); + } + explicit operator bool() const { return isValid(); } bool isValid() const { return not failure_; } @@ -120,12 +172,22 @@ namespace lib { { } /** standard case: valid result */ + template< typename=lib::meta::disable_if>> Result (RES&& value) : Result{true} , value_{std::forward (value)} { } - // is or is not copyable depending on RES + /** invoke a _callable_ and capture result in one shot */ + template>> + Result (FUN&& callable, ARGS&& ...args) + : Result{true} + , value_{failsafeInvoke (failure_ + ,std::forward (callable) + ,std::forward(args)...)} + { } + + // is or is not copyable depending on RES operator RES() const @@ -142,9 +204,16 @@ namespace lib { return static_cast (*value_); } + template + RES + value_or (O&& defaultVal) + { + return isValid()? *value_ : std::forward (defaultVal); + } + template RES - getOrElse (MAKE&& producer, ARGS ...args) + or_else (MAKE&& producer, ARGS ...args) { if (isValid()) return *value_; @@ -154,9 +223,13 @@ namespace lib { }; /** deduction guard: allow _perfect forwarding_ of a any result into the ctor call. */ - template + template>> Result (VAL&&) -> Result; - + + /** deduction guard: find out about result value to capture from a generic callable. */ + template + Result (FUN&&, ARGS&&...) -> Result>; + } // namespace lib diff --git a/tests/library/result-test.cpp b/tests/library/result-test.cpp index 77fc033ce..0cf593ec8 100644 --- a/tests/library/result-test.cpp +++ b/tests/library/result-test.cpp @@ -44,6 +44,7 @@ namespace test{ namespace error = lumiera::error; using error::LUMIERA_ERROR_FATAL; + using error::LUMIERA_ERROR_STATE; namespace { @@ -60,6 +61,12 @@ namespace test{ /***********************************************************************************//** * @test Verify an intermediary »Either« type, to embody either a successful result, * or document a failure with encountered exception. + * - when given a value, the Result captures it and is in »left« state + * - various value types can be picked up by perfect forwarding + * - when given an exception, the result is in »right« state + * - option-style `or-else` usage + * - can invoke arbitrary _callable_ and capture result or exception caught + * - invocation also works with void functors, and likewise captures failure * @see result.hpp * @see lib::ThreadJoinable usage example */ @@ -73,7 +80,7 @@ namespace test{ auto happy = Result{THE_END}; CHECK (happy == THE_END); CHECK (happy.isValid()); - CHECK (true == happy); + CHECK (bool(happy)); happy.maybeThrow(); // still alive... @@ -95,7 +102,47 @@ namespace test{ VERIFY_ERROR (FATAL, facepalm.get()); VERIFY_ERROR (FATAL, facepalm.maybeThrow() ); - CHECK (42.0 == facepalm.getOrElse([]{ return 42; })); + CHECK (42.0 == facepalm.or_else([]{ return 42; })); + CHECK (42.0 == facepalm.value_or(210/5)); + + + // a generic functor (template) to invoke + auto evil = [](auto it) + { + if (it % 2) + throw error::State{"conspiracy"}; + else + return it; + }; + + // Invoke failsafe and capture result.... + auto seed = Result{evil, '*'}; // this invocation is successful + CHECK (Type(seed) == "Result"_expect); // generic λ instantiated with + CHECK (42 == seed); // int('*') == 42 + + auto breed = Result{evil, 55ll}; // an odd number... + VERIFY_ERROR (STATE, breed.maybeThrow() ); + CHECK (Type(breed) == "Result"_expect); + + auto dead = Result{[]{ throw 55; }}; + auto deed = Result{[]{ /* :-) */ }}; + + CHECK (Type(dead) == "Result"_expect); + CHECK (Type(deed) == "Result"_expect); + + CHECK (not dead.isValid()); + CHECK ( deed.isValid()); + + try { dead.maybeThrow(); } // can handle really *anything* thrown + catch(int oo) + { CHECK (oo == 55); } + + // can also invoke member function.... + auto deaf = Result{&Literal::empty, &THE_END}; // Note: instance "this"-ptr as first argument + CHECK (deaf.isValid()); // no exception was thrown => state isValid() + CHECK (deaf == false); // yet invocation of THE_END.empty() yields false + CHECK (not deaf); // Warning: in this case, the conversion to payload type shadows + CHECK (Type(deaf) == "Result"_expect); } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 46f9e38f2..dbf540dab 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -79752,15 +79752,54 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

- Policy  main<POL,FUN,ARGS...> +  Thread  main<FUN,ARGS...>