├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── demo └── main.cpp ├── docs └── delegate.md ├── src └── delegate.h └── tests ├── CMakeLists.in ├── CMakeLists.txt └── tests.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | 19 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | project(delegate CXX) 4 | 5 | enable_testing() 6 | 7 | set(CMAKE_CXX_STANDARD 17) 8 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 9 | set(CMAKE_CXX_EXTENSIONS OFF) 10 | 11 | add_library(delegate INTERFACE) 12 | target_sources(delegate INTERFACE ${CMAKE_SOURCE_DIR}/src/delegate.h) 13 | target_include_directories(delegate INTERFACE ${CMAKE_SOURCE_DIR}/src) 14 | target_compile_options(delegate INTERFACE 15 | $<$:-Wall> 16 | $<$:-Wall> 17 | $<$:-Wall> 18 | $<$:/W4>) 19 | 20 | add_executable(delegate-demo ${CMAKE_SOURCE_DIR}/demo/main.cpp) 21 | target_link_libraries(delegate-demo delegate) 22 | 23 | add_subdirectory(tests) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Vadim Karkhin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # delegate 2 | 3 | Class template `vdk::delegate` is a general-purpose polymorphic function wrapper. Instances of `delegate` can store and invoke any function-like entity with specified signature, such as functions, lambda expressions, bind expressions, or other function objects. 4 | 5 | `delegate` satisfies the requirements of MoveConstructible and MoveAssignable, but not the requirements of either CopyConstructible or CopyAssignable. Because of this, stored function object does not have to be copyable; move-only type would suffice. 6 | 7 | `delegate` instances can be compared. Two delegates are equal if they both are empty, or if they both contain targets of exactly the same static type with accessible equality comparison operator and the result of their comparison returns `true`. As a consequence, two delegates are not equal if only one of them is empty, or they contain targets of different static types, or the result of comparison of stored targets returns `false`, or the target's type does not provide accessible comparison operator. 8 | 9 | To reduce dynamic memory allocation `delegate` implements small buffer optimization technique. When size of a stored callable target is `<= sizeof(ptr_to_any_callable) + sizeof(ptr_to_any_object)` and its move-operations are `noexcept`, small buffer optimization is guaranteed. Such targets are always directly stored inside delegate's internal buffer and no dynamic memory allocation takes place. However, if a target is too large to fit into delegate, or if its move operations may throw, the required memory is allocated dynamically. This implies that pointers to functions are always stored locally within a delegate instance. 10 | 11 | A signature given to `delegate` is used to define its function call operator. Any qualifiers and | or specifiers in the signature are applied to the delegate's function call operator and the target is invoked through the specified call path. 12 | 13 | Trying to invoke a delegate without a target results in assertion being triggered. If exceptions on failed dynamic memory allocations should be avoided, one could use experimental functionality - custom global memory resource in `vdk::memory::delegate` namespace. 14 | 15 | ## Usage: 16 | 17 | **Note!** `vdk::delegate` requires a compiler that supports **C++17** standard. 18 | 19 | To use `delegate` just include `delegate.h` into your project and you are good to go. 20 | 21 | GoogleTest is required to build and run tests. CMake files are provided to simplify the process. If you already have a copy of GoogleTest framework, just run CMake and set `GTEST_SOURCE_DIR` cache variable to point to the directory that contains your GoogleTest sources. If you don't have GoogleTest sources, CMake will download and compile them automatically. 22 | 23 | `demo` directory contains code examples that can serve as a tutorial for learning how to use `delegate`. 24 | 25 | ## [API documentation](docs/delegate.md) 26 | 27 | ## License: 28 | 29 | This software is licensed under the MIT License. -------------------------------------------------------------------------------- /demo/main.cpp: -------------------------------------------------------------------------------- 1 | /*=================================================================== 2 | * Copyright (c) Vadim Karkhin. All rights reserved. 3 | * Use, modification, and distribution is subject to license terms. 4 | * You are welcome to contact the author at: vdksoft@gmail.com 5 | ===================================================================*/ 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | using vdk::delegate; 13 | 14 | namespace 15 | { 16 | struct functor 17 | { 18 | explicit functor(int data) noexcept 19 | : data_{ data } 20 | {} 21 | void operator()(int arg) 22 | { 23 | std::cout << "functor(" << arg << ")" << std::endl; 24 | } 25 | void operator()(int arg) const 26 | { 27 | std::cout << "functor(" << arg << ")const" << std::endl; 28 | } 29 | void operator()(int arg) volatile 30 | { 31 | std::cout << "functor(" << arg << ")volatile" << std::endl; 32 | } 33 | void operator()(int arg) const volatile 34 | { 35 | std::cout << "functor(" << arg << ")const volatile" << std::endl; 36 | } 37 | bool operator==(const functor & other) const noexcept 38 | { 39 | return data_ == other.data_; 40 | } 41 | int data_; 42 | }; 43 | 44 | struct functor_noexcept 45 | { 46 | functor_noexcept() noexcept = default; 47 | void operator()(int arg) & noexcept 48 | { 49 | std::cout << "functor_noexcept(" << arg << ")& noexcept" << std::endl; 50 | } 51 | void operator()(int arg) && noexcept 52 | { 53 | std::cout << "functor_noexcept(" << arg << ")&& noexcept" << std::endl; 54 | } 55 | }; 56 | 57 | void function(int arg) 58 | { 59 | std::cout << "function(" << arg << ")" << std::endl; 60 | } 61 | 62 | } // namespace 63 | 64 | int main() 65 | { 66 | // Create an empty delegate 67 | delegate fn1; 68 | 69 | // Create delegate with a function 70 | delegate fn2{ function }; 71 | 72 | // Create delegate with a function object 73 | delegate fn3{ functor{ 10 } }; 74 | 75 | // Create delegate with a lambda 76 | double ballast = 2.0; 77 | // ballast is needed to prevent lambda conversion to function pointer! 78 | delegate fn4{[ballast](int arg) 79 | { 80 | std::cout << "lambda(" << arg << ")" << std::endl; 81 | }}; 82 | 83 | // Check whether delegates are empty 84 | if (fn1 == nullptr) std::cout << "fn1 is empty" << std::endl; 85 | if (fn2 == nullptr) std::cout << "fn2 is empty" << std::endl; 86 | if (fn3 == nullptr) std::cout << "fn3 is empty" << std::endl; 87 | if (fn4 == nullptr) std::cout << "fn4 is empty" << std::endl; 88 | 89 | // Call only those delegates that are not empty 90 | if (fn1) fn1(1); 91 | if (fn2) fn2(2); 92 | if (fn3) fn3(3); 93 | if (fn4) fn4(4); 94 | 95 | // Reassign targets for delegates 96 | fn1 = function; 97 | fn2 = functor{ 15 }; 98 | fn3 = [ballast](int arg){ std::cout << "lambda(" << arg << ")" << std::endl; }; 99 | 100 | // Make a delegate empty 101 | fn4 = nullptr; 102 | 103 | // Compare delegates 104 | 105 | // Both delegates point to the same function 106 | fn4 = function; 107 | if (fn1 == fn4) std::cout << "target is function: fn1 == fn4" << std::endl; 108 | 109 | // Compare delegates with comparable and equal targets 110 | fn4 = functor{ 15 }; 111 | if (fn2 == fn4) std::cout << "target is functor: fn2 == fn4" << std::endl; 112 | 113 | // Compare delegates with comparable but not equal targets 114 | fn4 = functor{ 20 }; 115 | if (fn2 != fn4) std::cout << "target is functor: fn2 != fn4" << std::endl; 116 | 117 | // Compare delegates with lambdas (!lambdas cannot be compared!) 118 | auto lambda = [ballast](int arg) { std::cout << "lambda(" << arg << ")" << std::endl; }; 119 | fn3 = lambda; 120 | fn4 = lambda; 121 | if (fn3 != fn4) std::cout << "target is lambda: fn3 != fn4" << std::endl; 122 | 123 | // Assign target that cannot be copied 124 | auto unique = std::make_unique(15); 125 | auto unique_lambda = [u = std::move(unique)](int arg) 126 | { 127 | std::cout << "I am unique lambda with arg = " << arg 128 | << " and unique part = " << *u << std::endl; 129 | }; 130 | 131 | fn4 = std::move(unique_lambda); 132 | 133 | // Call stored unique lambda 134 | fn4(10); 135 | 136 | // Move one delegate into another 137 | fn1 = std::move(fn4); 138 | 139 | // Check whether delegates are empty and call those that are not 140 | if (fn1) fn1(15); 141 | if (fn4) fn4(20); //!note! fn4 is now empty 142 | 143 | // Create delegates with different signatures (some examples) 144 | delegate fn5{ functor{ 10 } }; 145 | delegate fn6{ functor{ 10 } }; 146 | delegate fn7{ functor{ 10 } }; 147 | delegate fn8{ functor{ 10 } }; 148 | delegate fn9{ functor_noexcept{} }; 149 | delegate fn10{ functor_noexcept{} }; 150 | 151 | // Call them all 152 | fn5(5); 153 | fn6(6); 154 | fn7(7); 155 | fn8(8); 156 | fn9(9); 157 | std::move(fn10)(10); 158 | 159 | return 0; 160 | } -------------------------------------------------------------------------------- /docs/delegate.md: -------------------------------------------------------------------------------- 1 | # `class delegate` 2 | 3 | ## Methods: 4 | 5 | ----------------------- 6 | 7 | 1. **`delegate() noexcept = default;`** 8 | 9 | 2. **`delegate(std::nullptr_t) noexcept;`** 10 | 11 | 3. **`delegate(fn_t func) noexcept;`** 12 | 13 | 4. **`template`** 14 | **`delegate(F func) noexcept(is_internal_v);`** 15 | 16 | 5. **`delegate(delegate && other) noexcept;`** 17 | 18 | Constructs a `delegate` instance. 19 | 20 | 1-2: Creates a null (empty) delegate. 21 | 22 | 3: Creates a delegate with the pointer to function `func`. This constructor does not participate in overload resolution unless `func` is a pointer to function with exact signature as the delegate's signature. 23 | 24 | 4: Creates a delegate with the function object `func`. This constructor does not participate in overload resolution unless `func` is callable for argument types and return type specified in the delegate's signature. 25 | 26 | 5: Moves the target of `other` to the target of `*this`. If `other` is empty, `*this` will be empty after the call too. `other` is in a valid but unspecified state after the call. 27 | 28 | If `func` is a null pointer, `*this` will be empty after the call. For (4), if `func`'s size or alignment does not fit into internal delegate's storage, or if `func`'s move operations may throw an exception, the required memory is allocated dynamically. 29 | 30 | ----------------------- 31 | 32 | 1. **`delegate & operator=(delegate && other) noexcept;`** 33 | 34 | 2. **`delegate & operator=(std::nullptr_t) noexcept;`** 35 | 36 | 3. **`delegate & operator=(fn_t func) noexcept;`** 37 | 38 | 4. **`template`** 39 | **`delegate & operator=(F func) noexcept(is_internal_v);`** 40 | 41 | Assigns a new target to the `delegate`. 42 | 43 | 1: Moves the target of `other` to the target of `*this`. If `other` is empty, `*this` will be empty after the call as well. `other` is in a valid but unspecified state after the call. 44 | 45 | 2: Drops the current target. `*this` is empty after the call. 46 | 47 | 3: Sets the target of `*this` to the pointer to function `func`. This operator does not participate in overload resolution unless `func` is a pointer to function with exact signature as the delegate's signature. 48 | 49 | 4: Sets the target of `*this` to the function object `func`. This operator does not participate in overload resolution unless `func` is callable for argument types and return type specified in the delegate's signature. 50 | 51 | If `func` is a null pointer, `*this` will be unchanged after the call. For (4), if `func`'s size or alignment does not fit into internal delegate's storage, or if `func`'s move operations may throw an exception, the required memory is allocated dynamically. 52 | 53 | ----------------------- 54 | 55 | **`delegate(const delegate &) = delete;`** 56 | 57 | **`delegate & operator=(const delegate &) = delete;`** 58 | 59 | The copy constructor and copy assignment operators are deleted. `delegate` is move-only. 60 | 61 | ----------------------- 62 | 63 | **`~delegate() noexcept;`** 64 | 65 | Destroys the `delegate` instance. If the delegate is not empty, its target is destroyed as well. 66 | 67 | ----------------------- 68 | 69 | **`operator()(ArgTs ... args); |qualifiers-specifiers depend on the delegate's signature|`** 70 | 71 | Invokes the stored callable target with the provided arguments `args`. 72 | If `*this` is empty - assertion is triggered. 73 | Returns the result of invoking the stored target. 74 | 75 | ----------------------- 76 | 77 | **`explicit operator bool() const noexcept;`** 78 | 79 | Checks whether `*this` stores a callable target, i.e. is not empty. 80 | Returns `true` if `*this` is not empty, `false` otherwise. 81 | 82 | ----------------------- 83 | 84 | **`bool operator==(const delegate & other) const noexcept;`** 85 | 86 | **`bool operator!=(const delegate & other) const noexcept;`** 87 | 88 | Compares `delegate` instances. If their targets are of exactly the same static type and equality comparable, returns the result of comparison; otherwise returns `false`. 89 | 90 | ----------------------- 91 | 92 | ## Functions: 93 | 94 | **`template`** 95 | **`bool operator==(const delegate & lhs, std::nullptr_t) noexcept;`** 96 | 97 | **`template`** 98 | **`bool operator==(std::nullptr_t, const delegate & rhs) noexcept;`** 99 | 100 | **`template`** 101 | **`bool operator!=(const delegate & lhs, std::nullptr_t) noexcept;`** 102 | 103 | **`template`** 104 | **`bool operator!=(std::nullptr_t, const delegate & rhs) noexcept;`** 105 | 106 | Compares a `delegate` instance with a null pointer. 107 | Returns `true` if the delegate is empty, `false` otherwise. 108 | 109 | ----------------------- 110 | 111 | ## Notes: 112 | 113 | ### Memory resources 114 | 115 | If delegate's target cannot be stored within a delegate, it must be allocated dynamically through the centralized global memory resource. If this memory allocation fails, the behavior depends on the installed memory resource. Custom memory resources may return a null pointer on failed memory allocation; default memory resource throws `std::bad_alloc`. 116 | 117 | A custom memory resource can be installed through the experimental API - `vdk::memory::delegate::memory()` function that accepts and stores pointer to an instance of a class inherited from `vdk::memory::delegate::memory_resource`. 118 | 119 | _Note!_ The memory resource can be installed only once. This avoids many problems that could otherwise arise from incompatible memory resources trying to deallocate each other's memory. 120 | 121 | If memory allocation fails during delegate construction (by returning a null pointer), the constructed delegate instance will be empty. If memory allocation fails during assignment (either by returning a null pointer or by throwing an exception), delegate will remain unchanged. 122 | 123 | If a delegate's target satisfies the following requirements, it is guaranteed that no dynamic memory allocation takes place: 124 | 125 | 1. The target's size and alignment are less than or equal to: `sizeof(ptr_to_any_callable) + sizeof(ptr_to_any_object)`. 126 | 2. The target's move operations are `noexcept`. -------------------------------------------------------------------------------- /src/delegate.h: -------------------------------------------------------------------------------- 1 | /*=================================================================== 2 | * Copyright (c) Vadim Karkhin. All rights reserved. 3 | * Use, modification, and distribution is subject to license terms. 4 | * You are welcome to contact the author at: vdksoft@gmail.com 5 | ===================================================================*/ 6 | 7 | #ifndef VDK_DELEGATE_H 8 | #define VDK_DELEGATE_H 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace vdk 17 | { 18 | namespace memory::delegate 19 | { 20 | using std::size_t; 21 | 22 | // Interface for memory resources 23 | class memory_resource 24 | { 25 | public: 26 | virtual void * allocate(size_t size, size_t align) = 0; 27 | virtual void deallocate(void * addr, size_t size, size_t align) noexcept = 0; 28 | virtual ~memory_resource() noexcept = default; 29 | }; 30 | 31 | // Default memory resource 32 | class default_memory_resource : public memory_resource 33 | { 34 | public: 35 | void * allocate(size_t size, size_t align) override; 36 | void deallocate(void * addr, size_t size, size_t align) noexcept override; 37 | ~default_memory_resource() noexcept override = default; 38 | }; 39 | 40 | inline void * default_memory_resource:: 41 | allocate(size_t size, size_t align) 42 | { 43 | return ::operator new(size, std::align_val_t{ align }); 44 | } 45 | inline void default_memory_resource:: 46 | deallocate(void * addr, size_t size, size_t align) noexcept 47 | { 48 | ::operator delete(addr, size, std::align_val_t{ align }); 49 | } 50 | 51 | // Global memory resource for all allocations | deallocations 52 | inline memory_resource * 53 | memory(memory_resource * r = nullptr) noexcept 54 | { 55 | static memory_resource * const resource = r ? r : 56 | [] { static default_memory_resource dr{}; return &dr; }(); 57 | return resource; 58 | } 59 | 60 | } // namespace memory::delegate 61 | 62 | // Internal implementation details 63 | namespace internal::delegate 64 | { 65 | using std::size_t; 66 | using vdk::memory::delegate::memory; 67 | 68 | // Helper class for memory management during object construction 69 | class memory_owner 70 | { 71 | public: 72 | 73 | memory_owner(size_t size, size_t align); 74 | ~memory_owner() noexcept; 75 | 76 | void * get() const noexcept; 77 | void release() noexcept; 78 | 79 | memory_owner(const memory_owner &) = delete; 80 | memory_owner & operator=(const memory_owner &) = delete; 81 | 82 | private: 83 | 84 | void * addr_; 85 | size_t size_; 86 | size_t align_; 87 | }; 88 | 89 | inline memory_owner::memory_owner(size_t size, size_t align) 90 | : addr_{ memory()->allocate(size, align) }, 91 | size_{ size }, 92 | align_{ align } 93 | {} 94 | 95 | inline memory_owner::~memory_owner() noexcept 96 | { 97 | if (addr_) memory()->deallocate(addr_, size_, align_); 98 | } 99 | 100 | inline void * memory_owner::get() const noexcept 101 | { 102 | return addr_; 103 | } 104 | 105 | inline void memory_owner::release() noexcept 106 | { 107 | addr_ = nullptr; 108 | } 109 | 110 | // Check whether T is equality comparable 111 | template 112 | struct is_equality_comparable : std::false_type {}; 113 | template 114 | struct is_equality_comparable() == std::declval())> 116 | : std::true_type {}; 117 | template inline constexpr bool 118 | is_equality_comparable_v = is_equality_comparable::value; 119 | 120 | // Structure to estimate size and alignment of internal buffer 121 | struct block 122 | { 123 | class undef; 124 | union 125 | { 126 | void(undef::*p1)(); 127 | undef undef::*p2; 128 | void(*p3)(); 129 | }; 130 | undef*p4; 131 | }; 132 | 133 | // Check whether T can be stored in internal buffer 134 | template inline constexpr 135 | bool is_internal_v = sizeof(T) <= sizeof(block) && 136 | alignof(T) <= alignof(block) && 137 | std::is_nothrow_move_constructible_v; 138 | 139 | // Internal storage for delegate 140 | struct storage 141 | { 142 | void * get() noexcept; 143 | const void * get() const noexcept; 144 | volatile void * get() volatile noexcept; 145 | const volatile void * get() const volatile noexcept; 146 | 147 | template 148 | T * get_as() noexcept; 149 | template 150 | const T * get_as() const noexcept; 151 | template 152 | volatile T * get_as() volatile noexcept; 153 | template 154 | const volatile T * get_as() const volatile noexcept; 155 | 156 | std::aligned_storage_t data_; 157 | }; 158 | 159 | inline void * storage::get() noexcept 160 | { 161 | return &data_; 162 | } 163 | inline const void * storage::get() const noexcept 164 | { 165 | return &data_; 166 | } 167 | inline volatile void * storage::get() volatile noexcept 168 | { 169 | return &data_; 170 | } 171 | inline const volatile void * storage::get() const volatile noexcept 172 | { 173 | return &data_; 174 | } 175 | template 176 | inline T * storage::get_as() noexcept 177 | { 178 | if constexpr(is_internal_v) 179 | return static_cast(get()); 180 | else 181 | return *static_cast(get()); 182 | } 183 | template 184 | inline const T * storage::get_as() const noexcept 185 | { 186 | if constexpr(is_internal_v) 187 | return static_cast(get()); 188 | else 189 | return *static_cast(get()); 190 | } 191 | template 192 | inline volatile T * storage::get_as() volatile noexcept 193 | { 194 | if constexpr(is_internal_v) 195 | return static_cast(get()); 196 | else 197 | return *static_cast(get()); 198 | } 199 | template 200 | inline const volatile T * storage::get_as() const volatile noexcept 201 | { 202 | if constexpr(is_internal_v) 203 | return static_cast(get()); 204 | else 205 | return *static_cast(get()); 206 | } 207 | 208 | // Artificial virtual table 209 | struct vtbl 210 | { 211 | void(*move)(storage&, storage&)noexcept; 212 | bool(*compare)(const storage&, const storage&)noexcept; 213 | void(*destroy)(storage&)noexcept; 214 | }; 215 | 216 | // Move stored callable object 217 | template 218 | inline void move(storage & src, storage & dst) noexcept 219 | { 220 | if constexpr(is_internal_v) 221 | ::new (dst.get()) T{ std::move(*src.template get_as()) }; 222 | else 223 | *dst.template get_as() = src.template get_as(); 224 | } 225 | 226 | // Compare stored callable objects 227 | template 228 | inline bool compare([[maybe_unused]] const storage & lhs, 229 | [[maybe_unused]] const storage & rhs) noexcept 230 | { 231 | if constexpr(is_equality_comparable_v) 232 | return *lhs.template get_as() == *rhs.template get_as(); 233 | else 234 | return false; 235 | } 236 | 237 | // Destroy stored callable object 238 | template 239 | inline void destroy(storage & self) noexcept 240 | { 241 | if constexpr(is_internal_v) 242 | { 243 | self.template get_as()->~T(); 244 | } 245 | else 246 | { 247 | auto p = self.template get_as(); 248 | p->~T(); 249 | memory()->deallocate(p, sizeof(T), alignof(T)); 250 | } 251 | } 252 | 253 | // Obtain virtual table for type T 254 | template inline 255 | const vtbl * vtable() noexcept 256 | { 257 | static constexpr vtbl tbl{ &move, &compare, &destroy }; 258 | return &tbl; 259 | } 260 | 261 | // Traits for supported delegate types 262 | template struct traits; 263 | 264 | #define VDK_NONE 265 | #define VDK_TRAITS_CV_LR(CVQUAL, LRQUAL)\ 266 | template\ 267 | struct traits : storage\ 268 | {\ 269 | R(*call_)(CVQUAL storage*, A&&...) {};\ 270 | \ 271 | using fn_t = R(*)(A...);\ 272 | \ 273 | template\ 274 | static constexpr bool is_invocable_v =\ 275 | std::is_invocable_r_v;\ 276 | \ 277 | template\ 278 | static R invoke(CVQUAL storage * self, A&& ... args)\ 279 | {\ 280 | return (*self->get_as())(std::forward(args)...);\ 281 | }\ 282 | inline R operator()(A ... args) CVQUAL LRQUAL\ 283 | {\ 284 | assert(call_ != nullptr);\ 285 | return call_(this, std::forward(args)...);\ 286 | }\ 287 | }; 288 | #define VDK_TRAITS_CV_LR_NOEXCEPT(CVQUAL, LRQUAL)\ 289 | template\ 290 | struct traits : storage\ 291 | {\ 292 | R(*call_)(CVQUAL storage*, A&&...)noexcept {};\ 293 | \ 294 | using fn_t = R(*)(A...)noexcept;\ 295 | \ 296 | template\ 297 | static constexpr bool is_invocable_v =\ 298 | std::is_nothrow_invocable_r_v;\ 299 | \ 300 | template\ 301 | static R invoke(CVQUAL storage * self, A&& ... args)noexcept\ 302 | {\ 303 | return (*self->get_as())(std::forward(args)...);\ 304 | }\ 305 | inline R operator()(A ... args) CVQUAL LRQUAL noexcept\ 306 | {\ 307 | assert(call_ != nullptr);\ 308 | return call_(this, std::forward(args)...);\ 309 | }\ 310 | }; 311 | #define VDK_TRAITS_CV_RR(CVQUAL)\ 312 | template\ 313 | struct traits : storage\ 314 | {\ 315 | R(*call_)(CVQUAL storage*, A&&...) {};\ 316 | \ 317 | using fn_t = R(*)(A...);\ 318 | \ 319 | template\ 320 | static constexpr bool is_invocable_v =\ 321 | std::is_invocable_r_v;\ 322 | \ 323 | template\ 324 | static R invoke(CVQUAL storage * self, A&& ... args)\ 325 | {\ 326 | return std::move(*self->get_as())(std::forward(args)...);\ 327 | }\ 328 | inline R operator()(A ... args) CVQUAL &&\ 329 | {\ 330 | assert(call_ != nullptr);\ 331 | return call_(this, std::forward(args)...);\ 332 | }\ 333 | }; 334 | #define VDK_TRAITS_CV_RR_NOEXCEPT(CVQUAL)\ 335 | template\ 336 | struct traits : storage\ 337 | {\ 338 | R(*call_)(CVQUAL storage*, A&&...)noexcept {};\ 339 | \ 340 | using fn_t = R(*)(A...)noexcept;\ 341 | \ 342 | template\ 343 | static constexpr bool is_invocable_v =\ 344 | std::is_nothrow_invocable_r_v;\ 345 | \ 346 | template\ 347 | static R invoke(CVQUAL storage * self, A&& ... args)noexcept\ 348 | {\ 349 | return std::move(*self->get_as())(std::forward(args)...);\ 350 | }\ 351 | inline R operator()(A ... args) CVQUAL && noexcept\ 352 | {\ 353 | assert(call_ != nullptr);\ 354 | return call_(this, std::forward(args)...);\ 355 | }\ 356 | }; 357 | 358 | VDK_TRAITS_CV_LR(VDK_NONE, VDK_NONE) 359 | VDK_TRAITS_CV_LR(const, VDK_NONE) 360 | VDK_TRAITS_CV_LR(volatile, VDK_NONE) 361 | VDK_TRAITS_CV_LR(const volatile, VDK_NONE) 362 | VDK_TRAITS_CV_LR(VDK_NONE, &) 363 | VDK_TRAITS_CV_LR(const, &) 364 | VDK_TRAITS_CV_LR(volatile, &) 365 | VDK_TRAITS_CV_LR(const volatile, &) 366 | VDK_TRAITS_CV_LR_NOEXCEPT(VDK_NONE, VDK_NONE) 367 | VDK_TRAITS_CV_LR_NOEXCEPT(const, VDK_NONE) 368 | VDK_TRAITS_CV_LR_NOEXCEPT(volatile, VDK_NONE) 369 | VDK_TRAITS_CV_LR_NOEXCEPT(const volatile, VDK_NONE) 370 | VDK_TRAITS_CV_LR_NOEXCEPT(VDK_NONE, &) 371 | VDK_TRAITS_CV_LR_NOEXCEPT(const, &) 372 | VDK_TRAITS_CV_LR_NOEXCEPT(volatile, &) 373 | VDK_TRAITS_CV_LR_NOEXCEPT(const volatile, &) 374 | VDK_TRAITS_CV_RR(VDK_NONE) 375 | VDK_TRAITS_CV_RR(const) 376 | VDK_TRAITS_CV_RR(volatile) 377 | VDK_TRAITS_CV_RR(const volatile) 378 | VDK_TRAITS_CV_RR_NOEXCEPT(VDK_NONE) 379 | VDK_TRAITS_CV_RR_NOEXCEPT(const) 380 | VDK_TRAITS_CV_RR_NOEXCEPT(volatile) 381 | VDK_TRAITS_CV_RR_NOEXCEPT(const volatile) 382 | 383 | #undef VDK_NONE 384 | #undef VDK_TRAITS_CV_LR 385 | #undef VDK_TRAITS_CV_LR_NOEXCEPT 386 | #undef VDK_TRAITS_CV_RR 387 | #undef VDK_TRAITS_CV_RR_NOEXCEPT 388 | 389 | } // namespace internal::delegate 390 | 391 | // Public delegate interface 392 | template 393 | class delegate final : internal::delegate::traits 394 | { 395 | using base = internal::delegate::traits; 396 | using vtbl = internal::delegate::vtbl; 397 | 398 | using base::call_; 399 | using typename base::fn_t; 400 | 401 | template static constexpr 402 | bool is_internal_v = internal::delegate::is_internal_v; 403 | template static constexpr 404 | bool is_invocable_v = base::template is_invocable_v; 405 | 406 | public: 407 | 408 | delegate() noexcept = default; 409 | delegate(std::nullptr_t) noexcept {} 410 | delegate(fn_t func) noexcept; 411 | 412 | template>> 414 | delegate(F func) noexcept(is_internal_v); 415 | 416 | delegate(delegate && other) noexcept; 417 | delegate & operator=(delegate && other) noexcept; 418 | delegate & operator=(std::nullptr_t) noexcept; 419 | delegate & operator=(fn_t func) noexcept; 420 | 421 | template>> 423 | delegate & operator=(F func) noexcept(is_internal_v); 424 | 425 | ~delegate() noexcept; 426 | 427 | using base::operator(); 428 | explicit operator bool() const noexcept; 429 | 430 | bool operator==(const delegate & other) const noexcept; 431 | bool operator!=(const delegate & other) const noexcept; 432 | 433 | delegate(const delegate &) = delete; 434 | delegate & operator=(const delegate &) = delete; 435 | 436 | private: 437 | 438 | const vtbl * vptr_{}; 439 | }; 440 | 441 | template 442 | delegate::delegate(fn_t func) noexcept 443 | { 444 | if (!func) return; 445 | *(base::template get_as()) = func; 446 | call_ = &base::template invoke; 447 | vptr_ = internal::delegate::vtable(); 448 | } 449 | 450 | template 451 | template 452 | delegate::delegate(F func) noexcept(is_internal_v) 453 | { 454 | if constexpr(std::is_pointer_v) 455 | if (!func) return; 456 | 457 | if constexpr(is_internal_v) 458 | { 459 | ::new (base::get()) F{ std::move(func) }; 460 | } 461 | else 462 | { 463 | internal::delegate::memory_owner mem{ sizeof(F), alignof(F) }; 464 | 465 | if (mem.get()) 466 | { 467 | *(base::template get_as()) = 468 | ::new (mem.get()) F{ std::move(func) }; 469 | mem.release(); 470 | } 471 | else 472 | return; 473 | } 474 | 475 | call_ = &base::template invoke; 476 | vptr_ = internal::delegate::vtable(); 477 | } 478 | 479 | template 480 | delegate::delegate(delegate && other) noexcept 481 | { 482 | call_ = other.call_; 483 | vptr_ = other.vptr_; 484 | if (vptr_) vptr_->move(other, *this); 485 | other.call_ = nullptr; 486 | other.vptr_ = nullptr; 487 | } 488 | 489 | template 490 | delegate & delegate::operator=(delegate && other) noexcept 491 | { 492 | if (this != &other) 493 | { 494 | if (vptr_) vptr_->destroy(*this); 495 | call_ = other.call_; 496 | vptr_ = other.vptr_; 497 | if (vptr_) vptr_->move(other, *this); 498 | other.call_ = nullptr; 499 | other.vptr_ = nullptr; 500 | } 501 | return *this; 502 | } 503 | 504 | template 505 | delegate & delegate::operator=(std::nullptr_t) noexcept 506 | { 507 | if (vptr_) vptr_->destroy(*this); 508 | call_ = nullptr; 509 | vptr_ = nullptr; 510 | return *this; 511 | } 512 | 513 | template 514 | delegate & delegate::operator=(fn_t func) noexcept 515 | { 516 | if (!func) return *this; 517 | if (vptr_) vptr_->destroy(*this); 518 | *(base::template get_as()) = func; 519 | call_ = &base::template invoke; 520 | vptr_ = internal::delegate::vtable(); 521 | return *this; 522 | } 523 | 524 | template 525 | template 526 | delegate & delegate::operator=(F func) noexcept(is_internal_v) 527 | { 528 | if constexpr(std::is_pointer_v) 529 | if (!func) return *this; 530 | 531 | if constexpr(is_internal_v) 532 | { 533 | if (vptr_) vptr_->destroy(*this); 534 | ::new (base::get()) F{ std::move(func) }; 535 | } 536 | else 537 | { 538 | internal::delegate::memory_owner mem{ sizeof(F), alignof(F) }; 539 | 540 | if (mem.get()) 541 | { 542 | auto addr = ::new (mem.get()) F{ std::move(func) }; 543 | if (vptr_) vptr_->destroy(*this); 544 | *(base::template get_as()) = addr; 545 | mem.release(); 546 | } 547 | else 548 | return *this; 549 | } 550 | 551 | call_ = &base::template invoke; 552 | vptr_ = internal::delegate::vtable(); 553 | return *this; 554 | } 555 | 556 | template 557 | delegate::~delegate() noexcept 558 | { 559 | if (vptr_) vptr_->destroy(*this); 560 | } 561 | 562 | template 563 | delegate::operator bool() const noexcept 564 | { 565 | return vptr_ != nullptr; 566 | } 567 | 568 | template 569 | bool delegate::operator==(const delegate & other) const noexcept 570 | { 571 | if (!vptr_ && !other.vptr_) 572 | return true; 573 | if (vptr_ != other.vptr_) 574 | return false; 575 | return vptr_->compare(*this, other); 576 | } 577 | 578 | template 579 | bool delegate::operator!=(const delegate & other) const noexcept 580 | { 581 | return !(*this == other); 582 | } 583 | 584 | template 585 | bool operator==(const delegate & lhs, std::nullptr_t) noexcept 586 | { 587 | return !lhs; 588 | } 589 | 590 | template 591 | bool operator==(std::nullptr_t, const delegate & rhs) noexcept 592 | { 593 | return !rhs; 594 | } 595 | 596 | template 597 | bool operator!=(const delegate & lhs, std::nullptr_t) noexcept 598 | { 599 | return static_cast(lhs); 600 | } 601 | 602 | template 603 | bool operator!=(std::nullptr_t, const delegate & rhs) noexcept 604 | { 605 | return static_cast(rhs); 606 | } 607 | 608 | } // namespace vdk 609 | 610 | #endif // VDK_DELEGATE_H -------------------------------------------------------------------------------- /tests/CMakeLists.in: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | project(google-test-download NONE) 4 | 5 | include(ExternalProject) 6 | 7 | ExternalProject_Add(googletest 8 | URL https://github.com/google/googletest/archive/master.zip 9 | SOURCE_DIR "${GTEST_SOURCE_DIR}" 10 | BINARY_DIR "" 11 | CONFIGURE_COMMAND "" 12 | BUILD_COMMAND "" 13 | INSTALL_COMMAND "" 14 | TEST_COMMAND "") -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | project(delegate-test CXX) 4 | 5 | set(CMAKE_CXX_STANDARD 17) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | set(CMAKE_CXX_EXTENSIONS OFF) 8 | 9 | set(GTEST_SOURCE_DIR "" CACHE PATH "Path to Google Test sources") 10 | set(GTEST_WORKING_DIR "${CMAKE_BINARY_DIR}/googletest") 11 | 12 | if (NOT EXISTS "${GTEST_SOURCE_DIR}") 13 | 14 | set(GTEST_SOURCE_DIR "${GTEST_WORKING_DIR}/src" CACHE PATH "Path to Google Test sources" FORCE) 15 | configure_file("${CMAKE_CURRENT_LIST_DIR}/CMakeLists.in" 16 | "${GTEST_WORKING_DIR}/tmp/CMakeLists.txt") 17 | execute_process(COMMAND "${CMAKE_COMMAND}" -G "${CMAKE_GENERATOR}" . 18 | WORKING_DIRECTORY "${GTEST_WORKING_DIR}/tmp") 19 | execute_process(COMMAND "${CMAKE_COMMAND}" --build . 20 | WORKING_DIRECTORY "${GTEST_WORKING_DIR}/tmp") 21 | 22 | endif() 23 | 24 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 25 | 26 | add_subdirectory("${GTEST_SOURCE_DIR}" 27 | "${GTEST_WORKING_DIR}/build") 28 | 29 | add_executable(delegate-test "tests.cpp") 30 | target_link_libraries(delegate-test gtest_main delegate) 31 | 32 | add_test(delegate-test delegate-test) -------------------------------------------------------------------------------- /tests/tests.cpp: -------------------------------------------------------------------------------- 1 | /*=================================================================== 2 | * Copyright (c) Vadim Karkhin. All rights reserved. 3 | * Use, modification, and distribution is subject to license terms. 4 | * You are welcome to contact the author at: vdksoft@gmail.com 5 | ===================================================================*/ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | namespace 18 | { 19 | // Supported types 20 | using fn_01 = int(int); 21 | using fn_02 = int(int)const; 22 | using fn_03 = int(int)volatile; 23 | using fn_04 = int(int)const volatile; 24 | using fn_05 = int(int)&; 25 | using fn_06 = int(int)const&; 26 | using fn_07 = int(int)volatile&; 27 | using fn_08 = int(int)const volatile&; 28 | using fn_09 = int(int)&&; 29 | using fn_10 = int(int)const&&; 30 | using fn_11 = int(int)volatile&&; 31 | using fn_12 = int(int)const volatile&&; 32 | using fn_13 = int(int) noexcept; 33 | using fn_14 = int(int)const noexcept; 34 | using fn_15 = int(int)volatile noexcept; 35 | using fn_16 = int(int)const volatile noexcept; 36 | using fn_17 = int(int)& noexcept; 37 | using fn_18 = int(int)const& noexcept; 38 | using fn_19 = int(int)volatile& noexcept; 39 | using fn_20 = int(int)const volatile& noexcept; 40 | using fn_21 = int(int)&& noexcept; 41 | using fn_22 = int(int)const&& noexcept; 42 | using fn_23 = int(int)volatile&& noexcept; 43 | using fn_24 = int(int)const volatile&& noexcept; 44 | 45 | // Test-values for supported types 46 | constexpr int fn_01_v = 1; 47 | constexpr int fn_02_v = 2; 48 | constexpr int fn_03_v = 3; 49 | constexpr int fn_04_v = 4; 50 | constexpr int fn_05_v = 5; 51 | constexpr int fn_06_v = 6; 52 | constexpr int fn_07_v = 7; 53 | constexpr int fn_08_v = 8; 54 | constexpr int fn_09_v = 9; 55 | constexpr int fn_10_v = 10; 56 | constexpr int fn_11_v = 11; 57 | constexpr int fn_12_v = 12; 58 | constexpr int fn_13_v = 13; 59 | constexpr int fn_14_v = 14; 60 | constexpr int fn_15_v = 15; 61 | constexpr int fn_16_v = 16; 62 | constexpr int fn_17_v = 17; 63 | constexpr int fn_18_v = 18; 64 | constexpr int fn_19_v = 19; 65 | constexpr int fn_20_v = 20; 66 | constexpr int fn_21_v = 21; 67 | constexpr int fn_22_v = 22; 68 | constexpr int fn_23_v = 23; 69 | constexpr int fn_24_v = 24; 70 | 71 | // ====================== Test-utilities ====================== // 72 | 73 | struct test_exception : std::runtime_error 74 | { 75 | test_exception() 76 | : std::runtime_error{ "dummy_exception" } 77 | {} 78 | }; 79 | 80 | struct test_class 81 | { 82 | int method(int arg) 83 | { 84 | return fn_01_v + arg; 85 | } 86 | }; 87 | 88 | // ===================== Function objects ===================== // 89 | 90 | struct functor_base 91 | { 92 | explicit functor_base(int arg) noexcept 93 | : value_{ arg } 94 | {} 95 | bool operator==(const functor_base & other) const noexcept 96 | { 97 | return value_ == other.value_; 98 | } 99 | int value_; 100 | }; 101 | 102 | struct functor_base_except : functor_base 103 | { 104 | using functor_base::functor_base; 105 | using functor_base::operator==; 106 | 107 | int operator()(int arg) 108 | { 109 | return arg + fn_01_v; 110 | } 111 | int operator()(int arg)const 112 | { 113 | return arg + fn_02_v; 114 | } 115 | int operator()(int arg)volatile 116 | { 117 | return arg + fn_03_v; 118 | } 119 | int operator()(int arg)const volatile 120 | { 121 | return arg + fn_04_v; 122 | } 123 | }; 124 | 125 | struct functor_base_ref_except : functor_base 126 | { 127 | using functor_base::functor_base; 128 | using functor_base::operator==; 129 | 130 | int operator()(int arg)& 131 | { 132 | return arg + fn_05_v; 133 | } 134 | int operator()(int arg)const& 135 | { 136 | return arg + fn_06_v; 137 | } 138 | int operator()(int arg)volatile& 139 | { 140 | return arg + fn_07_v; 141 | } 142 | int operator()(int arg)const volatile& 143 | { 144 | return arg + fn_08_v; 145 | } 146 | int operator()(int arg) && 147 | { 148 | return arg + fn_09_v; 149 | } 150 | int operator()(int arg)const&& 151 | { 152 | return arg + fn_10_v; 153 | } 154 | int operator()(int arg)volatile&& 155 | { 156 | return arg + fn_11_v; 157 | } 158 | int operator()(int arg)const volatile&& 159 | { 160 | return arg + fn_12_v; 161 | } 162 | }; 163 | 164 | struct functor_base_noexcept : functor_base 165 | { 166 | using functor_base::functor_base; 167 | using functor_base::operator==; 168 | 169 | int operator()(int arg) noexcept 170 | { 171 | return arg + fn_13_v; 172 | } 173 | int operator()(int arg)const noexcept 174 | { 175 | return arg + fn_14_v; 176 | } 177 | int operator()(int arg)volatile noexcept 178 | { 179 | return arg + fn_15_v; 180 | } 181 | int operator()(int arg)const volatile noexcept 182 | { 183 | return arg + fn_16_v; 184 | } 185 | }; 186 | 187 | struct functor_base_ref_noexcept : functor_base 188 | { 189 | using functor_base::functor_base; 190 | using functor_base::operator==; 191 | 192 | int operator()(int arg)& noexcept 193 | { 194 | return arg + fn_17_v; 195 | } 196 | int operator()(int arg)const& noexcept 197 | { 198 | return arg + fn_18_v; 199 | } 200 | int operator()(int arg)volatile& noexcept 201 | { 202 | return arg + fn_19_v; 203 | } 204 | int operator()(int arg)const volatile& noexcept 205 | { 206 | return arg + fn_20_v; 207 | } 208 | int operator()(int arg) && noexcept 209 | { 210 | return arg + fn_21_v; 211 | } 212 | int operator()(int arg)const&& noexcept 213 | { 214 | return arg + fn_22_v; 215 | } 216 | int operator()(int arg)volatile&& noexcept 217 | { 218 | return arg + fn_23_v; 219 | } 220 | int operator()(int arg)const volatile&& noexcept 221 | { 222 | return arg + fn_24_v; 223 | } 224 | }; 225 | 226 | // Types to test internally and externally constructed targets 227 | using small_t = struct {}; 228 | using large_t = std::aligned_storage_t)>; 229 | 230 | template 231 | struct functor_except : functor_base_except 232 | { 233 | using functor_base_except::functor_base_except; 234 | using functor_base_except::operator(); 235 | DataType data_; 236 | }; 237 | 238 | template 239 | struct functor_ref_except : functor_base_ref_except 240 | { 241 | using functor_base_ref_except::functor_base_ref_except; 242 | using functor_base_ref_except::operator(); 243 | DataType data_; 244 | }; 245 | 246 | template 247 | struct functor_noexcept : functor_base_noexcept 248 | { 249 | using functor_base_noexcept::functor_base_noexcept; 250 | using functor_base_noexcept::operator(); 251 | DataType data_; 252 | }; 253 | 254 | template 255 | struct functor_ref_noexcept : functor_base_ref_noexcept 256 | { 257 | using functor_base_ref_noexcept::functor_base_ref_noexcept; 258 | using functor_base_ref_noexcept::operator(); 259 | DataType data_; 260 | }; 261 | 262 | struct functor_no_comparison 263 | { 264 | int operator()(int) noexcept { return {}; } 265 | int operator()(int)const noexcept { return {}; } 266 | int operator()(int)volatile noexcept { return {}; } 267 | int operator()(int)const volatile noexcept { return {}; } 268 | large_t data_; 269 | }; 270 | 271 | struct functor_no_copy_move 272 | { 273 | functor_no_copy_move() noexcept = default; 274 | functor_no_copy_move(const functor_no_copy_move &) = delete; 275 | functor_no_copy_move & operator=(const functor_no_copy_move &) = delete; 276 | functor_no_copy_move(functor_no_copy_move &&) = delete; 277 | functor_no_copy_move & operator=(functor_no_copy_move &&) = delete; 278 | int operator()(int arg) { return fn_01_v + arg; } 279 | int operator()(int arg)const { return fn_02_v + arg; } 280 | }; 281 | 282 | struct functor_throw_on_move 283 | { 284 | functor_throw_on_move() noexcept = default; 285 | functor_throw_on_move(const functor_throw_on_move &) noexcept = default; 286 | functor_throw_on_move & operator=(const functor_throw_on_move &) noexcept = default; 287 | functor_throw_on_move(functor_throw_on_move &&) 288 | { 289 | throw test_exception{}; 290 | } 291 | functor_throw_on_move & operator=(functor_throw_on_move &&) 292 | { 293 | throw test_exception{}; 294 | } 295 | int operator()(int arg) { return arg; } 296 | }; 297 | 298 | struct functor_throw_on_call 299 | { 300 | explicit functor_throw_on_call(int arg) noexcept 301 | : data_{ arg } 302 | {} 303 | functor_throw_on_call(const functor_throw_on_call & other) noexcept 304 | : data_{ other.data_ } 305 | {} 306 | functor_throw_on_call & operator=(const functor_throw_on_call & other) noexcept 307 | { 308 | data_ = other.data_; 309 | return *this; 310 | } 311 | bool operator==(const functor_throw_on_call & other) const noexcept 312 | { 313 | return data_ == other.data_; 314 | } 315 | int operator()(int) 316 | { 317 | throw test_exception{}; 318 | } 319 | int data_ = 0; 320 | }; 321 | 322 | // ========================= Functions ========================= // 323 | 324 | int function_except(int arg) 325 | { 326 | return arg + fn_01_v; 327 | } 328 | 329 | int function_noexcept(int arg) noexcept 330 | { 331 | return arg + fn_13_v; 332 | } 333 | 334 | int function_unique(int arg) noexcept 335 | { 336 | return arg; 337 | } 338 | 339 | int function_overload(int arg1, int arg2) 340 | { 341 | return arg1 + arg2; 342 | } 343 | 344 | std::string function_overload(int arg) 345 | { 346 | return std::to_string(arg); 347 | } 348 | 349 | } // namespace 350 | 351 | // Fixture to test all common delegate's operations 352 | template 353 | class DelegatesTest : public testing::Test 354 | { 355 | protected: 356 | using delegate_t = T; 357 | }; 358 | 359 | using vdk::delegate; 360 | 361 | using Types = testing::Types< 362 | delegate, delegate, delegate, 363 | delegate, delegate, delegate, 364 | delegate, delegate, delegate, 365 | delegate, delegate, delegate, 366 | delegate, delegate, delegate, 367 | delegate, delegate, delegate, 368 | delegate, delegate, delegate, 369 | delegate, delegate, delegate>; 370 | 371 | TYPED_TEST_CASE(DelegatesTest, Types); 372 | 373 | TYPED_TEST(DelegatesTest, Empty) 374 | { 375 | using delegate_t = typename TestFixture::delegate_t; 376 | 377 | // Default construction 378 | delegate_t fn0; 379 | EXPECT_FALSE(fn0); 380 | EXPECT_EQ(fn0, nullptr); 381 | EXPECT_EQ(nullptr, fn0); 382 | 383 | // Move construction 384 | delegate_t fn1; 385 | delegate_t fn2{ std::move(fn1) }; 386 | EXPECT_FALSE(fn1); 387 | EXPECT_FALSE(fn2); 388 | EXPECT_EQ(fn2, nullptr); 389 | EXPECT_EQ(nullptr, fn2); 390 | EXPECT_EQ(fn2, fn0); 391 | EXPECT_EQ(fn0, fn2); 392 | 393 | // Construction (null pointer) 394 | delegate_t fn3{ nullptr }; 395 | EXPECT_FALSE(fn3); 396 | EXPECT_EQ(fn3, nullptr); 397 | EXPECT_EQ(nullptr, fn3); 398 | EXPECT_EQ(fn3, fn0); 399 | EXPECT_EQ(fn0, fn3); 400 | 401 | // Construction (function pointer which is null pointer) 402 | int(*func_ptr_null)(int)noexcept = nullptr; 403 | delegate_t fn4{ func_ptr_null }; 404 | EXPECT_FALSE(fn4); 405 | EXPECT_EQ(fn4, nullptr); 406 | EXPECT_EQ(nullptr, fn4); 407 | EXPECT_EQ(fn4, fn0); 408 | EXPECT_EQ(fn0, fn4); 409 | 410 | // Move assignment 411 | delegate_t fn5; 412 | delegate_t fn6; 413 | fn5 = std::move(fn6); 414 | EXPECT_FALSE(fn5); 415 | EXPECT_EQ(fn5, nullptr); 416 | EXPECT_EQ(nullptr, fn5); 417 | EXPECT_EQ(fn5, fn0); 418 | EXPECT_EQ(fn0, fn5); 419 | 420 | // Assignment (null pointer) 421 | delegate_t fn7{ function_noexcept }; 422 | fn7 = nullptr; 423 | EXPECT_FALSE(fn7); 424 | EXPECT_EQ(fn7, nullptr); 425 | EXPECT_EQ(nullptr, fn7); 426 | EXPECT_EQ(fn7, fn0); 427 | EXPECT_EQ(fn0, fn7); 428 | 429 | // Assignment (function pointer which is null pointer) 430 | delegate_t fn8; 431 | fn8 = func_ptr_null; 432 | EXPECT_FALSE(fn8); 433 | EXPECT_EQ(fn8, nullptr); 434 | EXPECT_EQ(nullptr, fn8); 435 | EXPECT_EQ(fn8, fn0); 436 | EXPECT_EQ(fn0, fn8); 437 | } 438 | 439 | TYPED_TEST(DelegatesTest, Function) 440 | { 441 | using delegate_t = typename TestFixture::delegate_t; 442 | 443 | // Empty delegate 444 | delegate_t fn0; 445 | 446 | // Construction (function) 447 | delegate_t fn1{ function_noexcept }; 448 | EXPECT_TRUE(fn1); 449 | EXPECT_NE(fn1, nullptr); 450 | EXPECT_NE(nullptr, fn1); 451 | EXPECT_NE(fn1, fn0); 452 | EXPECT_NE(fn0, fn1); 453 | 454 | // Move construction 455 | delegate_t fn2{ function_noexcept }; 456 | delegate_t fn3{ std::move(fn2) }; 457 | EXPECT_FALSE(fn2); 458 | EXPECT_TRUE(fn3); 459 | EXPECT_NE(fn3, nullptr); 460 | EXPECT_NE(nullptr, fn3); 461 | EXPECT_NE(fn3, fn0); 462 | EXPECT_NE(fn0, fn3); 463 | EXPECT_EQ(fn3, fn1); 464 | EXPECT_EQ(fn1, fn3); 465 | 466 | // Construction (function pointer) 467 | int(*func_ptr)(int)noexcept = function_noexcept; 468 | delegate_t fn4{ func_ptr }; 469 | EXPECT_TRUE(fn4); 470 | EXPECT_NE(fn4, nullptr); 471 | EXPECT_NE(nullptr, fn4); 472 | EXPECT_NE(fn4, fn0); 473 | EXPECT_NE(fn0, fn4); 474 | EXPECT_EQ(fn4, fn1); 475 | EXPECT_EQ(fn1, fn4); 476 | 477 | // Move assignment 478 | delegate_t fn5; 479 | delegate_t fn6{ function_noexcept }; 480 | fn5 = std::move(fn6); 481 | EXPECT_TRUE(fn5); 482 | EXPECT_NE(fn5, nullptr); 483 | EXPECT_NE(nullptr, fn5); 484 | EXPECT_NE(fn5, fn0); 485 | EXPECT_NE(fn0, fn5); 486 | EXPECT_EQ(fn5, fn1); 487 | EXPECT_EQ(fn1, fn5); 488 | 489 | // Assignment (function) 490 | delegate_t fn7; 491 | fn7 = function_noexcept; 492 | EXPECT_TRUE(fn7); 493 | EXPECT_NE(fn7, nullptr); 494 | EXPECT_NE(nullptr, fn7); 495 | EXPECT_NE(fn7, fn0); 496 | EXPECT_NE(fn0, fn7); 497 | EXPECT_EQ(fn7, fn1); 498 | EXPECT_EQ(fn1, fn7); 499 | 500 | // Assignment (function pointer) 501 | delegate_t fn8; 502 | fn8 = func_ptr; 503 | EXPECT_TRUE(fn8); 504 | EXPECT_NE(fn8, nullptr); 505 | EXPECT_NE(nullptr, fn8); 506 | EXPECT_NE(fn8, fn0); 507 | EXPECT_NE(fn0, fn8); 508 | EXPECT_EQ(fn8, fn1); 509 | EXPECT_EQ(fn1, fn8); 510 | 511 | // Assignment (function pointer which is null pointer) 512 | int(*func_ptr_null)(int)noexcept = nullptr; 513 | delegate_t fn9{ function_noexcept }; 514 | fn9 = func_ptr_null; 515 | EXPECT_TRUE(fn9); 516 | EXPECT_NE(fn9, nullptr); 517 | EXPECT_NE(nullptr, fn9); 518 | EXPECT_NE(fn9, fn0); 519 | EXPECT_NE(fn0, fn9); 520 | EXPECT_EQ(fn9, fn1); 521 | EXPECT_EQ(fn1, fn9); 522 | } 523 | 524 | TYPED_TEST(DelegatesTest, SmallFunctor) 525 | { 526 | using delegate_t = typename TestFixture::delegate_t; 527 | 528 | // Empty delegate 529 | delegate_t fn0; 530 | 531 | functor_noexcept functor{ 10 }; 532 | 533 | // Construction (functor l-value) 534 | delegate_t fn1{ functor }; 535 | EXPECT_TRUE(fn1); 536 | EXPECT_NE(fn1, nullptr); 537 | EXPECT_NE(nullptr, fn1); 538 | EXPECT_NE(fn1, fn0); 539 | EXPECT_NE(fn0, fn1); 540 | 541 | // Construction (functor r-value) 542 | delegate_t fn2{ functor_noexcept{ 10 } }; 543 | EXPECT_TRUE(fn2); 544 | EXPECT_NE(fn2, nullptr); 545 | EXPECT_NE(nullptr, fn2); 546 | EXPECT_NE(fn2, fn0); 547 | EXPECT_NE(fn0, fn2); 548 | EXPECT_EQ(fn2, fn1); 549 | EXPECT_EQ(fn1, fn2); 550 | 551 | // Move construction 552 | delegate_t fn3{ functor }; 553 | delegate_t fn4{ std::move(fn3) }; 554 | EXPECT_FALSE(fn3); 555 | EXPECT_TRUE(fn4); 556 | EXPECT_NE(fn4, nullptr); 557 | EXPECT_NE(nullptr, fn4); 558 | EXPECT_NE(fn4, fn0); 559 | EXPECT_NE(fn0, fn4); 560 | EXPECT_EQ(fn4, fn1); 561 | EXPECT_EQ(fn1, fn4); 562 | 563 | // Move assignment 564 | delegate_t fn5; 565 | delegate_t fn6{ functor }; 566 | fn5 = std::move(fn6); 567 | EXPECT_TRUE(fn5); 568 | EXPECT_NE(fn5, nullptr); 569 | EXPECT_NE(nullptr, fn5); 570 | EXPECT_NE(fn5, fn0); 571 | EXPECT_NE(fn0, fn5); 572 | EXPECT_EQ(fn5, fn1); 573 | EXPECT_EQ(fn1, fn5); 574 | 575 | // Assignment (functor l-value) 576 | delegate_t fn7; 577 | fn7 = functor; 578 | EXPECT_TRUE(fn7); 579 | EXPECT_NE(fn7, nullptr); 580 | EXPECT_NE(nullptr, fn7); 581 | EXPECT_NE(fn7, fn0); 582 | EXPECT_NE(fn0, fn7); 583 | EXPECT_EQ(fn7, fn1); 584 | EXPECT_EQ(fn1, fn7); 585 | 586 | // Assignment (functor r-value) 587 | delegate_t fn8; 588 | fn8 = functor_noexcept{ 10 }; 589 | EXPECT_TRUE(fn8); 590 | EXPECT_NE(fn8, nullptr); 591 | EXPECT_NE(nullptr, fn8); 592 | EXPECT_NE(fn8, fn0); 593 | EXPECT_NE(fn0, fn8); 594 | EXPECT_EQ(fn8, fn1); 595 | EXPECT_EQ(fn1, fn8); 596 | 597 | // Assignment (function pointer which is null pointer) 598 | int(*func_ptr_null)(int)noexcept = nullptr; 599 | delegate_t fn9{ functor_noexcept{ 10 } }; 600 | fn9 = func_ptr_null; 601 | EXPECT_TRUE(fn9); 602 | EXPECT_NE(fn9, nullptr); 603 | EXPECT_NE(nullptr, fn9); 604 | EXPECT_NE(fn9, fn0); 605 | EXPECT_NE(fn0, fn9); 606 | EXPECT_EQ(fn9, fn1); 607 | EXPECT_EQ(fn1, fn9); 608 | } 609 | 610 | TYPED_TEST(DelegatesTest, LargeFunctor) 611 | { 612 | using delegate_t = typename TestFixture::delegate_t; 613 | 614 | // Empty delegate 615 | delegate_t fn0; 616 | 617 | functor_noexcept functor{ 10 }; 618 | 619 | // Construction (functor l-value) 620 | delegate_t fn1{ functor }; 621 | EXPECT_TRUE(fn1); 622 | EXPECT_NE(fn1, nullptr); 623 | EXPECT_NE(nullptr, fn1); 624 | EXPECT_NE(fn1, fn0); 625 | EXPECT_NE(fn0, fn1); 626 | 627 | // Construction (functor r-value) 628 | delegate_t fn2{ functor_noexcept{ 10 } }; 629 | EXPECT_TRUE(fn2); 630 | EXPECT_NE(fn2, nullptr); 631 | EXPECT_NE(nullptr, fn2); 632 | EXPECT_NE(fn2, fn0); 633 | EXPECT_NE(fn0, fn2); 634 | EXPECT_EQ(fn2, fn1); 635 | EXPECT_EQ(fn1, fn2); 636 | 637 | // Move construction 638 | delegate_t fn3{ functor }; 639 | delegate_t fn4{ std::move(fn3) }; 640 | EXPECT_FALSE(fn3); 641 | EXPECT_TRUE(fn4); 642 | EXPECT_NE(fn4, nullptr); 643 | EXPECT_NE(nullptr, fn4); 644 | EXPECT_NE(fn4, fn0); 645 | EXPECT_NE(fn0, fn4); 646 | EXPECT_EQ(fn4, fn1); 647 | EXPECT_EQ(fn1, fn4); 648 | 649 | // Move assignment 650 | delegate_t fn5; 651 | delegate_t fn6{ functor }; 652 | fn5 = std::move(fn6); 653 | EXPECT_TRUE(fn5); 654 | EXPECT_NE(fn5, nullptr); 655 | EXPECT_NE(nullptr, fn5); 656 | EXPECT_NE(fn5, fn0); 657 | EXPECT_NE(fn0, fn5); 658 | EXPECT_EQ(fn5, fn1); 659 | EXPECT_EQ(fn1, fn5); 660 | 661 | // Assignment (functor l-value) 662 | delegate_t fn7; 663 | fn7 = functor; 664 | EXPECT_TRUE(fn7); 665 | EXPECT_NE(fn7, nullptr); 666 | EXPECT_NE(nullptr, fn7); 667 | EXPECT_NE(fn7, fn0); 668 | EXPECT_NE(fn0, fn7); 669 | EXPECT_EQ(fn7, fn1); 670 | EXPECT_EQ(fn1, fn7); 671 | 672 | // Assignment (functor r-value) 673 | delegate_t fn8; 674 | fn8 = functor_noexcept{ 10 }; 675 | EXPECT_TRUE(fn8); 676 | EXPECT_NE(fn8, nullptr); 677 | EXPECT_NE(nullptr, fn8); 678 | EXPECT_NE(fn8, fn0); 679 | EXPECT_NE(fn0, fn8); 680 | EXPECT_EQ(fn8, fn1); 681 | EXPECT_EQ(fn1, fn8); 682 | 683 | // Assignment (function pointer which is null pointer) 684 | int(*func_ptr_null)(int)noexcept = nullptr; 685 | delegate_t fn9{ functor_noexcept{ 10 } }; 686 | fn9 = func_ptr_null; 687 | EXPECT_TRUE(fn9); 688 | EXPECT_NE(fn9, nullptr); 689 | EXPECT_NE(nullptr, fn9); 690 | EXPECT_NE(fn9, fn0); 691 | EXPECT_NE(fn0, fn9); 692 | EXPECT_EQ(fn9, fn1); 693 | EXPECT_EQ(fn1, fn9); 694 | } 695 | 696 | TYPED_TEST(DelegatesTest, Compare) 697 | { 698 | using delegate_t = typename TestFixture::delegate_t; 699 | 700 | // Functors of different types 701 | delegate_t fn1{ functor_noexcept{ 10 } }; 702 | delegate_t fn2{ functor_noexcept{ 10 } }; 703 | EXPECT_NE(fn1, fn2); 704 | EXPECT_NE(fn2, fn1); 705 | 706 | // Small functors of the same type (equal) 707 | delegate_t fn3{ functor_noexcept{ 10 } }; 708 | delegate_t fn4{ functor_noexcept{ 10 } }; 709 | EXPECT_EQ(fn3, fn4); 710 | EXPECT_EQ(fn4, fn3); 711 | 712 | // Large functors of the same type (equal) 713 | delegate_t fn5{ functor_noexcept{ 10 } }; 714 | delegate_t fn6{ functor_noexcept{ 10 } }; 715 | EXPECT_EQ(fn5, fn6); 716 | EXPECT_EQ(fn6, fn5); 717 | 718 | // Functors without comparison operator 719 | delegate_t fn7{ functor_no_comparison{} }; 720 | delegate_t fn8{ functor_no_comparison{} }; 721 | EXPECT_NE(fn7, fn8); 722 | EXPECT_NE(fn8, fn7); 723 | 724 | // Functors and functions 725 | delegate_t fn9{ functor_no_comparison{} }; 726 | delegate_t fn10{ functor_noexcept{ 10 } }; 727 | delegate_t fn11{ functor_noexcept{ 10 } }; 728 | delegate_t fn12{ function_noexcept }; 729 | EXPECT_NE(fn9, fn10); 730 | EXPECT_NE(fn10, fn9); 731 | EXPECT_NE(fn9, fn11); 732 | EXPECT_NE(fn11, fn9); 733 | EXPECT_NE(fn9, fn12); 734 | EXPECT_NE(fn12, fn9); 735 | EXPECT_NE(fn10, fn11); 736 | EXPECT_NE(fn11, fn10); 737 | EXPECT_NE(fn10, fn12); 738 | EXPECT_NE(fn12, fn10); 739 | EXPECT_NE(fn11, fn12); 740 | EXPECT_NE(fn12, fn11); 741 | 742 | // Small functors of the same type (not equal) 743 | delegate_t fn13{ functor_noexcept{ 1 } }; 744 | delegate_t fn14{ functor_noexcept{ 2 } }; 745 | EXPECT_NE(fn13, fn14); 746 | EXPECT_NE(fn14, fn13); 747 | 748 | // Large functors of the same type (not equal) 749 | delegate_t fn15{ functor_noexcept{ 1 } }; 750 | delegate_t fn16{ functor_noexcept{ 2 } }; 751 | EXPECT_NE(fn15, fn16); 752 | EXPECT_NE(fn16, fn15); 753 | 754 | // Same function 755 | delegate_t fn17{ function_noexcept }; 756 | delegate_t fn18{ function_noexcept }; 757 | EXPECT_EQ(fn17, fn18); 758 | EXPECT_EQ(fn18, fn17); 759 | 760 | // Different functions 761 | delegate_t fn19{ function_noexcept }; 762 | delegate_t fn20{ function_unique }; 763 | EXPECT_NE(fn19, fn20); 764 | EXPECT_NE(fn20, fn19); 765 | } 766 | 767 | TYPED_TEST(DelegatesTest, Swap) 768 | { 769 | using delegate_t = typename TestFixture::delegate_t; 770 | 771 | // Small functors 772 | delegate_t fn1{ functor_noexcept{ 1 } }; 773 | delegate_t fn2{ functor_noexcept{ 2 } }; 774 | std::swap(fn1, fn2); 775 | EXPECT_EQ(fn1, delegate_t{ functor_noexcept{ 2 } }); 776 | EXPECT_EQ(fn2, delegate_t{ functor_noexcept{ 1 } }); 777 | 778 | // Large functors 779 | delegate_t fn3{ functor_noexcept{ 1 } }; 780 | delegate_t fn4{ functor_noexcept{ 2 } }; 781 | std::swap(fn3, fn4); 782 | EXPECT_EQ(fn3, delegate_t{ functor_noexcept{ 2 } }); 783 | EXPECT_EQ(fn4, delegate_t{ functor_noexcept{ 1 } }); 784 | 785 | // Mixed functors 786 | delegate_t fn5{ functor_noexcept{ 1 } }; 787 | delegate_t fn6{ functor_noexcept{ 2 } }; 788 | std::swap(fn5, fn6); 789 | EXPECT_EQ(fn5, delegate_t{ functor_noexcept{ 2 } }); 790 | EXPECT_EQ(fn6, delegate_t{ functor_noexcept{ 1 } }); 791 | 792 | // Empty and small functor 793 | delegate_t fn7; 794 | delegate_t fn8{ functor_noexcept{ 1 } }; 795 | std::swap(fn7, fn8); 796 | EXPECT_TRUE(fn7); 797 | EXPECT_EQ(fn7, delegate_t{ functor_noexcept{ 1 } }); 798 | EXPECT_FALSE(fn8); 799 | 800 | // Empty and large functor 801 | delegate_t fn9; 802 | delegate_t fn10{ functor_noexcept{ 2 } }; 803 | std::swap(fn9, fn10); 804 | EXPECT_TRUE(fn9); 805 | EXPECT_EQ(fn9, delegate_t{ functor_noexcept{ 2 } }); 806 | EXPECT_FALSE(fn10); 807 | } 808 | 809 | TEST(DelegateTest, CallFunction) 810 | { 811 | int arg = 10; 812 | 813 | delegate fn01{ function_except }; 814 | EXPECT_EQ(fn01(arg), fn_01_v + arg); 815 | delegate fn02{ function_noexcept }; 816 | EXPECT_EQ(fn02(arg), fn_13_v + arg); 817 | } 818 | 819 | TEST(DelegateTest, CallSmallFunctor) 820 | { 821 | int arg = 10; 822 | 823 | delegate fn01{ functor_except{ 10 } }; 824 | EXPECT_EQ(fn01(arg), fn_01_v + arg); 825 | delegate fn02{ functor_except{ 10 } }; 826 | EXPECT_EQ(fn02(arg), fn_02_v + arg); 827 | delegate fn03{ functor_except{ 10 } }; 828 | EXPECT_EQ(fn03(arg), fn_03_v + arg); 829 | delegate fn04{ functor_except{ 10 } }; 830 | EXPECT_EQ(fn04(arg), fn_04_v + arg); 831 | delegate fn05{ functor_ref_except{ 10 } }; 832 | EXPECT_EQ(fn05(arg), fn_05_v + arg); 833 | delegate fn06{ functor_ref_except{ 10 } }; 834 | EXPECT_EQ(fn06(arg), fn_06_v + arg); 835 | delegate fn07{ functor_ref_except{ 10 } }; 836 | EXPECT_EQ(fn07(arg), fn_07_v + arg); 837 | delegate fn08{ functor_ref_except{ 10 } }; 838 | EXPECT_EQ(fn08(arg), fn_08_v + arg); 839 | delegate fn09{ functor_ref_except{ 10 } }; 840 | EXPECT_EQ(std::move(fn09)(arg), fn_09_v + arg); 841 | delegate fn10{ functor_ref_except{ 10 } }; 842 | EXPECT_EQ(std::move(fn10)(arg), fn_10_v + arg); 843 | delegate fn11{ functor_ref_except{ 10 } }; 844 | EXPECT_EQ(std::move(fn11)(arg), fn_11_v + arg); 845 | delegate fn12{ functor_ref_except{ 10 } }; 846 | EXPECT_EQ(std::move(fn12)(arg), fn_12_v + arg); 847 | delegate fn13{ functor_noexcept{ 10 } }; 848 | EXPECT_EQ(fn13(arg), fn_13_v + arg); 849 | delegate fn14{ functor_noexcept{ 10 } }; 850 | EXPECT_EQ(fn14(arg), fn_14_v + arg); 851 | delegate fn15{ functor_noexcept{ 10 } }; 852 | EXPECT_EQ(fn15(arg), fn_15_v + arg); 853 | delegate fn16{ functor_noexcept{ 10 } }; 854 | EXPECT_EQ(fn16(arg), fn_16_v + arg); 855 | delegate fn17{ functor_ref_noexcept{ 10 } }; 856 | EXPECT_EQ(fn17(arg), fn_17_v + arg); 857 | delegate fn18{ functor_ref_noexcept{ 10 } }; 858 | EXPECT_EQ(fn18(arg), fn_18_v + arg); 859 | delegate fn19{ functor_ref_noexcept{ 10 } }; 860 | EXPECT_EQ(fn19(arg), fn_19_v + arg); 861 | delegate fn20{ functor_ref_noexcept{ 10 } }; 862 | EXPECT_EQ(fn20(arg), fn_20_v + arg); 863 | delegate fn21{ functor_ref_noexcept{ 10 } }; 864 | EXPECT_EQ(std::move(fn21)(arg), fn_21_v + arg); 865 | delegate fn22{ functor_ref_noexcept{ 10 } }; 866 | EXPECT_EQ(std::move(fn22)(arg), fn_22_v + arg); 867 | delegate fn23{ functor_ref_noexcept{ 10 } }; 868 | EXPECT_EQ(std::move(fn23)(arg), fn_23_v + arg); 869 | delegate fn24{ functor_ref_noexcept{ 10 } }; 870 | EXPECT_EQ(std::move(fn24)(arg), fn_24_v + arg); 871 | } 872 | 873 | TEST(DelegateTest, CallLargeFunctor) 874 | { 875 | int arg = 10; 876 | 877 | delegate fn01{ functor_except{ 10 } }; 878 | EXPECT_EQ(fn01(arg), fn_01_v + arg); 879 | delegate fn02{ functor_except{ 10 } }; 880 | EXPECT_EQ(fn02(arg), fn_02_v + arg); 881 | delegate fn03{ functor_except{ 10 } }; 882 | EXPECT_EQ(fn03(arg), fn_03_v + arg); 883 | delegate fn04{ functor_except{ 10 } }; 884 | EXPECT_EQ(fn04(arg), fn_04_v + arg); 885 | delegate fn05{ functor_ref_except{ 10 } }; 886 | EXPECT_EQ(fn05(arg), fn_05_v + arg); 887 | delegate fn06{ functor_ref_except{ 10 } }; 888 | EXPECT_EQ(fn06(arg), fn_06_v + arg); 889 | delegate fn07{ functor_ref_except{ 10 } }; 890 | EXPECT_EQ(fn07(arg), fn_07_v + arg); 891 | delegate fn08{ functor_ref_except{ 10 } }; 892 | EXPECT_EQ(fn08(arg), fn_08_v + arg); 893 | delegate fn09{ functor_ref_except{ 10 } }; 894 | EXPECT_EQ(std::move(fn09)(arg), fn_09_v + arg); 895 | delegate fn10{ functor_ref_except{ 10 } }; 896 | EXPECT_EQ(std::move(fn10)(arg), fn_10_v + arg); 897 | delegate fn11{ functor_ref_except{ 10 } }; 898 | EXPECT_EQ(std::move(fn11)(arg), fn_11_v + arg); 899 | delegate fn12{ functor_ref_except{ 10 } }; 900 | EXPECT_EQ(std::move(fn12)(arg), fn_12_v + arg); 901 | delegate fn13{ functor_noexcept{ 10 } }; 902 | EXPECT_EQ(fn13(arg), fn_13_v + arg); 903 | delegate fn14{ functor_noexcept{ 10 } }; 904 | EXPECT_EQ(fn14(arg), fn_14_v + arg); 905 | delegate fn15{ functor_noexcept{ 10 } }; 906 | EXPECT_EQ(fn15(arg), fn_15_v + arg); 907 | delegate fn16{ functor_noexcept{ 10 } }; 908 | EXPECT_EQ(fn16(arg), fn_16_v + arg); 909 | delegate fn17{ functor_ref_noexcept{ 10 } }; 910 | EXPECT_EQ(fn17(arg), fn_17_v + arg); 911 | delegate fn18{ functor_ref_noexcept{ 10 } }; 912 | EXPECT_EQ(fn18(arg), fn_18_v + arg); 913 | delegate fn19{ functor_ref_noexcept{ 10 } }; 914 | EXPECT_EQ(fn19(arg), fn_19_v + arg); 915 | delegate fn20{ functor_ref_noexcept{ 10 } }; 916 | EXPECT_EQ(fn20(arg), fn_20_v + arg); 917 | delegate fn21{ functor_ref_noexcept{ 10 } }; 918 | EXPECT_EQ(std::move(fn21)(arg), fn_21_v + arg); 919 | delegate fn22{ functor_ref_noexcept{ 10 } }; 920 | EXPECT_EQ(std::move(fn22)(arg), fn_22_v + arg); 921 | delegate fn23{ functor_ref_noexcept{ 10 } }; 922 | EXPECT_EQ(std::move(fn23)(arg), fn_23_v + arg); 923 | delegate fn24{ functor_ref_noexcept{ 10 } }; 924 | EXPECT_EQ(std::move(fn24)(arg), fn_24_v + arg); 925 | } 926 | 927 | TEST(DelegateTest, Lambda) 928 | { 929 | delegate fn0; 930 | 931 | double ballast = 2.0; // Prevent lambda from turning into function 932 | auto lambda = [ballast](int arg)->int { return arg; }; 933 | auto lambda_mut = [ballast](int arg)mutable ->int { return arg; }; 934 | 935 | // Construction (lambda) 936 | delegate fn1{ lambda }; 937 | EXPECT_TRUE(fn1); 938 | EXPECT_NE(fn1, nullptr); 939 | EXPECT_NE(nullptr, fn1); 940 | EXPECT_NE(fn1, fn0); 941 | EXPECT_NE(fn0, fn1); 942 | EXPECT_EQ(fn1(10), 10); 943 | 944 | // Construction (mutable lambda) 945 | delegate fn2{ lambda_mut }; 946 | EXPECT_TRUE(fn2); 947 | EXPECT_NE(fn2, nullptr); 948 | EXPECT_NE(nullptr, fn2); 949 | EXPECT_NE(fn2, fn0); 950 | EXPECT_NE(fn0, fn2); 951 | EXPECT_EQ(fn2(10), 10); 952 | 953 | // Assignment (lambda) 954 | delegate fn3; 955 | fn3 = lambda; 956 | EXPECT_TRUE(fn3); 957 | EXPECT_NE(fn3, nullptr); 958 | EXPECT_NE(nullptr, fn3); 959 | EXPECT_NE(fn3, fn0); 960 | EXPECT_NE(fn0, fn3); 961 | EXPECT_EQ(fn3(10), 10); 962 | 963 | // Assignment (mutable lambda) 964 | delegate fn4; 965 | fn4 = lambda_mut; 966 | EXPECT_TRUE(fn4); 967 | EXPECT_NE(fn4, nullptr); 968 | EXPECT_NE(nullptr, fn4); 969 | EXPECT_NE(fn4, fn0); 970 | EXPECT_NE(fn0, fn4); 971 | EXPECT_EQ(fn4(10), 10); 972 | 973 | // Comparison 974 | delegate fn5{ lambda }; 975 | delegate fn6{ lambda }; 976 | EXPECT_NE(fn5, fn6); 977 | EXPECT_NE(fn6, fn5); 978 | } 979 | 980 | TEST(DelegateTest, MoveOnlyLambda) 981 | { 982 | auto data = std::make_unique(15); 983 | auto lambda = [d = std::move(data)](int arg) { return arg + *d; }; 984 | static_assert(!std::is_copy_constructible_v); 985 | static_assert(!std::is_copy_assignable_v); 986 | 987 | delegate fn1{ std::move(lambda) }; 988 | EXPECT_TRUE(fn1); 989 | EXPECT_EQ(fn1(5), 20); 990 | 991 | delegate fn2{ std::move(fn1) }; 992 | EXPECT_FALSE(fn1); 993 | EXPECT_TRUE(fn2); 994 | EXPECT_EQ(fn2(10), 25); 995 | } 996 | 997 | TEST(DelegateTest, NoCopyMoveRefs) 998 | { 999 | int arg = 10; 1000 | functor_no_copy_move functor; 1001 | delegate fn1{ std::ref(functor) }; 1002 | EXPECT_TRUE(fn1); 1003 | EXPECT_EQ(fn1(arg), fn_01_v + arg); 1004 | delegate fn2{ std::cref(functor) }; 1005 | EXPECT_TRUE(fn2); 1006 | EXPECT_EQ(fn2(arg), fn_02_v + arg); 1007 | } 1008 | 1009 | TEST(DelegateTest, Bind) 1010 | { 1011 | using std::placeholders::_1; 1012 | 1013 | int arg = 10; 1014 | test_class ts; 1015 | 1016 | delegate fn1{ std::bind(&test_class::method, ts, _1) }; 1017 | EXPECT_EQ(fn1(arg), fn_01_v + arg); 1018 | } 1019 | 1020 | TEST(DelegateTest, MemFn) 1021 | { 1022 | int arg = 10; 1023 | test_class ts; 1024 | 1025 | delegate fn1{ std::mem_fn(&test_class::method) }; 1026 | EXPECT_EQ(fn1(ts, arg), fn_01_v + arg); 1027 | } 1028 | 1029 | TEST(DelegateTest, ThrowOnAssignment) 1030 | { 1031 | int arg = 10; 1032 | functor_throw_on_move functor; 1033 | 1034 | // Small functor 1035 | delegate fn1{ functor_except{ 10 } }; 1036 | 1037 | try 1038 | { 1039 | fn1 = functor; 1040 | } 1041 | catch (...) 1042 | { 1043 | } 1044 | 1045 | // If assignment throws the delegate stays unaffected 1046 | EXPECT_TRUE(fn1); 1047 | EXPECT_EQ(fn1, delegate{ functor_except{ 10 } }); 1048 | EXPECT_EQ(fn1(arg), fn_01_v + arg); 1049 | 1050 | // Large functor 1051 | delegate fn2{ functor_except{ 10 } }; 1052 | 1053 | try 1054 | { 1055 | fn2 = functor; 1056 | } 1057 | catch (...) 1058 | { 1059 | } 1060 | 1061 | // If assignment throws the delegate stays unaffected 1062 | EXPECT_TRUE(fn2); 1063 | EXPECT_EQ(fn2, delegate{ functor_except{ 10 } }); 1064 | EXPECT_EQ(fn2(arg), fn_01_v + arg); 1065 | } 1066 | 1067 | TEST(DelegateTest, ThrowOnCall) 1068 | { 1069 | functor_throw_on_call functor{ 11 }; 1070 | 1071 | delegate fn1{ functor }; 1072 | 1073 | try 1074 | { 1075 | fn1(10); 1076 | } 1077 | catch (...) 1078 | { 1079 | } 1080 | 1081 | // If invocation throws the delegate stays unaffected 1082 | EXPECT_TRUE(fn1); 1083 | EXPECT_EQ(fn1, delegate{ functor_throw_on_call{ 11 } }); 1084 | } 1085 | 1086 | TEST(DelegateTest, FunctionOverload) 1087 | { 1088 | delegate fn1{ function_overload }; 1089 | EXPECT_EQ(fn1(10), std::string{ "10" }); 1090 | delegate fn2{ function_overload }; 1091 | EXPECT_EQ(fn2(3, 4), 7); 1092 | } 1093 | 1094 | TEST(DelegateTest, CallVoidNoArgs) 1095 | { 1096 | int result = 0; 1097 | auto lambda = [&result]()->void { result = 10; }; 1098 | delegate fn1{ lambda }; 1099 | fn1(); 1100 | EXPECT_EQ(result, 10); 1101 | } 1102 | 1103 | int main(int argc, char ** argv) 1104 | { 1105 | ::testing::InitGoogleTest(&argc, argv); 1106 | return RUN_ALL_TESTS(); 1107 | } --------------------------------------------------------------------------------