aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/yt/misc/function_view.h
blob: 259238521ff5eea786d71fde087b39c8b8b3a8ec (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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#pragma once

#include <concepts>

namespace NYT {

////////////////////////////////////////////////////////////////////////////////

namespace NDetail {

template <class TSignature>
struct TTypeErasureTraits;

template <class TResult, bool NoExcept, class... TArgs>
struct TTypeErasureTraits<TResult(TArgs...) noexcept(NoExcept)>
{
    using TSignature = TResult(TArgs...) noexcept(NoExcept);

    // TODO(arkady-e1ppa): Support pointer-to-member-function?
    template <class T>
    static constexpr bool IsInvocable = NoExcept
        ? requires (T obj, TArgs... args) {
            { obj(std::forward<TArgs>(args)...) } noexcept -> std::same_as<TResult>;
        }
        : requires (T obj, TArgs... args) {
            { obj(std::forward<TArgs>(args)...) } -> std::same_as<TResult>;
        };
};

} // namespace NDetail

////////////////////////////////////////////////////////////////////////////////

// Non-owning type-erasure container.
/*
    Example:

    template <class T>
    class TSerializedObject
    {
    public:
        explicit TSerializedObject(T value)
            : Object_(value)
        { }

        void Lock(TFunctionView<void(const T&)> callback)
        {
            auto guard = Guard(SpinLock_);
            callback(Object_);
        }

    private:
        TSpinLock SpinLock_;
        T Object_;
    };

    int main()
    {
        TSerializedObject<int> object(42);

        // object.Lock([] (const int& value) {
        //     fmt::println("Value is {}", value);
        // });
        // ^ CE -- cannot pass rvalue.

        auto callback = [] (const int& value) {
            fmt::println("Value is {}", value);
        };

        object.Lock(callback); // <- prints "Value is 42".
    }
*/
template <class TSignature>
class TFunctionView;

////////////////////////////////////////////////////////////////////////////////

template <class T, class TSignature>
concept CTypeErasable =
    NDetail::TTypeErasureTraits<TSignature>::template IsInvocable<T> &&
    (!std::same_as<T, TFunctionView<TSignature>>);

////////////////////////////////////////////////////////////////////////////////

template <class TResult, bool NoExcept, class... TArgs>
class TFunctionView<TResult(TArgs...) noexcept(NoExcept)>
{
public:
    using TSignature = TResult(TArgs...) noexcept(NoExcept);

    TFunctionView() = default;

    template <CTypeErasable<TSignature> TConcrete>
    TFunctionView(TConcrete& concreteRef) noexcept;

    template <CTypeErasable<TSignature> TConcrete>
    TFunctionView(TConcrete* concretePtr) noexcept;

    TResult operator()(TArgs... args) noexcept(NoExcept);

    explicit operator bool() const noexcept;

    TFunctionView Release() noexcept;

    bool IsValid() const noexcept;
    void Reset() noexcept;

    // bool operator==(const TFunctionView& other) const & = default;

private:
    // NB: Technically, this is UB according to C standard, which
    // was not changed for C++ standard.
    // This is so because it is allowed to have
    // function pointers to be modelled by entities
    // different from object pointers.
    // No reasonable system architecture (e.g. x86 or ARM
    // or any other POSIX compliant one) does this.
    // No reasonable compiler (clang/gcc) does anything with this.
    // Accounting for such requirement would cause this class
    // to have std::variant-like storage which would make this class
    // weight more. Thus, we have decided to keep it this way,
    // since we are safe on x86 or ARM + clang.
    using TErasedPtr = void*;
    using TErasedInvoke = TResult(*)(TArgs..., TErasedPtr);

    TErasedPtr Ptr_ = nullptr;
    TErasedInvoke Invoke_ = nullptr;

    template <class TConcrete>
    static TResult ConcreteInvoke(TArgs... args, TErasedPtr ptr) noexcept(NoExcept);
};

////////////////////////////////////////////////////////////////////////////////

} // namespace NYT

#define FUNCTION_VIEW_INL_H_
#include "function_view-inl.h"
#undef FUNCTION_VIEW_INL_H_