1// Copyright 2018 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#pragma once
6
7#include <new>
8#include <utility>
9
10#include "traits_internal.h"
11
12namespace fit {
13
14// A move-only deferred action wrapper with RAII semantics.
15// This class is not thread safe.
16//
17// The wrapper holds a function-like callable target with no arguments
18// which it invokes when it goes out of scope unless canceled, called, or
19// moved to a wrapper in a different scope.
20//
21// See |fit::defer()| for idiomatic usage.
22template <typename T>
23class deferred_action {
24public:
25    // Creates a deferred action without a pending target.
26    deferred_action()
27        : pending_(false) {}
28
29    // Creates a deferred action with a pending target.
30    explicit deferred_action(T target) {
31        if (fit::internal::is_null(target)) {
32            pending_ = false;
33        } else {
34            pending_ = true;
35            new (&target_) T(std::move(target));
36        }
37    }
38
39    // Creates a deferred action with a pending target moved from another
40    // deferred action, leaving the other one without a pending target.
41    deferred_action(deferred_action&& other) {
42        move_from(std::move(other));
43    }
44
45    // Invokes and releases the deferred action's pending target (if any).
46    ~deferred_action() {
47        call();
48    }
49
50    // Returns true if the deferred action has a pending target.
51    explicit operator bool() const {
52        return pending_;
53    }
54
55    // Invokes and releases the deferred action's pending target (if any),
56    // then move-assigns it from another deferred action, leaving the latter
57    // one without a pending target.
58    deferred_action& operator=(deferred_action&& other) {
59        call();
60        move_from(std::move(other));
61        return *this;
62    }
63
64    // Invokes and releases the deferred action's pending target (if any).
65    void call() {
66        if (pending_) {
67            // Move to a local to guard against re-entrance.
68            T local_target = std::move(target_);
69            pending_ = false;
70            target_.~T();
71            local_target();
72        }
73    }
74
75    // Releases the deferred action's pending target (if any) without
76    // invoking it.
77    void cancel() {
78        if (pending_) {
79            pending_ = false;
80            target_.~T();
81        }
82    }
83
84    deferred_action(const deferred_action& other) = delete;
85    deferred_action& operator=(const deferred_action& other) = delete;
86
87private:
88    void move_from(deferred_action&& other) {
89        if (this == &other)
90            return;
91        pending_ = other.pending_;
92        if (pending_) {
93            new (&target_) T(std::move(other.target_));
94            other.target_.~T();
95            other.pending_ = false;
96        }
97    }
98
99    bool pending_;
100
101    // Storage for the target.
102    // The target is only initialized when the call is pending.
103    union {
104        T target_;
105    };
106};
107
108// Defers execution of a function-like callable target with no arguments
109// unless the value returned by this function goes out of scope unless canceled,
110// called, or moved to a wrapper in a different scope.
111//
112// // This example prints "Hello..." then "Goodbye!".
113// void test() {
114//     auto d = fit::defer([]{ puts("Goodbye!"); });
115//     puts("Hello...");
116// }
117//
118// // This example prints nothing because the deferred action is canceled.
119// void do_nothing() {
120//     auto d = fit::defer([]{ puts("I'm not here."); });
121//     d.cancel();
122// }
123//
124// // This example shows how the deferred action can be reassigned assuming
125// // the new target has the same type and the old one, in this case by
126// // representing the target as a |fit::closure|.
127// void reassign() {
128//     auto d = fit::defer<fit::closure>([] { puts("This runs first."); });
129//     d = fit::defer<fit::closure>([] { puts("This runs afterwards."); });
130// }
131template <typename T>
132inline deferred_action<T> defer(T target) {
133    return deferred_action<T>(std::move(target));
134}
135
136} // namespace fit
137