aboutsummaryrefslogtreecommitdiffstats
path: root/util/generic/function_ref.h
blob: c55de8694c96aae95fefea1c54ab02439e69836b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#pragma once

#include <util/generic/function.h>
#include <util/system/yassert.h>

#include <functional>

template <typename Signature>
class TFunctionRef;

template <typename Ret, typename... Args, bool IsNoexcept>
class TFunctionRef<Ret(Args...) noexcept(IsNoexcept)> {
public:
    using TSignature = Ret(Args...) noexcept(IsNoexcept);

private:
    union TErasedCallable {
        const void* Functor;
        void (*Function)();
    };
    using TProxy = Ret (*)(TErasedCallable callable, Args...);

    // Making this a lambda inside TFunctionRef ctor caused:
    // "error: cannot compile this forwarded non-trivially copyable parameter yet"
    // on clang-win-i686-release.
    //
    // Using correct noexcept specifiers here (noexcept(IsNoexcept)) caused miscompilation on clang:
    // https://github.com/llvm/llvm-project/issues/55280.
    template <typename Functor>
    static Ret InvokeErasedFunctor(TErasedCallable callable, Args... args) {
        auto& ref = *static_cast<const std::remove_reference_t<Functor>*>(callable.Functor);
        return static_cast<Ret>(std::invoke(ref, std::forward<Args>(args)...));
    }

    template <typename Function>
    static Ret InvokeErasedFunction(TErasedCallable callable, Args... args) {
        auto* function = reinterpret_cast<Function*>(callable.Function);
        return static_cast<Ret>(std::invoke(function, std::forward<Args>(args)...));
    }

    template <class F>
    static constexpr bool IsInvocableUsing = std::conditional_t<
        IsNoexcept,
        std::is_nothrow_invocable_r<Ret, F, Args...>,
        std::is_invocable_r<Ret, F, Args...>>::value;

    // clang-format off
    template <class Callable>
    static constexpr bool IsSuitableFunctor =
        IsInvocableUsing<Callable>
        && !std::is_function_v<Callable>
        && !std::is_same_v<std::remove_cvref_t<Callable>, TFunctionRef>;

    template <class Callable>
    static constexpr bool IsSuitableFunction =
        IsInvocableUsing<Callable>
        && std::is_function_v<Callable>;
    // clang-format on

public:
    // Function ref should not be default constructible.
    // While the function ref can have empty state (for example, Proxy_ == nullptr),
    // It does not make sense in common usage cases.
    TFunctionRef() = delete;

    // Construct function ref from a functor.
    template <typename Functor, typename = std::enable_if_t<IsSuitableFunctor<Functor>>>
    TFunctionRef(Functor&& functor) noexcept
        : Callable_{
              .Functor = std::addressof(functor),
          }
        , Proxy_{InvokeErasedFunctor<Functor>}
    {
    }

    // Construct function ref from a function pointer.
    template <typename Function, typename = std::enable_if_t<IsSuitableFunction<Function>>>
    TFunctionRef(Function* function) noexcept
        : Callable_{
              .Function = reinterpret_cast<void (*)()>(function),
          }
        , Proxy_{InvokeErasedFunction<Function>}
    {
    }

    // Copy ctors & assignment.
    // Just copy pointers.
    TFunctionRef(const TFunctionRef& rhs) noexcept = default;
    TFunctionRef& operator=(const TFunctionRef& rhs) noexcept = default;

    Ret operator()(Args... args) const noexcept(IsNoexcept) {
        return Proxy_(Callable_, std::forward<Args>(args)...);
    }

private:
    TErasedCallable Callable_;
    TProxy Proxy_ = nullptr;
};

namespace NPrivate {

    template <typename Callable, typename Signature = typename TCallableTraits<Callable>::TSignature>
    struct TIsNothrowInvocable;

    template <typename Callable, typename Ret, typename... Args>
    struct TIsNothrowInvocable<Callable, Ret(Args...)> {
        static constexpr bool IsNoexcept = std::is_nothrow_invocable_r_v<Ret, Callable, Args...>;
        using TSignature = Ret(Args...) noexcept(IsNoexcept);
    };

    template <typename Callable>
    struct TCallableTraitsWithNoexcept {
        using TSignature = typename TIsNothrowInvocable<Callable>::TSignature;
    };

} // namespace NPrivate

template <typename Callable>
TFunctionRef(Callable&&) -> TFunctionRef<typename NPrivate::TCallableTraitsWithNoexcept<Callable>::TSignature>;

template <typename Function>
TFunctionRef(Function*) -> TFunctionRef<Function>;