File size: 5,049 Bytes
985c397
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#pragma once

#include <cassert>
#include <cstddef>
#include <cstdint>
#include <type_traits>
#include <utility>

namespace fastsignals::detail
{
/// Buffer for callable object in-place construction,
/// helps to implement Small Buffer Optimization.
static constexpr size_t inplace_buffer_size = (sizeof(int) == sizeof(void*) ? 8 : 6) * sizeof(void*);

/// Structure that has size enough to keep type "T" including vtable.
template <class T>
struct type_container

{
	T data;
};

/// Type that has enough space to keep type "T" (including vtable) with suitable alignment.
using function_buffer_t = std::aligned_storage_t<inplace_buffer_size>;

/// Constantly is true if callable fits function buffer, false otherwise.
template <class T>
inline constexpr bool fits_inplace_buffer = (sizeof(type_container<T>) <= inplace_buffer_size);

// clang-format off
/// Constantly is true if callable fits function buffer and can be safely moved, false otherwise
template <class T>
inline constexpr bool can_use_inplace_buffer = 
	fits_inplace_buffer<T> && 
	std::is_nothrow_move_constructible_v<T>;
// clang format on

/// Type that is suitable to keep copy of callable object.
///  - equal to pointer-to-function if Callable is pointer-to-function
///  - otherwise removes const/volatile and references to allow copying callable.
template <class Callable>
using callable_copy_t = std::conditional_t<std::is_function_v<std::remove_reference_t<Callable>>,
	Callable,
	std::remove_cv_t<std::remove_reference_t<Callable>>>;

class base_function_proxy

{
public:
	virtual ~base_function_proxy() = default;
	virtual base_function_proxy* clone(void* buffer) const = 0;
	virtual base_function_proxy* move(void* buffer) noexcept = 0;
};

template <class Signature>
class function_proxy;

template <class Return, class... Arguments>
class function_proxy<Return(Arguments...)> : public base_function_proxy
{
public:
	virtual Return operator()(Arguments&&...) = 0;
};

template <class Callable, class Return, class... Arguments>
class function_proxy_impl final : public function_proxy<Return(Arguments...)>
{
public:
	// If you see this error, probably your function returns value and you're trying to
	//  connect it to `signal<void(...)>`. Just remove return value from callback.
	static_assert(std::is_same_v<std::invoke_result_t<Callable, Arguments...>, Return>,
		"cannot construct function<> class from callable object with different return type");

	template <class FunctionObject>
	explicit function_proxy_impl(FunctionObject&& function)
		: m_callable(std::forward<FunctionObject>(function))
	{
	}

	Return operator()(Arguments&&... args) final
	{
		return m_callable(std::forward<Arguments>(args)...);
	}

	base_function_proxy* clone(void* buffer) const final
	{
		if constexpr (can_use_inplace_buffer<function_proxy_impl>)
		{
			return new (buffer) function_proxy_impl(*this);
		}
		else
		{
			(void)buffer;
			return new function_proxy_impl(*this);
		}
	}

	base_function_proxy* move(void* buffer) noexcept final
	{
		if constexpr (can_use_inplace_buffer<function_proxy_impl>)
		{
			base_function_proxy* moved = new (buffer) function_proxy_impl(std::move(*this));
			this->~function_proxy_impl();
			return moved;
		}
		else
		{
			(void)buffer;
			return this;
		}
	}

private:
	callable_copy_t<Callable> m_callable;
};

template <class Fn, class Return, class... Arguments>
inline constexpr bool is_noexcept_packed_function_init = can_use_inplace_buffer<function_proxy_impl<Fn, Return, Arguments...>>;

class packed_function final

{
public:
	packed_function() = default;
	packed_function(packed_function&& other) noexcept;
	packed_function(const packed_function& other);
	packed_function& operator=(packed_function&& other) noexcept;
	packed_function& operator=(const packed_function& other);
	~packed_function() noexcept;

	// Initializes packed function.
	// Cannot be called without reset().
	template <class Callable, class Return, class... Arguments>
	void init(Callable&& function) noexcept(is_noexcept_packed_function_init<Callable, Return, Arguments...>)
	{
		using proxy_t = function_proxy_impl<Callable, Return, Arguments...>;

		assert(m_proxy == nullptr);
		if constexpr (can_use_inplace_buffer<proxy_t>)
		{
			m_proxy = new (&m_buffer) proxy_t{ std::forward<Callable>(function) };
		}
		else
		{
			m_proxy = new proxy_t{ std::forward<Callable>(function) };
		}
	}

	template <class Signature>
	function_proxy<Signature>& get() const
	{
		return static_cast<function_proxy<Signature>&>(unwrap());
	}

	void reset() noexcept;

private:
	base_function_proxy* move_proxy_from(packed_function&& other) noexcept;
	base_function_proxy* clone_proxy_from(const packed_function &other);
	base_function_proxy& unwrap() const;
	bool is_buffer_allocated() const noexcept;

	function_buffer_t m_buffer[1] = {};
	base_function_proxy* m_proxy = nullptr;
};

} // namespace fastsignals::detail