Skip to content

Commit b05238e

Browse files
add some noexcept
1 parent df58812 commit b05238e

File tree

3 files changed

+74
-32
lines changed

3 files changed

+74
-32
lines changed

examples/scope_example.cpp

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,20 @@
33
#include <beman/scope/scope.hpp>
44

55
#include <cstdlib>
6+
#include <format>
67
#include <iostream>
78
#include <string_view>
89

910
namespace scope = beman::scope;
1011

1112
void print_exit_status(std::string_view name, bool exit_status, bool did_throw) {
12-
std::cout << name << ":\n";
13-
std::cout << " Exception thrown: " << (did_throw ? "yes" : "no") << "\n";
14-
std::cout << " Exit function: " << (exit_status ? "invoked" : "not invoked") << "\n\n";
13+
std::cout << std::format("{}:\n"
14+
" Exception thrown: {}\n"
15+
" Exit function: {}\n"
16+
"\n",
17+
name,
18+
(did_throw ? "yes" : "no"),
19+
(exit_status ? "invoked" : "not invoked"));
1520
}
1621

1722
// Randomly throw an exception (50% chance)
@@ -22,7 +27,8 @@ void maybe_throw() {
2227
}
2328

2429
int main() {
25-
bool is_exit_func_invoked{false}, is_exception_thrown{false};
30+
bool is_exit_func_invoked {false};
31+
bool is_exception_thrown {false};
2632

2733
// Manual handling at "end of scope"
2834
try {

include/beman/scope/scope.hpp

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,11 @@ concept HasStaticRelease = requires {
6767
};
6868

6969
template <typename F, typename R, typename... Args>
70-
concept invocable_return = std::invocable<F, Args...> && std::same_as<std::invoke_result_t<F, Args...>, R>;
70+
concept invocable_return = std::invocable<F, Args...> && std::convertible_to<std::invoke_result_t<F, Args...>, R>;
71+
72+
template <typename F>
73+
concept scope_exit_function =
74+
invocable_return<F, void> && std::is_nothrow_move_constructible_v<F> && std::is_copy_constructible_v<F>;
7175

7276
template <typename T>
7377
concept scope_invoke_checker = HasStaticCanInvoke<T> || HasCanInvoke<T> || invocable_return<T, bool>;
@@ -78,19 +82,24 @@ struct ExecuteAlways;
7882

7983
//=========================================================
8084

81-
template <invocable_return<void> ExitFunc, scope_invoke_checker InvokeChecker = ExecuteAlways>
85+
template <scope_exit_function ExitFunc, scope_invoke_checker InvokeChecker = ExecuteAlways>
8286
class scope_guard {
8387
public:
84-
explicit scope_guard(ExitFunc&& exit_func) /*noexcept(see below)*/
85-
: m_exit_func(std::move(exit_func)) //
86-
{}
88+
explicit constexpr scope_guard(ExitFunc&& exit_func) noexcept(std::is_nothrow_constructible_v<ExitFunc>) //
89+
try
90+
: m_exit_func(std::forward<ExitFunc>(exit_func)) //
91+
{
92+
} catch (...) {
93+
m_exit_func();
94+
}
8795

88-
explicit scope_guard(ExitFunc&& exit_func, InvokeChecker&& invoke_checker) /*noexcept(see below)*/
89-
: m_exit_func(std::move(exit_func)),
90-
m_invoke_checker{std::move(invoke_checker)} //
96+
explicit constexpr scope_guard(ExitFunc&& exit_func, InvokeChecker&& invoke_checker) noexcept(
97+
std::is_nothrow_constructible_v<ExitFunc> && std::is_nothrow_constructible_v<InvokeChecker>)
98+
: m_exit_func(std::forward<ExitFunc>(exit_func)),
99+
m_invoke_checker{std::forward<InvokeChecker>(invoke_checker)} //
91100
{}
92101

93-
explicit scope_guard(scope_guard&& rhs) noexcept
102+
explicit constexpr scope_guard(scope_guard&& rhs) noexcept
94103
: m_exit_func{std::move(rhs)},
95104
m_invoke_checker{std::move(rhs.m_invoke_checker)} //
96105
{}
@@ -99,13 +108,13 @@ class scope_guard {
99108
scope_guard& operator=(const scope_guard&) = delete;
100109
scope_guard& operator=(scope_guard&&) = delete;
101110

102-
~scope_guard() /*noexcept(see below)*/ {
103-
if (can_invoke_check(m_invoke_checker)) {
111+
constexpr ~scope_guard() noexcept(noexcept(m_exit_func())) {
112+
if (check_can_invoke(m_invoke_checker)) {
104113
m_exit_func();
105114
}
106115
}
107116

108-
void release() noexcept
117+
constexpr void release() noexcept // Shouldn't this noexcept be dependent on the noexcept of the release function?
109118
requires HasRelease<InvokeChecker> || HasStaticRelease<InvokeChecker>
110119
{
111120
if constexpr (HasRelease<InvokeChecker>) {
@@ -116,23 +125,17 @@ class scope_guard {
116125
}
117126

118127
private:
119-
ExitFunc m_exit_func;
128+
[[no_unique_address]] ExitFunc m_exit_func;
120129
[[no_unique_address]] InvokeChecker m_invoke_checker;
121130

122131
template <typename T>
123-
static bool can_invoke_check(const T& obj) {
132+
static constexpr bool check_can_invoke(const T& obj) {
124133
if constexpr (HasStaticCanInvoke<T>) {
125134
return T::can_invoke();
126135
} else if constexpr (HasCanInvoke<T>) {
127136
return obj.can_invoke();
128-
} else if constexpr (invocable_return<T, bool>) {
129-
return std::invoke(obj);
130-
//} else if constexpr (HasStaticParenthesisOperator<T>) {
131-
// return T::operator()();
132-
//} else if constexpr (HasParenthesisOperator<T>) {
133-
// return obj();
134137
} else {
135-
return true; // Default behavior if no check function is available
138+
return std::invoke(obj);
136139
}
137140
}
138141
};

tests/beman/scope/scope.test.cpp

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,54 @@
1212

1313
TEST_CASE("scope_guard") {
1414
SECTION("Constructing") {
15+
SECTION("lambdas") {
1516

16-
auto exit_guard = beman::scope::scope_guard{[] {}, [] { return true; }};
17-
// vvv doesn't compile (as planned)- NOT releasable
18-
// exit_guard.release();
17+
auto exit_guard0 = beman::scope::scope_guard{[] {}};
1918

20-
REQUIRE(sizeof(decltype(exit_guard)) == 1);
19+
auto exit_guard1 = beman::scope::scope_guard{[] {}, [] { return true; }};
2120

22-
// vvv Doesn't compile (as planned), return must be bool
23-
// auto exit_guard2 = beman::scope::scope_guard{[] {}, [] { return 1; }};
21+
// vvv doesn't compile (as planned)- NOT releasable
22+
// exit_guard1.release();
23+
// ^^^
2424

25-
REQUIRE(true);
25+
auto exit_guard2 = beman::scope::scope_guard{[] {}, [] { return 1; }};
26+
27+
REQUIRE(true);
28+
}
29+
30+
SECTION("Function object") {
31+
struct FunctionObject {
32+
void operator()() {
33+
is_executed = true;
34+
invoked_count++;
35+
}
36+
37+
bool is_executed = false;
38+
int invoked_count = 0;
39+
40+
} exit_func_onj;
41+
42+
{
43+
auto exit_guard3_1 = beman::scope::scope_guard{exit_func_onj};
44+
auto exit_guard3_2 = beman::scope::scope_guard{exit_func_onj};
45+
auto exit_guard3_3 = beman::scope::scope_guard{exit_func_onj};
46+
auto exit_guard3_4 = beman::scope::scope_guard{exit_func_onj};
47+
auto exit_guard3_5 = beman::scope::scope_guard{exit_func_onj};
48+
}
49+
50+
REQUIRE(exit_func_onj.is_executed == true);
51+
REQUIRE(exit_func_onj.invoked_count == 5);
52+
}
2653
}
2754
}
2855

2956
TEST_CASE("scope_exit") {
3057

3158
SECTION("Constructing") {
3259
beman::scope::scope_exit exit_guard1([] {});
60+
3361
// beman::scope::scope_exit exit_guard2 = [] {}; // can't do: no conversion
62+
3463
// beman::scope::scope_exit exit_guard3 = {[] {}}; // can't do: explict constructor
3564

3665
auto exit_guard4 = beman::scope::scope_exit([] {});
@@ -115,7 +144,9 @@ TEST_CASE("scope_fail") {
115144

116145
SECTION("Constructing") {
117146
beman::scope::scope_fail exit_guard1([] {});
147+
118148
// beman::scope::scope_fail exit_guard2 = [] {}; // can't do: no conversion
149+
119150
// beman::scope::scope_fail exit_guard3 = {[] {}}; // can't do: explict constructor
120151

121152
auto exit_guard4 = beman::scope::scope_fail([] {});
@@ -198,7 +229,9 @@ TEST_CASE("scope_success") {
198229

199230
SECTION("Constructing") {
200231
beman::scope::scope_success exit_guard1([] {});
232+
201233
// beman::scope::scope_success exit_guard2 = [] {}; // can't do: no conversion
234+
202235
// beman::scope::scope_success exit_guard3 = {[] {}}; // can't do: explict constructor
203236

204237
auto exit_guard4 = beman::scope::scope_success([] {});

0 commit comments

Comments
 (0)