Library: augment the »Either« wrapper to funciton invocation

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
This commit is contained in:
Fischlurch 2023-09-27 23:17:56 +02:00
parent 284984ad27
commit 620639b7ce
3 changed files with 173 additions and 14 deletions

View file

@ -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<void>(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 <type_traits>
#include <exception>
#include <utility>
@ -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<class FUN, typename...ARGS>
inline auto
failsafeInvoke (std::exception_ptr& capturedFailure
,FUN&& callable
,ARGS&& ...args)
{
using Res = std::invoke_result_t<FUN,ARGS...>;
try {
capturedFailure = nullptr;
if constexpr (std::is_void_v<Res>)
std::invoke (std::forward<FUN>(callable), std::forward<ARGS>(args)...);
else
return std::invoke (std::forward<FUN>(callable), std::forward<ARGS>(args)...);
}
catch(...)
{
capturedFailure = std::current_exception();
if constexpr (not std::is_void_v<Res>)
return lib::NullValue<Res>::get();
}
}
/**
* Representation of the result of some operation, _EITHER_ a value or a failure.
@ -73,6 +113,7 @@ namespace lib {
template<>
class Result<void>
{
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<class FUN, typename...ARGS, typename=lib::meta::enable_if<std::is_invocable<FUN,ARGS...>>>
Result (FUN&& callable, ARGS&& ...args)
: failure_{}
{
failsafeInvoke (failure_
,std::forward<FUN> (callable)
,std::forward<ARGS>(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<std::is_invocable<RES>>>
Result (RES&& value)
: Result<void>{true}
, value_{std::forward<RES> (value)}
{ }
// is or is not copyable depending on RES
/** invoke a _callable_ and capture result in one shot */
template<class FUN, typename...ARGS, typename=lib::meta::enable_if<std::is_invocable<FUN,ARGS...>>>
Result (FUN&& callable, ARGS&& ...args)
: Result<void>{true}
, value_{failsafeInvoke (failure_
,std::forward<FUN> (callable)
,std::forward<ARGS>(args)...)}
{ }
// is or is not copyable depending on RES
operator RES() const
@ -142,9 +204,16 @@ namespace lib {
return static_cast<TY> (*value_);
}
template<typename O>
RES
value_or (O&& defaultVal)
{
return isValid()? *value_ : std::forward<O> (defaultVal);
}
template<typename MAKE, typename...ARGS>
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<typename VAL>
template<typename VAL, typename=lib::meta::disable_if<std::is_invocable<VAL>>>
Result (VAL&&) -> Result<VAL>;
/** deduction guard: find out about result value to capture from a generic callable. */
template<typename FUN, typename...ARGS>
Result (FUN&&, ARGS&&...) -> Result<std::invoke_result_t<FUN,ARGS...>>;
} // namespace lib

View file

@ -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<double&>());
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<char>"_expect); // generic λ instantiated with <char>
CHECK (42 == seed); // int('*') == 42
auto breed = Result{evil, 55ll}; // an odd number...
VERIFY_ERROR (STATE, breed.maybeThrow() );
CHECK (Type(breed) == "Result<long long>"_expect);
auto dead = Result{[]{ throw 55; }};
auto deed = Result{[]{ /* :-) */ }};
CHECK (Type(dead) == "Result<void>"_expect);
CHECK (Type(deed) == "Result<void>"_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<bool>"_expect);
}
};

View file

@ -79752,15 +79752,54 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node CREATED="1695749456164" ID="ID_1054598766" MODIFIED="1695749478601" TEXT="Mittelschicht: &#xbb;ThreadLifecycle&#xab;">
<node CREATED="1695749538333" ID="ID_1896384393" MODIFIED="1695749549565" TEXT="definiert alle Konstruktoren"/>
<node CREATED="1695749531182" ID="ID_1148976245" MODIFIED="1695749537508" TEXT="definiert den Destruktor"/>
<node CREATED="1695749523263" ID="ID_1222427695" MODIFIED="1695749551050" TEXT="f&#xfc;hrt eine Policy ein"/>
<node CREATED="1695749552627" ID="ID_573772960" MODIFIED="1695749648141">
<node CREATED="1695749523263" ID="ID_1222427695" MODIFIED="1695749551050" TEXT="f&#xfc;hrt eine Policy ein">
<node CREATED="1695836703221" ID="ID_1826750646" MODIFIED="1695836713503" TEXT="Umgang mit R&#xfc;ckgabewerten">
<node CREATED="1695836742647" ID="ID_1537554363" MODIFIED="1695836756035" TEXT="hier lib::Result einf&#xfc;hren">
<icon BUILTIN="yes"/>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1695836757205" ID="ID_231445329" MODIFIED="1695849015400" TEXT="die void / non-void-Unterscheidung &#x27f9; handhaben per lib::Result">
<icon BUILTIN="pencil"/>
<node CREATED="1695836841642" ID="ID_29705339" MODIFIED="1695836854994" TEXT="std::invoke mit constexpr if">
<icon BUILTIN="idea"/>
</node>
<node COLOR="#5b280f" CREATED="1695836855928" ID="ID_812193758" MODIFIED="1695836897006" TEXT="in Wrapper-Funktion einbauen">
<icon BUILTIN="button_cancel"/>
</node>
<node COLOR="#338800" CREATED="1695836898202" ID="ID_1691988936" MODIFIED="1695848934909" TEXT="Nein: gleich in einen Konstruktor &#xfc;bernehmen">
<icon BUILTIN="button_ok"/>
<node CREATED="1695836911753" ID="ID_440888656" MODIFIED="1695836922568" TEXT="meine neuen F&#xe4;higkeiten gassi f&#xfc;hren">
<icon BUILTIN="ksmiletris"/>
</node>
<node COLOR="#338800" CREATED="1695838533698" ID="ID_387182382" MODIFIED="1695848924951" TEXT="Hilfsmittel: failsafeInvoke ">
<icon BUILTIN="button_ok"/>
<node COLOR="#5b280f" CREATED="1695838550590" ID="ID_652620368" MODIFIED="1695838678578" TEXT="beruht auf std::invoke_r">
<icon BUILTIN="button_cancel"/>
</node>
<node CREATED="1695838679373" ID="ID_1879095081" LINK="https://en.cppreference.com/w/cpp/utility/functional/invoke#Version_2" MODIFIED="1695838723112" TEXT="die &#xe4;quivaltente C++17-Impl per constexpr if"/>
<node CREATED="1695838556170" ID="ID_1446507689" MODIFIED="1695838569693" TEXT="setzt einen std::exception_ptr per Seiteneffekt"/>
</node>
<node COLOR="#435e98" CREATED="1695838577806" ID="ID_1120414496" MODIFIED="1695848941054" TEXT="die void / non-Void-Unterscheidung leistet der deduction-guard">
<icon BUILTIN="idea"/>
</node>
<node COLOR="#435e98" CREATED="1695848949084" ID="ID_409709339" MODIFIED="1695848979720" TEXT="Problem: Argument-loser Funktor kollidiert mit Wrapper-ctor">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1695848982471" ID="ID_1780819608" MODIFIED="1695848995149" TEXT="mu&#xdf; man explizit aussteuern"/>
<node CREATED="1695848995758" ID="ID_557992131" MODIFIED="1695849002136" TEXT="mit enable_if und is_callable"/>
</node>
</node>
</node>
</node>
<node CREATED="1695836714439" ID="ID_1467866217" MODIFIED="1695836729182" TEXT="Aktionen zum Thread-Ende"/>
<node CREATED="1695836729993" ID="ID_357017376" MODIFIED="1695836738339" TEXT="Umgang mit noch laufenden Threads"/>
</node>
<node CREATED="1695749552627" ID="ID_573772960" MODIFIED="1695836690424">
<richcontent TYPE="NODE"><html>
<head>
</head>
<body>
<p>
Policy <font size="5"><b>&#10549;</b></font>&#160;<font color="#07097c" face="Monospaced">main&lt;POL,FUN,ARGS...&gt;</font>
<font color="#7118a9" size="4">&#9881;</font>&#160;Thread <font size="5"><b>&#10549;</b></font>&#160;<font color="#07097c" face="Monospaced">main&lt;FUN,ARGS...&gt;</font>
</p>
</body>
</html></richcontent>