Skip to content
thinlang edited this page Apr 16, 2024 · 11 revisions

Implementation of a defer macro for library consideration.

#include <iostream>
#include <utility>

using namespace std;

struct annotate {
    annotate() { cout << "annotate ctor\n"; }
    annotate(const annotate&) { cout << "annotate copy-ctor\n"; }
    annotate(annotate&&) noexcept { cout << "annotate move-ctor\n"; }
    annotate& operator=(const annotate&) { cout << "annotate copy-assign\n"; return *this; }
    annotate& operator=(annotate&&) noexcept { cout << "annotate move-assign\n"; return *this; }
    ~annotate() { cout << "annotate dtor\n"; }
    friend inline void swap(annotate&, annotate&) { cout << "annotate swap\n"; }
    friend inline bool operator==(const annotate&, const annotate&) { return true; }
    friend inline bool operator!=(const annotate&, const annotate&) { return false; }
};

annotate g() {
    return annotate{};
}
void f(annotate) { }

template <class F, class Require = std::enable_if_t<std::is_nothrow_invocable_v<F>>>
struct defer {
    F _f;
    defer(F&& f) : _f{std::move(f)} { }
    ~defer() { _f(); }
};

struct no_return{ };

#define STLAB_MACRO_CONCAT_(X, Y) X##Y
#define STLAB_MACRO_CONCAT(X, Y) STLAB_MACRO_CONCAT_(X, Y)
#define STLAB_ESCAPE(...) __VA_ARGS__
#define STLAB_STRIP_PARENS(X) X
#define STLAB_DEFER(F)                                                             \
    defer STLAB_MACRO_CONCAT(stlab_defer_, __LINE__){[&]() noexcept -> no_return { \
        { STLAB_STRIP_PARENS(STLAB_ESCAPE F) }                                     \
        return no_return{};                                                        \
    }};

int main() {
    int i{42};
    STLAB_DEFER((cout << i << "\n";))
    STLAB_DEFER((cout << i << "\n";))
    i = 54;
    cout << "End of function\n";
}

Alternative suggestion for avoiding double (()) in macro call and allows defining capture scope (would probably benefit from the std::enable_if_t<std::is_nothrow_invocable_v<F>>> of the above example)

enum class defer_adder {};
template <typename CB> struct defer final {
    defer(CB&& _cb) : cb{ std::forward<CB>(_cb) } {}
    ~defer() {
        try {
            cb();
        }
        catch (...) {
        }
    }
private:
    CB cb;
};

template <typename CB> auto operator+(defer_adder, CB&& cb) { return defer<CB>{std::forward<CB>(cb)}; }

#define DEFER_CONCAT2(X, Y) X ## Y
#define DEFER_CONCAT(X, Y) DEFER_CONCAT2(X, Y)
#define DEFER(...) const auto DEFER_CONCAT(defer_, __LINE__) = defer_adder{} + [__VA_ARGS__]() mutable

int main(int argc, char* argv[]) {
    std::cout << argc << std::endl;
    DEFER(&argc) {
        std::cout << argc << std::endl;
        --argc;
        std::cout << argc << std::endl;
    };
    DEFER(&) {
        std::cout << argc << std::endl;
        argc -= 3;
        std::cout << argc << std::endl;
    };
    std::cout << argc << std::endl;
    return 0;
}

perhaps a hybrid like this?

enum class defer_adder {};
template <typename CB> struct defer final {
    static_assert(std::is_nothrow_invocable_v<CB>);
    static_assert(std::is_same_v<std::invoke_result_t<CB>, void>);
    explicit defer(CB&& cb) : _cb{ std::forward<CB>(cb) } {}
    ~defer() { _cb(); }
private:
    defer(const defer&) = delete;
    defer& operator=(const defer&) = delete;
    defer(defer&&) = delete;
    defer& operator=(defer&&) = delete;
    CB _cb;
};

template <typename CB> auto operator+(defer_adder, CB&& cb) { return defer<CB>{std::forward<CB>(cb)}; }

#define DEFER_CONCAT2(X, Y) X ## Y
#define DEFER_CONCAT(X, Y) DEFER_CONCAT2(X, Y)
#define DEFER(...) const auto DEFER_CONCAT(defer_, __LINE__) = defer_adder{} + [__VA_ARGS__]() mutable noexcept
Clone this wiki locally