{"id":23236765,"url":"https://github.com/pratikpc/scoped-exit-cxx20","last_synced_at":"2025-04-05T21:17:25.256Z","repository":{"id":125288916,"uuid":"343326312","full_name":"pratikpc/scoped-exit-cxx20","owner":"pratikpc","description":"Run something at the end of a scope with exception support","archived":false,"fork":false,"pushed_at":"2021-03-02T08:10:57.000Z","size":17,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"develop","last_synced_at":"2025-02-12T00:38:44.086Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pratikpc.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-03-01T07:25:50.000Z","updated_at":"2022-11-18T03:46:52.000Z","dependencies_parsed_at":null,"dependency_job_id":"aa2d5ec3-d96b-48bc-a521-bede9d5cdd23","html_url":"https://github.com/pratikpc/scoped-exit-cxx20","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pratikpc%2Fscoped-exit-cxx20","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pratikpc%2Fscoped-exit-cxx20/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pratikpc%2Fscoped-exit-cxx20/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pratikpc%2Fscoped-exit-cxx20/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pratikpc","download_url":"https://codeload.github.com/pratikpc/scoped-exit-cxx20/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247399897,"owners_count":20932881,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-19T04:12:38.962Z","updated_at":"2025-04-05T21:17:25.250Z","avatar_url":"https://github.com/pratikpc.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Scoped Exit C++20\n\nA better version of [Microsoft GSL](https://github.com/microsoft/GSL)'s [Finally and final_action](https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#gslutil-utilities) utilizing C++17 (and [requires](https://en.cppreference.com/w/cpp/keyword/requires) from C++20. Although can easily be replaced by [std::enable_if_t](https://en.cppreference.com/w/cpp/types/enable_if))\n\n---\n\n## What are the differences?\n\n1. Microsoft's implementation supports C++14.  \n    Our's does not.  \n    All of our advantages can be simplified as, we do not support C++14  \n    If your codebase needs to _support C++14_, please **use** Microsoft GSL\n2. C++17 introduces [CTAD](https://en.cppreference.com/w/cpp/language/class_template_argument_deduction) which means the `finally` helper is no longer required\n3. `final_action` contains a variable `invoke_` which, I don't think is very useful\n4. `final_action` can also be moved  \n   The only usecase of a movable `final_action` is to make it possible for `finally` to return\n6. With [CTAD](https://en.cppreference.com/w/cpp/language/class_template_argument_deduction), that use case is outdated\n7. The destructor is always noexcept  \n   This seems like a good idea at first  \n   But then, we must always realize that the scoped_exit is not always called in the main function  \n   And sometimes, even the best of Cleanup codes throw  \n   On Cleanup being called during a stack unwinding after an exception throw, we us `std::uncaught_exceptions` to check if there are any exceptions propagating  \n   And disable a call to cleanup  \n   If there are low chances of this happening, we can disable this  \n   You only pay for what you use  \n8. At the same time, we need to ensure that, genuine users who need noexcept support should also receive it?\n9. This is easily solved with [std::is_nothrow_invocable](https://en.cppreference.com/w/cpp/types/is_invocable)   \n   Microsoft's GSL is not using this because this was introduced in C++17\n\n---\n\n## [Usage](sample/run.cxx)\n\n### No Throw\n\n```cpp\n#include \u003cscoped_exit.hxx\u003e\n#include \u003ciostream\u003e\n\nvoid no_throw()\n{\n   // Note that as the Function provided here is noexcept\n   // So will our destructor\n\n   // Note that throwing in a nothrow lambda\n   // Will lead to noexcept semantics\n   pc::scoped_exit const nothrow{\n       []() noexcept { std::cout \u003c\u003c \"Perform cleanup here\\n\"; }};\n   std::cout \u003c\u003c \"Function Started\\n\";\n   std::cout \u003c\u003c \"Function Ended\\n\";\n}\n```\n\n### Throw\n\n```cpp\n#include \u003cscoped_exit.hxx\u003e\n#include \u003ciostream\u003e\n\nvoid throw_but_catch()\n{\n   try\n   {\n      // GSL destructor is noexcept\n      // But our cleanup may throw an exception\n      // Which we might catch later\n\n      pc::scoped_exit const throws{[]() noexcept(false) {\n         std::cout \u003c\u003c \"During Cleanup, we had to throw\\n\";\n         throw std::runtime_error(\"Sorry. Something during Cleanup errored out\");\n      }};\n      std::cout \u003c\u003c \"Block Started\\n\";\n      std::cout \u003c\u003c \"Block Ended\\n\";\n      // Destructor called here\n   }\n   catch (std::runtime_error const\u0026 ex)\n   {\n      std::cout \u003c\u003c \"Exception caught : \" \u003c\u003c ex.what() \u003c\u003c \"\\n\";\n   }\n}\n```\n\n### Outer throw leads to cleanup not being called\n\n```cpp\n// The cleanup will not be called due to the exception\nvoid throw_outside_cleanup_not_called()\n{\n   try\n   {\n      // GSL destructor is noexcept\n      // But our cleanup may throw an exception\n      // Which we might catch later\n\n      pc::scoped_exit const canThrow{\n          []() {\n             std::cout \u003c\u003c \"As there was an exception outside, this was not called\\n\";\n          }};\n      std::cout \u003c\u003c \"Block Started\\n\";\n      throw std::runtime_error(\"We threw an exception. As there is an exception present, cleanup won't be called\");\n      std::cout \u003c\u003c \"Block Ended\\n\";\n      // Destructor called here\n   }\n   catch (std::runtime_error const\u0026 ex)\n   {\n      std::cout \u003c\u003c \"Exception caught : \" \u003c\u003c ex.what() \u003c\u003c \"\\n\";\n   }\n}\n```\n\n### Don't check for uncaught exceptions in case chances of them being thrown are low\n\n```cpp\n// Let us assume that there is a very low risk of the rest of our code throwing\n// But our cleanup might certainly throw\n// One way to handle this would be to\n// Use the default method\n// But the major issue with the default method is\n// It's more safer and checks for uncaught_exceptions\n// However if you wish to disable the check, you can use ignore_uncaught_exceptions\nvoid dont_check_for_uncaught_exceptions()\n{\n   try\n   {\n      // GSL destructor is noexcept\n      // But our cleanup may throw an exception\n      // Which we might catch later\n\n      pc::scoped_exit const throws{\n          []() {\n             std::cout \u003c\u003c \"During Cleanup, we had to throw\\n\";\n             throw std::runtime_error(\n                 \"As an exception throw outside was unlikely, we disabled the check. It \"\n                 \"worked. We threw. Outside did not\");\n          },\n          pc::dont_check_for_uncaught_exceptions};\n      std::cout \u003c\u003c \"Block Started\\n\";\n      std::cout \u003c\u003c \"Block Ended\\n\";\n      // Destructor called here\n   }\n   catch (std::runtime_error const\u0026 ex)\n   {\n      std::cout \u003c\u003c \"Exception caught : \" \u003c\u003c ex.what() \u003c\u003c \"\\n\";\n   }\n}\n```\n\n---\n\n## [Our code](header/scoped_exit.hxx)\n\n```cpp\n#pragma once\n\n#include \u003cexception\u003e\n#include \u003ctype_traits\u003e\n#include \u003cutility\u003e\n\nnamespace pc\n{\n   // Let us say that our F can throw\n   // Then in that case, our code will check for uncaught exceptions\n   // However, if you are sure outer scope will not throw at all\n   // Then you can reduce the cost of the check\n   auto inline constexpr dont_check_for_uncaught_exceptions = std::false_type{};\n\n   template \u003ctypename F, bool checkForUncaughtExceptions = true\u003e\n   // Change Requires to an ugly enable_if_t or a static_assert for C++17 support\n   requires(std::is_invocable_v\u003cF\u003e) struct scoped_exit\n   {\n      // Store the function only\n      // No need to store a 2nd variable\n      F const f;\n\n      constexpr explicit scoped_exit(\n          F\u0026\u0026 f,\n          // By default check for uncaught exceptions\n          // If the Cleanup function is called during\n          // An uncaught exception\n          // This would ensure that the cleanup is not run\n          // However, in case we wish to disable this check at compile-time\n          // We can\n          const std::bool_constant\u003ccheckForUncaughtExceptions\u003e =\n              std::true_type{}) noexcept\n          // If the function is nothrow invocable\n          // Then modifying the checkForUncaughtExceptions is useless\n          // So don't let user modify the param\n          requires(!(!checkForUncaughtExceptions \u0026\u0026 std::is_nothrow_invocable_v\u003cF\u003e)) :\n          f(std::forward\u003cF\u003e(f))\n      {\n      }\n\n      ~scoped_exit() noexcept(\n          // GSL Destructors are always noexcept\n          // But sometimes, we might have to throw an exception\n          // It is a convenience factor\n          std::is_nothrow_invocable_v\u003cF\u003e)\n      {\n         // Check if can be invoked without throwing\n         // Or user is sure, the chances of a double throw are low\n         // Risky. As such checkForUncaughtExceptions is set to true by default\n         if constexpr (std::is_nothrow_invocable_v\u003cF\u003e || !checkForUncaughtExceptions)\n            f();\n         else\n             // Run cleanup only when there are no uncaught_exceptions\n             if (std::uncaught_exceptions() == 0)\n            [[likely]] f();\n      }\n\n      // Disable move \u0026 copy\n      scoped_exit(const scoped_exit\u0026) = delete;\n      scoped_exit(scoped_exit\u0026\u0026)      = delete;\n      scoped_exit\u0026 operator=(scoped_exit\u0026\u0026) = delete;\n      scoped_exit\u0026 operator=(const scoped_exit\u0026) = delete;\n   };\n\n   // Ensure naming convention matches GSL\n   // To make switching from GSL to pc::scoped_exit easy\n   template \u003ctypename F\u003e\n   using finally = scoped_exit\u003cF\u003e;\n   template \u003ctypename F\u003e\n   using final_action = scoped_exit\u003cF\u003e;\n} // namespace pc\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpratikpc%2Fscoped-exit-cxx20","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpratikpc%2Fscoped-exit-cxx20","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpratikpc%2Fscoped-exit-cxx20/lists"}