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:
parent
284984ad27
commit
620639b7ce
3 changed files with 173 additions and 14 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -79752,15 +79752,54 @@ Date:   Thu Apr 20 18:53:17 2023 +0200<br/>
|
|||
<node CREATED="1695749456164" ID="ID_1054598766" MODIFIED="1695749478601" TEXT="Mittelschicht: »ThreadLifecycle«">
|
||||
<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ührt eine Policy ein"/>
|
||||
<node CREATED="1695749552627" ID="ID_573772960" MODIFIED="1695749648141">
|
||||
<node CREATED="1695749523263" ID="ID_1222427695" MODIFIED="1695749551050" TEXT="führt eine Policy ein">
|
||||
<node CREATED="1695836703221" ID="ID_1826750646" MODIFIED="1695836713503" TEXT="Umgang mit Rückgabewerten">
|
||||
<node CREATED="1695836742647" ID="ID_1537554363" MODIFIED="1695836756035" TEXT="hier lib::Result einführen">
|
||||
<icon BUILTIN="yes"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1695836757205" ID="ID_231445329" MODIFIED="1695849015400" TEXT="die void / non-void-Unterscheidung ⟹ 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 übernehmen">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1695836911753" ID="ID_440888656" MODIFIED="1695836922568" TEXT="meine neuen Fähigkeiten gassi fü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 ä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ß 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>⤵</b></font> <font color="#07097c" face="Monospaced">main<POL,FUN,ARGS...></font>
|
||||
<font color="#7118a9" size="4">⚙</font> Thread <font size="5"><b>⤵</b></font> <font color="#07097c" face="Monospaced">main<FUN,ARGS...></font>
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
|
|
|
|||
Loading…
Reference in a new issue