halcheck 1.0
Loading...
Searching...
No Matches
effect.hpp
1#ifndef HALCHECK_LIB_EFFECT_HPP
2#define HALCHECK_LIB_EFFECT_HPP
3
10#include <halcheck/lib/functional.hpp>
11#include <halcheck/lib/optional.hpp>
12#include <halcheck/lib/scope.hpp>
13#include <halcheck/lib/tuple.hpp>
14#include <halcheck/lib/type_traits.hpp>
15#include <halcheck/lib/utility.hpp>
16
17#include <algorithm>
18#include <cassert>
19#include <cstddef>
20#include <type_traits>
21#include <vector>
22
23namespace halcheck { namespace lib {
24
25#ifdef HALCHECK_DOXYGEN
32lib::effect_result_t<T> fallback(T args);
33#endif
34
35static const class {
36private:
37 template<typename T>
38 using member_t = decltype(std::declval<const T &>().fallback());
39
40 template<typename T>
41 using free_t = decltype(fallback(std::declval<const T &>()));
42
43public:
44 template<typename T>
45 member_t<T> operator()(T args) const {
46 return std::move(args).fallback();
47 }
48
49 template<typename T, HALCHECK_REQUIRE(!lib::is_detected<member_t, T>())>
50 free_t<T> operator()(T args) const {
51 return fallback(std::move(args));
52 }
53} fallback;
54
60template<typename T>
61using effect_result_t = lib::invoke_result_t<decltype(lib::fallback), const T &>;
62
72template<typename T>
73struct is_effect : lib::is_detected<lib::effect_result_t, T> {};
74
79class effect {
80private:
81 static std::size_t next();
82
83 template<typename T>
84 static std::size_t index() {
85 static const std::size_t output = next();
86 return output;
87 }
88
89 struct entry {
90 void *func;
92 };
93
95
96 static const context empty;
97 static thread_local const context *current;
98
99 struct clone_effect {
100 lib::move_only_function<lib::finally_t<>(bool)> fallback() const;
101 };
102
103 template<typename Effect>
104 struct base {
105 static_assert(lib::is_effect<Effect>(), "Effect must satisfy is_effect");
106 virtual lib::effect_result_t<Effect> operator()(Effect args) = 0;
107 };
108
109public:
117 template<typename T, HALCHECK_REQUIRE(lib::is_effect<T>())>
119 const std::size_t i = index<T>();
120 if (i < current->size() && (*current)[i]) {
121 auto &entry = *(*current)[i];
122 assert(entry.func && "entry.func should not be null");
123 assert(entry.context && "entry.context should not be null");
124 auto old = lib::exchange(current, entry.context);
125 auto _ = lib::finally([&] { current = old; });
126 return (*reinterpret_cast<base<T> *>(entry.func))(std::move(args));
127 } else {
128 auto old = lib::exchange(current, &empty);
129 auto _ = lib::finally([&] { current = old; });
130 return lib::fallback(std::move(args));
131 }
132 }
133
142 template<
143 typename T,
144 typename... Args,
147 static lib::effect_result_t<T> invoke(Args &&...args) {
148 return invoke(T{std::forward<Args>(args)...});
149 }
150
157 template<typename Self, typename... Effects>
158 class handler : private base<clone_effect>, private base<Effects>... {
159 private:
160 class reset {
161 private:
162 friend class handler;
163
164 context next;
165 const context *old;
166
167 explicit reset(handler *self) : next(self->install()), old(lib::exchange(current, &next)) {}
168
169 public:
170 reset(reset &&other) noexcept : next(std::move(other.next)), old(other.old) {
171 assert(current == &other.next);
172 current = &next;
173 }
174
175 reset(const reset &) = delete;
176 reset &operator=(reset &&) = delete;
177 reset &operator=(const reset &) = delete;
178 ~reset() = default;
179
180 void operator()() const { current = old; }
181 };
182
183 class owning_reset {
184 private:
185 friend class handler;
186
187 Self self;
188 context next;
189 const context *old;
190
191 explicit owning_reset(handler *self)
192 : self(std::move(*static_cast<Self *>(self))), next(this->self.install()),
193 old(lib::exchange(current, &next)) {}
194
195 public:
196 owning_reset(owning_reset &&other) noexcept(std::is_nothrow_move_constructible<Self>())
197 : self(std::move(other.self)), next((current = other.old, self.install())),
198 old(lib::exchange(current, &next)) {}
199
200 owning_reset(const owning_reset &) = delete;
201 owning_reset &operator=(owning_reset &&) = delete;
202 owning_reset &operator=(const owning_reset &) = delete;
203 ~owning_reset() = default;
204
205 void operator()() const { current = old; }
206 };
207
208 public:
211
214
220 scope handle() & { return lib::finally(reset(this)); }
221
227 owning_scope handle() && { return lib::finally(owning_reset(this)); }
228
240 template<
241 typename F,
242 typename... Args,
245 lib::invoke_result_t<F, Args...> handle(F func, Args &&...args) {
246 auto _ = handle();
247 return lib::invoke(std::move(func), std::forward<Args>(args)...);
248 }
249
250 private:
251 context install() {
252 auto output = *current;
253 output.resize(std::max({output.size(), index<clone_effect>(), index<Effects>()...}) + 1);
254 output[index<clone_effect>()] = entry{static_cast<base<clone_effect> *>(this), current};
255 lib::ignore = {(output[index<Effects>()] = entry{static_cast<base<Effects> *>(this), current})...};
256 return output;
257 }
258
259 lib::effect_result_t<clone_effect> operator()(clone_effect) final {
260 using namespace std::placeholders;
261 return std::bind(
262 [](bool owning, lib::effect_result_t<clone_effect> &outer, handler &copy) -> lib::finally_t<> {
263 auto handler1 = outer(owning);
264 if (owning) {
265 return std::move(handler1) + std::move(copy).handle();
266 } else {
267 return std::move(handler1) + copy.handle();
268 }
269 },
270 _1,
272 *static_cast<Self *>(this));
273 }
274 };
275
279 class state {
280 public:
288 state() = default;
289
297
303
309
323 template<typename F, typename... Args, HALCHECK_REQUIRE(lib::is_invocable<F, Args...>())>
324 lib::invoke_result_t<F, Args...> handle(F func, Args &&...args) {
325 auto _ = handle();
326 return lib::invoke(std::move(func), std::forward<Args>(args)...);
327 }
328
329 private:
330 lib::move_only_function<lib::finally_t<>(bool)> _impl;
331 };
332
337 static state save() { return state(lib::in_place); }
338};
339
340}} // namespace halcheck::lib
341
342#endif
T bind(T... args)
lib::invoke_result_t< F, Args... > handle(F func, Args &&...args)
Invokes a function, handling any effects using this handler.
Definition effect.hpp:245
scope handle() &
Overrides the behaviour of a set of effects.
Definition effect.hpp:220
owning_scope handle() &&
Overrides the behaviour of a set of effects.
Definition effect.hpp:227
An effect handler defines the behaviour of a set of effects.
Definition effect.hpp:158
lib::invoke_result_t< F, Args... > handle(F func, Args &&...args)
Invokes a function, handling any effects using this state.
Definition effect.hpp:324
lib::finally_t handle() &&
Overrides the current set of effect handlers.
state(lib::in_place_t)
Copies the current set of effect handlers.
lib::finally_t handle() &
Overrides the current set of effect handlers.
state()=default
Constructs the default state object.
A state determines the behaviour of all effects.
Definition effect.hpp:279
static state save()
Copies the current set of effect handlers.
Definition effect.hpp:337
static lib::effect_result_t< T > invoke(Args &&...args)
Invokes an effect.
Definition effect.hpp:147
static lib::effect_result_t< T > invoke(T args)
Invokes an effect.
Definition effect.hpp:118
Provides operations for simulating scoped-algebraic effects (a.k.a. resumable exceptions....
Definition effect.hpp:79
Calls a function upon destruction.
Definition scope.hpp:26
T copy(T... args)
T declval(T... args)
T forward(T... args)
lib::invoke_result_t< decltype(lib::fallback), const T & > effect_result_t
Evaluates to the result type of the given effect.
Definition effect.hpp:61
HALCHECK_INLINE_CONSTEXPR struct halcheck::lib::@20 invoke
An implementation of std::invoke.
decltype(lib::invoke(std::declval< F >(), std::declval< Args >()...)) invoke_result_t
An implementation of std::invoke_result_t.
Definition invoke.hpp:42
HALCHECK_INLINE_CONSTEXPR struct halcheck::lib::@26 empty
Determines if a range is empty.
HALCHECK_INLINE_CONSTEXPR struct halcheck::lib::@25 size
Obtains the size of a range.
lib::finally_t< F > finally(F func)
Definition scope.hpp:157
static const lib::ignore_t ignore
A version of std::ignore usable with initializer lists.
Definition tuple.hpp:38
#define HALCHECK_REQUIRE(...)
Expands to a template argument that is only valid if the given argument evaluates to true.
Definition type_traits.hpp:24
T exchange(T &value, U &&next)
An implementation of std::exchange.
Definition utility.hpp:41
static constexpr in_place_t in_place
An implementation of std::in_place.
Definition utility.hpp:33
T max(T... args)
T resize(T... args)
An implementation of std::conjunction.
Definition type_traits.hpp:59
An implementation of std::in_place_t.
Definition utility.hpp:26
Determines if a type is constructible from a given set of argument types using initiailizer-list-styl...
Definition type_traits.hpp:280
An implementation of std::experimental::is_detected.
Definition type_traits.hpp:247
Determines if a given type is an effect.
Definition effect.hpp:73
An implementation of std::is_invocable.
Definition invoke.hpp:66