├── README.md ├── delegate.h └── delegate_ut.cpp /README.md: -------------------------------------------------------------------------------- 1 | # Delegate 2 | Fixed-size C++ delegates - efficient alternatives to std::function. 3 | 4 | Some performance benchmarks against std::function and SG14 (among others) here: https://github.com/jamboree/CxxFunctionBenchmark. 5 | 6 | As of the time of this comment, Delegate is the fastest of the implementations. It occupies 8 bytes plus capture size on 32-bit systems, and 8 additional bytes on 64-bit systems. 7 | 8 | There are two variants, one for capturing copyable objects (the 99% case) and one for capturing non-copyable objects (the 1% case). 9 | 10 | It depends on https://github.com/catchorg/Catch2 only for the unit tests; the delegate.h file can be included and compiled by any compliant C++17 compiler. 11 | 12 | See the unit tests for more complete examples, (e.g. to capture things like unique_ptr), but a couple of simple examples: 13 | 14 | ```c++ 15 | ... 16 | #include "delegate.h 17 | ... 18 | 19 | int func(int i) 20 | { 21 | return 17 + i; 22 | } 23 | 24 | //simple function delegate, no captures 25 | { 26 | delegate::Delegate f = &func; 27 | ... 28 | f(12); 29 | } 30 | 31 | //lambda with POD capture, but non-POD also works correctly. 32 | { 33 | double d = 99.5; 34 | double *something = &d; 35 | delegate::Delegate g = [=](const double d) 36 | { 37 | *something += d; 38 | 39 | return something; 40 | }; 41 | ... 42 | g(12.345); 43 | } 44 | ``` 45 | -------------------------------------------------------------------------------- /delegate.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | * Copyright 2019-2022 4 | * Authored by: Ben Diamand 5 | * 6 | * English version - you can use this for whatever you want. Attribution much 7 | * appreciated but not required. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 10 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 11 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 12 | * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 13 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 14 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 15 | * OTHER DEALINGS IN THE SOFTWARE. 16 | * 17 | * Anyone is free to copy, modify, publish, use, compile, sell, or 18 | * distribute this software, either in source code form or as part of a compiled 19 | * binary, for any purpose, commercial or non-commercial, and by any means, 20 | * subject to the following conditions(s): 21 | * 22 | * ** This comment block must remain in this and derived works. 23 | */ 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | /** 32 | * ^^^ Rationale ^^^ 33 | * 34 | * There are many examples of std::function replacements, but I was unable to find something that did exactly what 35 | * these classes do. For example, this page has a comparison of a plethora of std::function replacements: 36 | * https://github.com/jamboree/CxxFunctionBenchmark. 37 | * 38 | * The design goals (in order) for this implementation are: 39 | * Predictable memory and runtime -> fixed size and heapless 40 | * Simple to use. 41 | * Fast 42 | * Small 43 | * 44 | * Fixed (size) delegates are a std::function alternative, with more speed / space performance but less functionality. 45 | * The differences compared to std:function are: 46 | * (Pro) Faster than std::function for the same capture sizes 47 | * (Pro) Small fixed size, never allocates 48 | * (Pro) Documented implementation 49 | * (Pro) Uninitialized state except is handled like std::function, with operator bool 50 | * (Pro) Similar but simpler syntax to std::function 51 | * (Pro / Con) No support for RTTI or exceptions (no knowledge of exceptions at all) 52 | * (Con) Unable to store arbitrary sized captures - captures must fit the compile-time delegate size 53 | * 54 | * Note: See the accompanying unit tests for some good examples of use. 55 | */ 56 | namespace delegate 57 | { 58 | /** Allow the delegate size to be specified as a compile-time constant. */ 59 | #ifndef DELEGATE_ARGS_SIZE 60 | #define DELEGATE_ARGS_SIZE sizeof(int) + sizeof(int *) 61 | #define DELEGATE_ARGS_SIZE_UNDEF 62 | #endif 63 | #ifndef DELEGATE_ARGS_ALIGN 64 | #define DELEGATE_ARGS_ALIGN 8 65 | #define DELEGATE_ARGS_ALIGN_UNDEF 66 | #endif 67 | 68 | /** 69 | * Templated class representing the aligned storage of a delegate. The intent is for all delegates to have the 70 | * same size, and this information is purposefully not part of the delegate signature. 71 | * 72 | * @tparam size Number of bytes of storage per delegate. 73 | * @tparam alignement How to align the data. 74 | */ 75 | template 76 | struct TemplateFunctorArgs 77 | { 78 | private: 79 | /** The actual storage. */ 80 | alignas(alignment) std::array args; 81 | }; 82 | 83 | #ifdef DELEGATE_ARGS_SIZE_UNDEF 84 | #undef DELEGATE_ARGS_SIZE 85 | #undef DELEGATE_ARGS_SIZE_UNDEF 86 | #endif 87 | #ifdef DELEGATE_ARGS_ALIGN_UNDEF 88 | #undef DELEGATE_ARGS_ALIGN 89 | #undef DELEGATE_ARGS_ALIGN_UNDEF 90 | #endif 91 | 92 | /** A simplifying name to make the code more readable. */ 93 | using FunctorArgs = TemplateFunctorArgs<>; 94 | 95 | /** 96 | * Determine whether there is enough space to hold the delegate. 97 | * 98 | * @return Returns true if there is enough space, else false. 99 | */ 100 | template 101 | constexpr bool can_emplace() 102 | { 103 | return (sizeof(T) <= sizeof(FunctorArgs)) && 104 | (std::alignment_of::value % std::alignment_of::value) == 0; 105 | } 106 | 107 | /** 108 | * Determine the templated class is copyable. 109 | * 110 | * @return Returns true if the class is copyable, else false. 111 | */ 112 | template 113 | constexpr bool can_copy() 114 | { 115 | return std::is_copy_constructible::value; 116 | } 117 | 118 | /** 119 | * Reimbues a type-erased piece of memory with its original functor type. 120 | * 121 | * @tparam T The functor type. 122 | * @param args The memory to reimbue. 123 | * 124 | * @return Returns a reference to the (now properly typed) memory. 125 | */ 126 | template 127 | static T &get_typed_functor(const FunctorArgs &args) 128 | { 129 | return (T &)(args); 130 | } 131 | 132 | /** 133 | * Store a functor's associated captured data into a piece of type-erased memory. 134 | * 135 | * @tparam T The functor type. 136 | * @param args The memory to store to. 137 | * @to_store The memory to store from. 138 | */ 139 | template 140 | static void store_functor(FunctorArgs &args, const T &to_store) 141 | { 142 | ::new (&get_typed_functor(args)) T(to_store); 143 | } 144 | 145 | /** 146 | * Move a functor's associated captured data into a piece of type-erased memory. 147 | * 148 | * @tparam T The functor type. 149 | * @param args The memory to store into. 150 | * @param to_move The type to move. 151 | */ 152 | template 153 | static void move_functor(FunctorArgs &args, T &&to_move) 154 | { 155 | ::new (&get_typed_functor(args)) T(std::move(to_move)); 156 | } 157 | 158 | /** 159 | * Call the type-erased functor (with the correct type). This is a pure forwarding function, only passing along 160 | * arguments to the actual functor, i.e. a trampoline to call the real functor. 161 | * 162 | * @tparam T The functor type. 163 | * @tparam Result The return type. 164 | * @tparam Arguments The functor argument types. 165 | * @param args The functor memory (i.e. function pointer or captures). 166 | * @param arguments The functor arguments. 167 | * 168 | * @return The functor return type. 169 | */ 170 | template 171 | static Result typed_call(const FunctorArgs &args, Arguments&&... arguments) 172 | { 173 | return get_typed_functor(args)(std::forward(arguments)...); 174 | } 175 | 176 | /** Another (smaller) name for the type-erased call function. */ 177 | template 178 | using func_call = Result (*)(const FunctorArgs &args, Arguments&&... arguments); 179 | 180 | /** 181 | * Manual virtual table implementation. A virtual table is useful because there are multiple functions a full 182 | * delegate has beyond the call function (copy, move, deletion), and storing pointers for each type erased function 183 | * would make the delegate larger for no real gain (delegates are for calling - the other operations are incidental). 184 | */ 185 | struct Vtable 186 | { 187 | /** 188 | * Emits a full function static table pointer, unique to the template parameter. 189 | * 190 | * @tparam T The functor type associated with the virtual table. 191 | */ 192 | template 193 | inline static const Vtable &get_vtable() 194 | { 195 | // Fill in the vtable for this type - the same type winds up with the same pointers for each. 196 | static const Vtable vtable = 197 | { 198 | typed_copy, 199 | typed_move, 200 | typed_destroy 201 | }; 202 | 203 | return vtable; 204 | } 205 | 206 | /** Reference to the copy function. */ 207 | void (& copy)(FunctorArgs &lhs, const FunctorArgs &rhs); 208 | 209 | /** Reference to the move function. */ 210 | void (& move)(FunctorArgs &lhs, FunctorArgs &&rhs); 211 | 212 | /** Reference to the destroy function. */ 213 | void (& destroy)(FunctorArgs &args); 214 | 215 | /** 216 | * Actual code to perform a copy. 217 | * 218 | * @tparam T The functor type to copy. 219 | * @param lhs The reference to receive the copied data. 220 | * @param rhs The reference to provide the copied data. 221 | */ 222 | template(), T>::type* = nullptr> 224 | static void typed_copy(FunctorArgs &lhs, const FunctorArgs &rhs) 225 | { 226 | store_functor(lhs, get_typed_functor(rhs)); 227 | } 228 | 229 | /** 230 | * Dummy copy. 231 | * 232 | * This function exists because there are functors which cannot be copy constructed. For example, unique_ptr 233 | * doesn't allow itself to be copied. If a delegate has a unique_ptr value capture, the delegate can no longer 234 | * be copied to another delegate; that would violate the promise unique_ptr makes that there will exist only one 235 | * actual pointer value. 236 | * 237 | * @tparam T The functor type to copy. 238 | * @param lhs The reference to receive the copied data. 239 | * @param rhs The reference to provide the copied data. 240 | */ 241 | template(), T>::type* = nullptr> 243 | static void typed_copy(FunctorArgs &, const FunctorArgs &) 244 | { 245 | std::terminate(); 246 | } 247 | 248 | /** 249 | * Actual code to perform a move. 250 | * 251 | * @tparam T The functor type to move. 252 | * @param lhs The reference to receive the moved data. 253 | * @param rhs The reference to provide the moved data. 254 | */ 255 | template 256 | static void typed_move(FunctorArgs &lhs, FunctorArgs &&rhs) 257 | { 258 | move_functor(lhs, std::move(get_typed_functor(rhs))); 259 | } 260 | 261 | /** 262 | * Actual code to perform a destroy. 263 | * 264 | * @tparam T The functor type to copy. 265 | * @param args The memory of the functor type to destroy. 266 | */ 267 | template 268 | static void typed_destroy(FunctorArgs &args) 269 | { 270 | get_typed_functor(args).~T(); 271 | } 272 | }; 273 | 274 | /** 275 | * Base delegate - usable for the less common case of delegates with non-copyable captures. 276 | * 277 | * @tparam Result The delegate return type. 278 | * @tparam Arguments The delegate function arguments. 279 | */ 280 | template 281 | class FuncNonCopyable 282 | { 283 | public: 284 | /** Default constructed delegates, like std::function, are legal but uncallable. */ 285 | inline static auto badcall = [](Arguments...) -> Result {std::terminate();}; 286 | 287 | /** Set the call function to be the right one for the type passed. */ 288 | template 289 | void set_call_by_type(const T&) 290 | { 291 | call = &typed_call; 292 | } 293 | 294 | /** Set the call function to default to the badcall lambda. */ 295 | void set_bad_call() 296 | { 297 | set_call_by_type(badcall); 298 | } 299 | 300 | /** 301 | * Set the vtable to be the right one for the type passed. 302 | * 303 | * @tparam T The lambda type to use for setting the vtable. 304 | */ 305 | template 306 | void set_vtable_by_type(const T&) 307 | { 308 | vtable = &Vtable::get_vtable(); 309 | } 310 | 311 | /** 312 | * Returns whether the current call function matches the one corresponding 313 | * to the passed in template parameter. 314 | * 315 | * @tparam T The type of the lambda the check against. 316 | * @return True if the current call function is the same as the lambda. 317 | */ 318 | template 319 | bool check_same_call(T&& check) const 320 | { 321 | return call == ✓ 322 | } 323 | 324 | /** 325 | * Like std::function, returns whether it's safe to call this delegate. 326 | * 327 | * @return True if the delegate is safe to call, else false. 328 | */ 329 | explicit operator bool() const 330 | { 331 | return !check_same_call(typed_call); 332 | } 333 | 334 | /** Default constructor. Creates a valid (but uncallable) object. */ 335 | FuncNonCopyable() 336 | : call(&typed_call) 337 | , vtable(&Vtable::get_vtable()) 338 | { 339 | } 340 | 341 | /** 342 | * Converting from functor move constructor. 343 | * 344 | * @tparam T The functor type. 345 | * @param functor The functor to move. 346 | */ 347 | template 348 | explicit FuncNonCopyable(T &&functor) : 349 | call(&typed_call), 350 | vtable(&Vtable::get_vtable()) 351 | { 352 | static_assert(can_emplace(), "Delegate doesn't fit."); 353 | static_assert(std::is_same_v>, "Wrong return type."); 354 | move_functor(args, std::move(functor)); 355 | } 356 | 357 | /** 358 | * Move constructor. 359 | * 360 | * @param other The delegate to move from. 361 | */ 362 | FuncNonCopyable(FuncNonCopyable &&other) 363 | : call(other.call) 364 | , vtable(other.vtable) 365 | { 366 | other.vtable->move(args, std::move(other.args)); 367 | 368 | /** Leave other in some well defined state. */ 369 | other.set_bad_call(); 370 | } 371 | 372 | /** 373 | * Functor move assignment operator. 374 | * 375 | * @param other The delegate to move from. 376 | */ 377 | template 378 | FuncNonCopyable &operator=(T &&functor) 379 | { 380 | static_assert(can_emplace(), "Delegate doesn't fit."); 381 | static_assert(std::is_same_v>, "Wrong return type."); 382 | 383 | // Destroy whatever's currently stored before moving the parameter. 384 | vtable->destroy(args); 385 | move_functor(args, std::move(functor)); 386 | 387 | set_call_by_type(functor); 388 | set_vtable_by_type(functor); 389 | 390 | return *this; 391 | } 392 | 393 | /** 394 | * Move assignment operator. 395 | * 396 | * @param other The delegate to move from. 397 | * 398 | * @return Returns a reference to this. 399 | */ 400 | FuncNonCopyable &operator=(FuncNonCopyable &&other) 401 | { 402 | if (&other == this) 403 | { 404 | return *this; 405 | } 406 | 407 | vtable->destroy(args); 408 | 409 | other.vtable->move(args, std::move(other.args)); 410 | this->call = other.call; 411 | this->vtable = other.vtable; 412 | 413 | // Leave other in some well defined state. 414 | other.set_bad_call(); 415 | 416 | return *this; 417 | } 418 | 419 | /** 420 | * Forwarding function call operator. 421 | * 422 | * @param arguments The arguments to pass through to the delegate. 423 | * 424 | * @return Returns the Result type. 425 | */ 426 | Result operator()(Arguments... arguments) const 427 | { 428 | return call(args, std::forward(arguments)...); 429 | } 430 | 431 | /** Destructor. */ 432 | ~FuncNonCopyable() 433 | { 434 | vtable->destroy(args); 435 | } 436 | 437 | /** These must be deleted to allow for non-copyable captures (like unique_ptr). */ 438 | template 439 | FuncNonCopyable(const T &functor) = delete; 440 | FuncNonCopyable(const FuncNonCopyable &other) = delete; 441 | FuncNonCopyable &operator=(const FuncNonCopyable &other) = delete; 442 | template 443 | FuncNonCopyable &operator=(FuncNonCopyable &other) = delete; 444 | 445 | protected: 446 | /** 447 | * Construct a new Func object with both the call and vtable set to the 448 | * passed in ones. Used by the CopyableType's copy constructors. 449 | * 450 | * @tparam C The call type. 451 | * @tparam V The vtable type. 452 | * @param call_type The call function to set. 453 | * @param vtable_type The vtable to set. 454 | */ 455 | template 456 | FuncNonCopyable(C call_type, V vtable_type) 457 | : call(call_type) 458 | , vtable(vtable_type) 459 | { 460 | } 461 | 462 | /** The delegate arguments (function pointers and / or captures go here). */ 463 | FunctorArgs args; 464 | 465 | /** 466 | * Trampoline function which reimbues the type-erased delgate with its original type and calls the functor. 467 | * This is statically constructed by the compiler or copied from another value and cannot be null. 468 | */ 469 | func_call call; 470 | 471 | /** 472 | * Pointer to the manual virtual table. This is statically constructed by the compiler or copied from another 473 | * value and cannot be null. 474 | */ 475 | const Vtable *vtable; 476 | }; 477 | 478 | /** 479 | * Copyable delegate - usable for the more common case of delegates with copyable captures. 480 | * 481 | * @tparam Result The delegate return type. 482 | * @tparam Arguments The delegate function arguments. 483 | */ 484 | template 485 | class FuncCopyable : protected FuncNonCopyable 486 | { 487 | public: 488 | /** Type used to bring forward the useful functions from the base class. */ 489 | using FNC = FuncNonCopyable; 490 | using FNC::operator(); 491 | using FNC::operator bool; 492 | 493 | /** Default constructor. Leave the object in an uninitialized state (see operator bool). */ 494 | FuncCopyable() : FNC() 495 | { 496 | } 497 | 498 | /** 499 | * Converting from functor move constructor. This could have been pass by value, 500 | * but the parent's move constructor exists so this saves some code. The cost 501 | * is roughly the same. 502 | * 503 | * @tparam T The functor type. 504 | * @param functor The functor to move. 505 | */ 506 | template 507 | FuncCopyable(const T& functor) : FNC(&typed_call, &Vtable::get_vtable()) 508 | { 509 | static_assert(can_copy(), "Object is non-copyable"); 510 | store_functor(this->args, functor); 511 | } 512 | 513 | /** 514 | * Copy constructor. 515 | * 516 | * @param other The delegate to move from. 517 | */ 518 | FuncCopyable(const FuncCopyable &other) : FNC(other.call, other.vtable) 519 | { 520 | this->vtable->copy(this->args, other.args); 521 | } 522 | 523 | /** 524 | * Copy functor assignment operator. 525 | * 526 | * @param other The delegate to copy from. 527 | * 528 | * @return Returns a reference to this. 529 | */ 530 | template 531 | FuncCopyable &operator=(const T& functor) 532 | { 533 | static_assert(can_emplace(), "Delegate doesn't fit."); 534 | static_assert(std::is_same_v>, "Wrong return type."); 535 | static_assert(can_copy(), "Object is non-copyable"); 536 | 537 | store_functor(this->args, functor); 538 | FNC::template set_call_by_type(functor); 539 | FNC::template set_vtable_by_type(functor); 540 | 541 | return *this; 542 | } 543 | 544 | /** 545 | * Copy assignment operator. 546 | * 547 | * @param other The delegate to copy from. 548 | * 549 | * @return Returns a reference to this. 550 | */ 551 | FuncCopyable &operator=(const FuncCopyable &other) 552 | { 553 | if (this == &other) 554 | { 555 | return *this; 556 | } 557 | 558 | this->vtable->destroy(this->args); 559 | other.vtable->copy(this->args, other.args); 560 | this->call = other.call; 561 | this->vtable = other.vtable; 562 | 563 | return *this; 564 | } 565 | }; 566 | 567 | /** The following two are convenient names for the delegates. */ 568 | template 569 | using MoveDelegate = FuncNonCopyable; 570 | 571 | template 572 | using Delegate = FuncCopyable; 573 | } 574 | 575 | -------------------------------------------------------------------------------- /delegate_ut.cpp: -------------------------------------------------------------------------------- 1 | #define DELEGATE_ARGS_SIZE 24 2 | #define DELEGATE_ARGS_ALIGN 8 3 | #include "delegate/delegate.h" 4 | 5 | #ifdef WIN32 6 | #define DO_NOT_USE_WMAIN 7 | #define CATCH_CONFIG_WINDOWS_CRTDBG 8 | #endif 9 | #define CATCH_CONFIG_RUNNER 10 | #define CATCH_CONFIG_FAST_COMPILE 11 | #include "catch2/catch.hpp" 12 | 13 | namespace StaticFixture 14 | { 15 | static bool ran = false; 16 | static int in = 0; 17 | 18 | static void init() 19 | { 20 | ran = false; 21 | in = 0; 22 | } 23 | 24 | static void func_void() 25 | { 26 | ran = true; 27 | } 28 | static int func_int() 29 | { 30 | ran = true; 31 | return 17; 32 | } 33 | static void func_void_int(int i) 34 | { 35 | ran = true; 36 | in = i; 37 | } 38 | static int func_int_int(int i) 39 | { 40 | ran = true; 41 | in = i; 42 | return 101 + i; 43 | } 44 | }; 45 | 46 | class ClassFixture 47 | { 48 | public: 49 | bool ran; 50 | int in; 51 | 52 | static int construct_count; 53 | static int destruct_count; 54 | 55 | ClassFixture() : 56 | ran(false), 57 | in(0) 58 | { 59 | construct_count++; 60 | } 61 | 62 | ClassFixture(const ClassFixture &other) 63 | { 64 | construct_count++; 65 | if (this == &other) 66 | { 67 | return; 68 | } 69 | this->ran = other.ran; 70 | this->in = other.in; 71 | } 72 | 73 | ~ClassFixture() 74 | { 75 | destruct_count++; 76 | } 77 | 78 | static void reset_counts() 79 | { 80 | construct_count = 0; 81 | destruct_count = 0; 82 | } 83 | 84 | void func_void() 85 | { 86 | ran = true; 87 | } 88 | int func_int() 89 | { 90 | ran = true; 91 | return 17; 92 | } 93 | void func_void_int(int i) 94 | { 95 | ran = true; 96 | in = i; 97 | } 98 | int func_int_int(int i) 99 | { 100 | ran = true; 101 | in = i; 102 | return 101 + i; 103 | } 104 | }; 105 | int ClassFixture::construct_count = 0; 106 | int ClassFixture::destruct_count = 0; 107 | 108 | /** Test nove-only classes using smart pointers. */ 109 | TEST_CASE("Smart pointers", "[smart_pointer]") 110 | { 111 | ClassFixture::reset_counts(); 112 | SECTION("Assign") 113 | { 114 | { 115 | std::unique_ptr cf(new ClassFixture); 116 | delegate::MoveDelegate test([cf = std::move(cf)](int i) 117 | { 118 | return cf->func_int_int(i); 119 | }); 120 | REQUIRE(!!test == true); 121 | 122 | REQUIRE(ClassFixture::construct_count == 1); 123 | REQUIRE(test(1234) == 1335); 124 | REQUIRE(ClassFixture::destruct_count == 0); 125 | } 126 | 127 | REQUIRE(ClassFixture::construct_count == 1); 128 | REQUIRE(ClassFixture::destruct_count == 1); 129 | } 130 | SECTION("Functor Move") 131 | { 132 | { 133 | delegate::MoveDelegate test; 134 | REQUIRE(!!test == false); 135 | { 136 | std::unique_ptr cf(new ClassFixture); 137 | auto f = [cf = std::move(cf)](int i) 138 | { 139 | return cf->func_int_int(i); 140 | }; 141 | test = std::move(f); 142 | REQUIRE(!!test == true); 143 | } 144 | 145 | REQUIRE(ClassFixture::construct_count == 1); 146 | REQUIRE(test(1234) == 1335); 147 | REQUIRE(ClassFixture::destruct_count == 0); 148 | } 149 | REQUIRE(ClassFixture::construct_count == 1); 150 | REQUIRE(ClassFixture::destruct_count == 1); 151 | } 152 | SECTION("Delegate Move") 153 | { 154 | { 155 | delegate::MoveDelegate test; 156 | REQUIRE(!!test == false); 157 | { 158 | std::unique_ptr cf(new ClassFixture); 159 | auto f = [cf = std::move(cf)](int i) 160 | { 161 | return cf->func_int_int(i); 162 | }; 163 | test = std::move(f); 164 | REQUIRE(!!test == true); 165 | delegate::MoveDelegate temp(std::move(test)); 166 | REQUIRE(!!test == false); 167 | REQUIRE(!!temp == true); 168 | 169 | REQUIRE(temp(1234) == 1335); 170 | 171 | REQUIRE(ClassFixture::destruct_count == 0); 172 | } 173 | 174 | REQUIRE(ClassFixture::construct_count == 1); 175 | REQUIRE(ClassFixture::destruct_count == 1); 176 | } 177 | REQUIRE(ClassFixture::construct_count == 1); 178 | REQUIRE(ClassFixture::destruct_count == 1); 179 | } 180 | } 181 | 182 | /** Show that delegates can indeed store up to their maximum size. */ 183 | TEST_CASE("Storage Test", "[storage_test]") 184 | { 185 | uint32_t param0 = 111; 186 | uint32_t param1 = 222; 187 | uint32_t param2 = 333; 188 | uint32_t param3 = 444; 189 | uint32_t param4 = 555; 190 | uint32_t param5 = 666; 191 | 192 | static uint32_t sparam0 = 0; 193 | static uint32_t sparam1 = 0; 194 | static uint32_t sparam2 = 0; 195 | static uint32_t sparam3 = 0; 196 | static uint32_t sparam4 = 0; 197 | static uint32_t sparam5 = 0; 198 | 199 | auto lambda1 = [](){}; 200 | auto lambda4 = [param0](){sparam0 = param0;}; 201 | auto lambda8 = [param0, param1](){sparam0 = param0; sparam1 = param1;}; 202 | auto lambda12 = [param0, param1, param2](){sparam0 = param0; sparam1 = param1; sparam2 = param2;}; 203 | auto lambda16 = [param0, param1, param2, param3]() 204 | { 205 | sparam0 = param0; sparam1 = param1; sparam2 = param2; sparam3 = param3; 206 | }; 207 | auto lambda20 = [param0, param1, param2, param3, param4]() 208 | { 209 | sparam0 = param0; sparam1 = param1; sparam2 = param2; sparam3 = param3; sparam4 = param4; 210 | }; 211 | auto lambda24 = [param0, param1, param2, param3, param4, param5]() 212 | { 213 | sparam0 = param0; sparam1 = param1; sparam2 = param2; sparam3 = param3; sparam4 = param4; sparam5 = param5; 214 | }; 215 | 216 | static_assert(sizeof(lambda1) == 1, "A capture-less lambda is assumed to be one byte"); 217 | static_assert(sizeof(lambda4) == 4, "A 4-byte capture should result in a 4-byte lambda"); 218 | static_assert(sizeof(lambda8) == 8, "A 8-byte capture should result in a 8-byte lambda"); 219 | static_assert(sizeof(lambda12) == 12, "A 12-byte capture should result in a 12-byte lambda"); 220 | static_assert(sizeof(lambda16) == 16, "A 16-byte capture should result in a 16-byte lambda"); 221 | static_assert(sizeof(lambda20) == 20, "A 20-byte capture should result in a 20-byte lambda"); 222 | static_assert(sizeof(lambda24) == 24, "A 24-byte capture should result in a 24-byte lambda"); 223 | static_assert(sizeof(delegate::FunctorArgs) == 24, "This test assumes 24-bytes of storage"); 224 | 225 | delegate::Delegate delegate1 = lambda1; 226 | delegate1(); 227 | REQUIRE(sparam0 == 0); 228 | REQUIRE(sparam1 == 0); 229 | REQUIRE(sparam2 == 0); 230 | REQUIRE(sparam3 == 0); 231 | REQUIRE(sparam4 == 0); 232 | REQUIRE(sparam5 == 0); 233 | 234 | delegate::Delegate delegate4 = lambda4; 235 | delegate4(); 236 | REQUIRE(sparam0 == 111); 237 | REQUIRE(sparam1 == 0); 238 | REQUIRE(sparam2 == 0); 239 | REQUIRE(sparam3 == 0); 240 | REQUIRE(sparam4 == 0); 241 | REQUIRE(sparam5 == 0); 242 | sparam0 = 0; 243 | 244 | delegate::Delegate delegate8 = lambda8; 245 | delegate8(); 246 | REQUIRE(sparam0 == 111); 247 | REQUIRE(sparam1 == 222); 248 | REQUIRE(sparam2 == 0); 249 | REQUIRE(sparam3 == 0); 250 | REQUIRE(sparam4 == 0); 251 | REQUIRE(sparam5 == 0); 252 | sparam0 = 0; 253 | sparam1 = 0; 254 | 255 | delegate::Delegate delegate12 = lambda12; 256 | delegate12(); 257 | REQUIRE(sparam0 == 111); 258 | REQUIRE(sparam1 == 222); 259 | REQUIRE(sparam2 == 333); 260 | REQUIRE(sparam3 == 0); 261 | REQUIRE(sparam4 == 0); 262 | REQUIRE(sparam5 == 0); 263 | sparam0 = 0; 264 | sparam1 = 0; 265 | sparam2 = 0; 266 | 267 | delegate::Delegate delegate16 = lambda16; 268 | delegate16(); 269 | REQUIRE(sparam0 == 111); 270 | REQUIRE(sparam1 == 222); 271 | REQUIRE(sparam2 == 333); 272 | REQUIRE(sparam3 == 444); 273 | REQUIRE(sparam4 == 0); 274 | REQUIRE(sparam5 == 0); 275 | sparam0 = 0; 276 | sparam1 = 0; 277 | sparam2 = 0; 278 | sparam3 = 0; 279 | 280 | delegate::Delegate delegate20 = lambda20; 281 | delegate20(); 282 | REQUIRE(sparam0 == 111); 283 | REQUIRE(sparam1 == 222); 284 | REQUIRE(sparam2 == 333); 285 | REQUIRE(sparam3 == 444); 286 | REQUIRE(sparam4 == 555); 287 | REQUIRE(sparam5 == 0); 288 | sparam0 = 0; 289 | sparam1 = 0; 290 | sparam2 = 0; 291 | sparam3 = 0; 292 | sparam4 = 0; 293 | 294 | delegate::Delegate delegate24 = lambda24; 295 | delegate24(); 296 | REQUIRE(sparam0 == 111); 297 | REQUIRE(sparam1 == 222); 298 | REQUIRE(sparam2 == 333); 299 | REQUIRE(sparam3 == 444); 300 | REQUIRE(sparam4 == 555); 301 | REQUIRE(sparam5 == 666); 302 | } 303 | 304 | /** Test constructors and destructors and that delegate copies work correctly. */ 305 | TEST_CASE("Construct / Destruct", "[construct_destruct]") 306 | { 307 | SECTION("Construct / Destruct / Same Copy") 308 | { 309 | ClassFixture::reset_counts(); 310 | { 311 | static bool state_ran = false; 312 | static int state_in = 0; 313 | 314 | /* 315 | * This fixture will be created and destroyed, the temporary lambda assigned to f will also be created and 316 | * destroyed, taking its copy of ClassFixture through a cycle, and the placement new in the constructor for 317 | * the delegate will also create a lambda, but there are only destructor calls for full type, and copies 318 | * only call constructors for full types. 319 | */ 320 | ClassFixture fixture; 321 | delegate::Delegate f = [fixture](const bool dump_state, const int i) mutable 322 | { 323 | if(dump_state) 324 | { 325 | state_ran = fixture.ran; 326 | state_in = fixture.in; 327 | 328 | return 0; 329 | } 330 | else 331 | { 332 | return fixture.func_int_int(i); 333 | } 334 | }; 335 | 336 | REQUIRE(ClassFixture::construct_count == 3); 337 | REQUIRE(ClassFixture::destruct_count == 1); 338 | 339 | REQUIRE(f(false, 123) == 224); 340 | REQUIRE(state_ran == false); 341 | REQUIRE(state_in == 0); 342 | REQUIRE(f(true, 123) == 0); 343 | REQUIRE(state_ran == true); 344 | REQUIRE(state_in == 123); 345 | 346 | /* 347 | * Make a new delegate and copy over the one above. No fixture constructors or destructors are called. 348 | */ 349 | delegate::Delegate f_copy = f; 350 | REQUIRE(f_copy(true, 987) == 0); 351 | REQUIRE(state_ran == true); 352 | REQUIRE(state_in == 123); 353 | 354 | delegate::Delegate fff; 355 | 356 | /* 357 | * The new delegate keeps chugging along. 358 | */ 359 | REQUIRE(f_copy(false, 555) == 656); 360 | 361 | /* 362 | * The original delegate is unaffected. 363 | */ 364 | REQUIRE(f(true, 123) == 0); 365 | REQUIRE(state_ran == true); 366 | REQUIRE(state_in == 123); 367 | 368 | REQUIRE(ClassFixture::construct_count == 4); 369 | REQUIRE(ClassFixture::destruct_count == 1); 370 | 371 | /* 372 | * Perform a copy assignemnt. 373 | */ 374 | delegate::Delegate f_copy_assign; 375 | f_copy_assign = f; 376 | } 377 | 378 | /* 379 | * f_copy_assign was assigned f, which means a new f is constructed into f_copy_assign. 380 | */ 381 | REQUIRE(ClassFixture::construct_count == 5); 382 | REQUIRE(ClassFixture::destruct_count == 5); 383 | } 384 | } 385 | 386 | /** Test trivial functions (no captures). */ 387 | TEST_CASE("Trivial Function", "[trivial_function]") 388 | { 389 | SECTION("void") 390 | { 391 | StaticFixture::init(); 392 | delegate::Delegate f(&StaticFixture::func_void); 393 | f(); 394 | REQUIRE(StaticFixture::ran == true); 395 | REQUIRE(StaticFixture::in == 0); 396 | } 397 | SECTION("int") 398 | { 399 | StaticFixture::init(); 400 | delegate::Delegate f(&StaticFixture::func_int); 401 | REQUIRE(f() == 17); 402 | REQUIRE(StaticFixture::ran == true); 403 | REQUIRE(StaticFixture::in == 0); 404 | } 405 | SECTION("void int") 406 | { 407 | StaticFixture::init(); 408 | delegate::Delegate f(&StaticFixture::func_void_int); 409 | f(21); 410 | REQUIRE(StaticFixture::ran == true); 411 | REQUIRE(StaticFixture::in == 21); 412 | } 413 | SECTION("int int") 414 | { 415 | StaticFixture::init(); 416 | delegate::Delegate f(&StaticFixture::func_int_int); 417 | REQUIRE(f(33) == 134); 418 | REQUIRE(StaticFixture::ran == true); 419 | REQUIRE(StaticFixture::in == 33); 420 | } 421 | } 422 | 423 | /** Test class functions (implicit this captures and explicit captures). */ 424 | TEST_CASE("Class Function", "[class_function]") 425 | { 426 | SECTION("void") 427 | { 428 | ClassFixture fixture; 429 | delegate::Delegate f(std::bind(&ClassFixture::func_void, &fixture)); 430 | f(); 431 | REQUIRE(fixture.ran == true); 432 | REQUIRE(fixture.in == 0); 433 | } 434 | SECTION("int") 435 | { 436 | ClassFixture fixture; 437 | delegate::Delegate f(std::bind(&ClassFixture::func_int, &fixture)); 438 | REQUIRE(f() == 17); 439 | REQUIRE(fixture.ran == true); 440 | REQUIRE(fixture.in == 0); 441 | } 442 | SECTION("void int") 443 | { 444 | ClassFixture fixture; 445 | delegate::Delegate f(std::bind(&ClassFixture::func_void_int, 446 | &fixture, 447 | std::placeholders::_1)); 448 | f(21); 449 | REQUIRE(fixture.ran == true); 450 | REQUIRE(fixture.in == 21); 451 | } 452 | SECTION("int int") 453 | { 454 | ClassFixture fixture; 455 | delegate::Delegate f(std::bind(&ClassFixture::func_int_int, 456 | &fixture, 457 | std::placeholders::_1)); 458 | REQUIRE(f(33) == 134); 459 | REQUIRE(fixture.ran == true); 460 | REQUIRE(fixture.in == 33); 461 | } 462 | } 463 | 464 | /** Test lambda functions. */ 465 | TEST_CASE("Lambda", "[lambda]") 466 | { 467 | SECTION("void") 468 | { 469 | ClassFixture fixture; 470 | delegate::Delegate f([&fixture](){fixture.func_void();}); 471 | f(); 472 | REQUIRE(fixture.ran == true); 473 | REQUIRE(fixture.in == 0); 474 | } 475 | SECTION("int") 476 | { 477 | ClassFixture fixture; 478 | delegate::Delegate f([&fixture](){return fixture.func_int();}); 479 | REQUIRE(f() == 17); 480 | REQUIRE(fixture.ran == true); 481 | REQUIRE(fixture.in == 0); 482 | } 483 | SECTION("void int") 484 | { 485 | ClassFixture fixture; 486 | delegate::Delegate f([&fixture](int i){fixture.func_void_int(i);}); 487 | f(21); 488 | REQUIRE(fixture.ran == true); 489 | REQUIRE(fixture.in == 21); 490 | } 491 | SECTION("int int") 492 | { 493 | ClassFixture fixture; 494 | delegate::Delegate f([&fixture](int i){return fixture.func_int_int(i);}); 495 | REQUIRE(f(33) == 134); 496 | REQUIRE(fixture.ran == true); 497 | REQUIRE(fixture.in == 33); 498 | } 499 | } 500 | 501 | /** Test uninitialized delegates. */ 502 | TEST_CASE("Trivial Function Default", "[trivial_function_default]") 503 | { 504 | SECTION("default void") 505 | { 506 | delegate::Delegate f; 507 | REQUIRE(!!f == false); 508 | } 509 | SECTION("trivial default int") 510 | { 511 | delegate::Delegate f; 512 | REQUIRE(!!f == false); 513 | } 514 | SECTION("default void int") 515 | { 516 | delegate::Delegate f; 517 | REQUIRE(!!f == false); 518 | } 519 | SECTION("default int int") 520 | { 521 | delegate::Delegate f; 522 | REQUIRE(!!f == false); 523 | } 524 | } 525 | 526 | /** Test type forwarding delegates. */ 527 | TEST_CASE("Forwarding", "[forwarding]") 528 | { 529 | SECTION("non-reference type to non-reference argument") 530 | { 531 | ClassFixture::reset_counts(); 532 | { 533 | delegate::Delegate f([](ClassFixture fixture){fixture.func_void();}); 534 | ClassFixture fixture; 535 | REQUIRE(ClassFixture::construct_count == 1); 536 | REQUIRE(ClassFixture::destruct_count == 0); 537 | f(fixture); 538 | REQUIRE(ClassFixture::construct_count == 3); 539 | REQUIRE(ClassFixture::destruct_count == 2); 540 | } 541 | } 542 | SECTION("non-reference type to rvalue reference argument") 543 | { 544 | ClassFixture::reset_counts(); 545 | { 546 | delegate::Delegate< void, ClassFixture> f([](ClassFixture&& fixture){fixture.func_void();}); 547 | ClassFixture fixture; 548 | REQUIRE(ClassFixture::construct_count == 1); 549 | REQUIRE(ClassFixture::destruct_count == 0); 550 | f(fixture); 551 | REQUIRE(ClassFixture::construct_count == 2); 552 | REQUIRE(ClassFixture::destruct_count == 1); 553 | } 554 | } 555 | SECTION("rvalue reference type to non-reference argument") 556 | { 557 | ClassFixture::reset_counts(); 558 | { 559 | delegate::Delegate f([](ClassFixture fixture){fixture.func_void();}); 560 | ClassFixture fixture; 561 | REQUIRE(ClassFixture::construct_count == 1); 562 | REQUIRE(ClassFixture::destruct_count == 0); 563 | f(std::move(fixture)); 564 | REQUIRE(ClassFixture::construct_count == 2); 565 | REQUIRE(ClassFixture::destruct_count == 1); 566 | } 567 | } 568 | SECTION("rvalue reference type to rvalue reference argument") 569 | { 570 | ClassFixture::reset_counts(); 571 | { 572 | delegate::Delegate f([](ClassFixture&& fixture){fixture.func_void();}); 573 | ClassFixture fixture; 574 | REQUIRE(ClassFixture::construct_count == 1); 575 | REQUIRE(ClassFixture::destruct_count == 0); 576 | f(std::move(fixture)); 577 | REQUIRE(ClassFixture::construct_count == 1); 578 | REQUIRE(ClassFixture::destruct_count == 0); 579 | } 580 | } 581 | } 582 | 583 | void intf(int i) {printf("intf: %d\n", i);}; 584 | int main(int, char*[]) 585 | { 586 | Catch::Session session; 587 | 588 | Catch::ConfigData config_data; 589 | session.useConfigData(config_data); 590 | 591 | session.run(); 592 | 593 | return 0; 594 | } 595 | --------------------------------------------------------------------------------