diff --git a/include/beman/scope/scope.hpp b/include/beman/scope/scope.hpp index 68e5e51..27656fe 100644 --- a/include/beman/scope/scope.hpp +++ b/include/beman/scope/scope.hpp @@ -3,8 +3,11 @@ #ifndef BEMAN_SCOPE_HPP #define BEMAN_SCOPE_HPP +#include +#include #include -#include +#include +#include // clang-format off #if __cplusplus < 202002L @@ -12,10 +15,52 @@ #endif // clang-format on -#include +#include //todo unconditional for unique_resource + +#ifdef BEMAN_SCOPE_USE_STD_EXPERIMENTAL namespace beman::scope { +template +using scope_exit = std::experimental::scope_exit; + +template +using scope_fail = std::experimental::scope_fail; + +template +using scope_success = std::experimental::scope_success; + +// todo temporary +// template +// using unique_resource = std::experimental::unique_resource; + +// template > +// unique_resource, std::decay_t> +// make_unique_resource_checked(R&& r, const S& invalid, D&& d) noexcept(noexcept( +// std::experimental::make_unique_resource_checked(std::forward(r), std::forward(invalid), std::forward(d)))) { +// return std::experimental::make_unique_resource_checked(std::forward(r), std::forward(invalid), std::forward(d)); +//} + +} // namespace beman::scope + +#else // ! BEMAN_SCOPE__USE_STD_EXPERIMENTAL + +namespace beman::scope { + +// todo temporary +template +using unique_resource = std::experimental::unique_resource; + +// todo temporary +template > +unique_resource, std::decay_t > +make_unique_resource_checked(R&& r, const S& invalid, D&& d) noexcept(noexcept( + std::experimental::make_unique_resource_checked(std::forward(r), std::forward(invalid), std::forward(d)))) { + return std::experimental::make_unique_resource_checked(std::forward(r), std::forward(invalid), std::forward(d)); +} + +//================================================================================================== +// // -- 7.6.7 Feature test macro -- // // __cpp_lib_scope @@ -24,39 +69,24 @@ namespace beman::scope { // -- 7.5.1 Header synopsis [scope.syn] -- // // namespace std { - +// // template // class scope_exit; -template -using scope_exit = std::experimental::scope_exit; - +// // template // class scope_fail; -template -using scope_fail = std::experimental::scope_fail; - +// // template // class scope_success; -template -using scope_success = std::experimental::scope_success; - +// // template // class unique_resource; -template -using unique_resource = std::experimental::unique_resource; - +// // // factory function // template > // unique_resource, decay_t> // make_unique_resource_checked(R&& r, const S& invalid, D&& d) noexcept(see below); - -template > -unique_resource, std::decay_t> -make_unique_resource_checked(R&& r, const S& invalid, D&& d) noexcept(noexcept( - std::experimental::make_unique_resource_checked(std::forward(r), std::forward(invalid), std::forward(d)))) { - return std::experimental::make_unique_resource_checked(std::forward(r), std::forward(invalid), std::forward(d)); -} - +// // } // namespace std // @@ -86,7 +116,7 @@ make_unique_resource_checked(R&& r, const S& invalid, D&& d) noexcept(noexcept( // // template // scope_guard(EF) -> scope_guard; -// + // -- 7.6.1 Class template unique_resource [scope.unique_resource.class] -- // // template @@ -119,7 +149,278 @@ make_unique_resource_checked(R&& r, const S& invalid, D&& d) noexcept(noexcept( // // template // unique_resource(R, D) -> unique_resource; +// +//================================================================================================== + +// Template argument `ScopeExitFunc` shall be +// - a function object type([function.objects]), +// - lvalue reference to function, +// - or lvalue reference to function object type. +// +// If `ScopeExitFunc` is an object type, it shall meet the requirements of Cpp17Destructible(Table 30). +// Given an lvalue g of type remove_reference_t, the expression g() shall be well- formed. + +//================================================================================================== + +// --- Concepts --- +template +concept invocable_return = std::invocable && std::convertible_to, R>; + +template +concept scope_exit_function = + invocable_return && (std::is_nothrow_move_constructible_v || std::is_copy_constructible_v); + +template +concept scope_function_invoke_check = invocable_return; + +template +concept HasRelease = requires(T t) { + { t.release() } -> std::same_as; +}; + +template +concept HasStaticRelease = requires { + { T::release() } -> std::same_as; +}; + +// --- Enum --- + +enum class exception_during_construction_behaviour { dont_invoke_exit_func, invoke_exit_func }; + +//================================================================================================== + +// --- `scope_guard` - primary template --- + +template +class [[nodiscard]] scope_guard; + +//================================================================================================== + +/** Generalized scope guard template + * This template provides the general behaviors required for more concrete instances of scope types. + * @tparam ScopeExitFunction callable function that is conditionally invoked at the end of the scope. + * @tparam InvokeChecker callable function that handles checking if callback should be called on scope exit. + * @tparam ConstructionExceptionBehavior callable function that defines the behavior if an exception occurs + * on the construction. + */ +template +class [[nodiscard]] scope_guard { + public: + /** The constructor parameter `exit_func` in the following constructors shall be : + * - a reference to a function + * - or a reference to a function object([function.objects]) + * + * + * If EFP is not an lvalue reference type and is_nothrow_constructible_v is true, + * initialize exit_function with std::forward(f); + * otherwise initialize exit_function with f. + * + * scope_fail / scope_exit + * If the initialization of exit_function throws an exception, calls f(). + * + * scope_success + * [Note: If initialization of exit_function fails, f() wont be called. end note] + */ + template + constexpr scope_guard(EF&& exit_func, + CHKR&& invoke_checker) noexcept(std::is_nothrow_constructible_v && + std::is_nothrow_constructible_v) try + : exit_func{std::forward(exit_func)}, invoke_check_func{std::forward(invoke_checker)} { + } catch (...) { + if constexpr (ConstructionExceptionBehavior == exception_during_construction_behaviour::invoke_exit_func) { + exit_func(); + + // To throw? or not to throw? + throw; + } + } + + template + explicit constexpr scope_guard(EF&& exit_func) noexcept(std::is_nothrow_constructible_v && + std::is_nothrow_constructible_v) + requires(std::is_default_constructible_v && !std::is_same_v, scope_guard>) + try : exit_func{std::forward(exit_func)} { + } catch (...) { + if constexpr (ConstructionExceptionBehavior == exception_during_construction_behaviour::invoke_exit_func) { + exit_func(); + + // To throw? or not to throw? + throw; + } + } + + constexpr scope_guard(scope_guard&& rhs) noexcept(std::is_nothrow_move_constructible_v && + std::is_nothrow_move_constructible_v) + requires(HasRelease || HasStaticRelease) + : exit_func{std::move(rhs.exit_func)}, invoke_check_func{std::move(rhs.invoke_check_func)} { + // TODO: This does not work corectly for a shared invoke checker + // After a move will disable all. + + if constexpr (HasStaticRelease) { + InvokeChecker::release(); + } else { + rhs.release(); + } + } + + scope_guard(const scope_guard&) = delete; + scope_guard& operator=(const scope_guard&) = delete; + scope_guard& operator=(scope_guard&& rhs) = delete; + + constexpr ~scope_guard() noexcept(noexcept(exit_func()) && noexcept(invoke_check_func())) { + if (invoke_check_func()) { + exit_func(); + } + } + + InvokeChecker& invoke_checker() & noexcept { return invoke_checker; } + + constexpr void release() noexcept + // Shouldn't this "noexcept" be dependent on the noexcept of the release function? how?? + requires(HasRelease || HasStaticRelease) + { + if constexpr (HasRelease) { + invoke_check_func.release(); + } else { + InvokeChecker::release(); + } + } + + private: + ScopeExitFunc exit_func; + InvokeChecker invoke_check_func; +}; + +//====== + +// --- Specializations for no releaser + +template +class [[nodiscard]] scope_guard { + ScopeExitFunc exit_func; + + public: + template + explicit constexpr scope_guard(T&& exit_func) noexcept(std::is_nothrow_constructible_v) + requires(!std::is_same_v, scope_guard>) + try : exit_func(std::forward(exit_func)) { + } catch (...) { + exit_func(); + + throw; + } + + scope_guard(const scope_guard&) = delete; + scope_guard(scope_guard&&) = delete; + scope_guard& operator=(const scope_guard&) = delete; + scope_guard& operator=(scope_guard&&) = delete; + + constexpr ~scope_guard() noexcept(noexcept(exit_func())) { exit_func(); } +}; + +//====== + +template +class [[nodiscard]] scope_guard { + ScopeExitFunc exit_func; + + public: + template + explicit constexpr scope_guard(T&& exit_func) noexcept(std::is_nothrow_constructible_v) + requires(!std::is_same_v, scope_guard>) + : exit_func(std::forward(exit_func)) {} + + scope_guard(const scope_guard&) = delete; + scope_guard(scope_guard&&) = delete; + scope_guard& operator=(const scope_guard&) = delete; + scope_guard& operator=(scope_guard&&) = delete; + + constexpr ~scope_guard() noexcept(noexcept(exit_func())) { exit_func(); } +}; + +//================================================================================================== + +// --- Deduction guides --- + +template + requires(scope_exit_function && (scope_function_invoke_check)) +scope_guard(ExitFunc&&, InvokeChecker&&) -> scope_guard, std::decay_t, ecdb>; + +template + requires(scope_exit_function && + (scope_function_invoke_check || std::is_void_v)) +scope_guard(ExitFunc&&) -> scope_guard, InvokeChecker, ecdb>; + +//================================================================================================== + +class releaser { + public: + bool operator()() const { return can_invoke; } + + void release() { can_invoke = false; } + + private: + bool can_invoke = true; +}; + +//====== + +class releaseable_execute_when_no_exception { + public: + using DontInvokeOnCreationException = void; + + [[nodiscard]] bool operator()() const noexcept(noexcept(std::uncaught_exceptions())) { + return uncaught_on_creation >= std::uncaught_exceptions(); + } + + void release() { uncaught_on_creation = std::numeric_limits::min(); } + + private: + int uncaught_on_creation = std::uncaught_exceptions(); +}; + +//====== + +class releaseable_execute_only_when_exception { + public: + [[nodiscard]] bool operator()() const noexcept(noexcept(std::uncaught_exceptions())) { + return uncaught_on_creation < std::uncaught_exceptions(); + } + + void release() { uncaught_on_creation = std::numeric_limits::max(); } + + private: + int uncaught_on_creation = std::uncaught_exceptions(); +}; + +//================================================================================================== + +// --- type aliases --- + +template +using scope_exit = scope_guard; + +template +using scope_success = scope_guard; + +template +using scope_fail = scope_guard; } // namespace beman::scope +#endif // BEMAN_SCOPE__USE_STD_EXPERIMENTAL + #endif // BEMAN_SCOPE_HPP diff --git a/tests/scope_exit.test.cpp b/tests/scope_exit.test.cpp index 072ac38..65acf7a 100644 --- a/tests/scope_exit.test.cpp +++ b/tests/scope_exit.test.cpp @@ -9,7 +9,9 @@ #define CATCH_CONFIG_MAIN #include -using namespace beman::scope; + +using beman::scope::scope_exit; + TEST_CASE("scope_exit runs handler on normal scope exit", "[scope_exit]") { bool cleanup_ran = false; @@ -73,17 +75,17 @@ TEST_CASE("scope_exit supports move semantics", "[scope_exit]") { } TEST_CASE("moved-from scope_exit does not trigger handler", "[scope_exit]") { - bool cleanup_ran = false; + int cleanup_ran_count = 0; { scope_exit guard1([&]() { - cleanup_ran = true; + ++cleanup_ran_count; }); [[maybe_unused]] auto guard2 = std::move(guard1); // guard1 is now a no-op } - REQUIRE(cleanup_ran == true); // cleanup still runs — but from guard2 + REQUIRE(cleanup_ran_count == 1 ); // cleanup still runs — but from guard2 } TEST_CASE("scope_exit supports noexcept lambdas", "[scope_exit][advanced]") { diff --git a/tests/scope_fail.test.cpp b/tests/scope_fail.test.cpp index 3497a6a..47f4530 100644 --- a/tests/scope_fail.test.cpp +++ b/tests/scope_fail.test.cpp @@ -9,7 +9,8 @@ #define CATCH_CONFIG_MAIN #include -using namespace beman::scope; +using beman::scope::scope_fail; +using beman::scope::scope_success; TEST_CASE("scope_fail runs handler on exception", "[scope_fail]") { bool triggered = false; @@ -187,11 +188,11 @@ TEST_CASE("scope_fail coexists with scope_success", "[scope_fail][advanced]") { std::string output; try { - std::experimental::scope_success success([&]() { + scope_success success([&]() { output += "success "; }); - std::experimental::scope_fail fail([&]() { + scope_fail fail([&]() { output += "fail "; });