diff --git a/sup/include/sup/scope_exit.hpp b/sup/include/sup/scope_exit.hpp index 67faecebd47974f47c484dc4a7231c92fa0343ac..acba9da5a96d5f842f52f1dbe577a35c7ee151b4 100644 --- a/sup/include/sup/scope_exit.hpp +++ b/sup/include/sup/scope_exit.hpp @@ -1,5 +1,6 @@ #pragma once +#include <functional> #include <type_traits> #include <utility> @@ -42,9 +43,34 @@ public: } }; +// std::function is not nothrow move constructable before C++20, so, er, cheat. +namespace impl { + template <typename R> + struct wrap_std_function { + std::function<R ()> f; + + wrap_std_function() noexcept {} + wrap_std_function(const std::function<R ()>& f): f(f) {} + wrap_std_function(std::function<R ()>&& f): f(std::move(f)) {} + wrap_std_function(wrap_std_function&& other) noexcept { + try { + f = std::move(other.f); + } + catch (...) {} + } + + void operator()() const { f(); } + }; +} + template <typename F> -scope_exit<std::decay_t<F>> on_scope_exit(F&& f) { +auto on_scope_exit(F&& f) { return scope_exit<std::decay_t<F>>(std::forward<F>(f)); } +template <typename R> +auto on_scope_exit(std::function<R ()> f) { + return on_scope_exit(impl::wrap_std_function<R>(std::move(f))); +} + } // namespace sup diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index c78bf48c894c1d3341a13f835e6602ca03222588..b89cece7d93a51260b005155aa18cd37948d85aa 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -94,6 +94,7 @@ set(unit_sources test_schedule.cpp test_spike_source.cpp test_local_context.cpp + test_scope_exit.cpp test_simd.cpp test_span.cpp test_spikes.cpp diff --git a/test/unit/test_scope_exit.cpp b/test/unit/test_scope_exit.cpp new file mode 100644 index 0000000000000000000000000000000000000000..12b8c140aed5dbb1f32f58ec202e2c9ed97157ad --- /dev/null +++ b/test/unit/test_scope_exit.cpp @@ -0,0 +1,49 @@ +#include <functional> + +#include "../gtest.h" + +#include <sup/scope_exit.hpp> + +using sup::on_scope_exit; + +TEST(scope_exit, basic) { + bool a = false; + { + auto guard = on_scope_exit([&a] { a = true; }); + EXPECT_FALSE(a); + } + EXPECT_TRUE(a); +} + +TEST(scope_exit, noexceptcall) { + auto guard1 = on_scope_exit([] {}); + using G1 = decltype(guard1); + EXPECT_FALSE(noexcept(guard1.~G1())); + + auto guard2 = on_scope_exit([]() noexcept {}); + using G2 = decltype(guard2); + EXPECT_TRUE(noexcept(guard2.~G2())); +} + +TEST(scope_exit, function) { + // on_scope_exit has a special overload for std::function + // to work around its non-noexcept move ctor. + bool a = false; + std::function<void ()> setter = [&a] { a = true; }; + + { + auto guard = on_scope_exit(setter); + EXPECT_FALSE(a); + } + EXPECT_TRUE(a); + + a = false; + std::function<int ()> setter2 = [&a] { a = true; return 3; }; + + { + auto guard = on_scope_exit(setter2); + EXPECT_FALSE(a); + } + EXPECT_TRUE(a); +} +