├── test ├── src │ ├── main.cpp │ └── delegate.test.cpp └── CMakeLists.txt ├── .gitmodules ├── cmake ├── DelegateConfig.cmake.in └── AddSelfContainmentTest.cmake ├── .github ├── FUNDING.yml └── workflows │ ├── analysis.yml │ ├── scan-build.yml │ ├── build-macos.yaml │ ├── build-windows.yaml │ ├── coverage.yml │ └── build-ubuntu.yaml ├── LICENSE ├── CMakeLists.txt ├── README.md └── include └── delegate.hpp /test/src/main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/catch"] 2 | path = third_party/catch 3 | url = git@github.com:catchorg/Catch2.git 4 | -------------------------------------------------------------------------------- /cmake/DelegateConfig.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | if (NOT TARGET Result::Result) 4 | include("${CMAKE_CURRENT_LIST_DIR}/DelegateTargets.cmake") 5 | endif () 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [bitwizeshift] 4 | # patreon: bitwizeshift 5 | custom: https://www.buymeacoffee.com/dsq3XCcBE 6 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(source_files 2 | src/main.cpp 3 | src/delegate.test.cpp 4 | ) 5 | 6 | add_executable(${PROJECT_NAME}.test 7 | ${source_files} 8 | ) 9 | add_executable(${PROJECT_NAME}::test ALIAS ${PROJECT_NAME}.test) 10 | 11 | if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") 12 | target_compile_options(${PROJECT_NAME}.test PRIVATE 13 | "-Wno-shorten-64-to-32" 14 | "-Wno-sign-conversion" 15 | ) 16 | endif () 17 | 18 | if (MINGW AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 19 | target_compile_options(${PROJECT_NAME}.test PRIVATE "-Wa,-mbig-obj") 20 | endif () 21 | 22 | target_link_libraries(${PROJECT_NAME}.test 23 | PRIVATE ${PROJECT_NAME}::${PROJECT_NAME} 24 | PRIVATE Catch2::Catch2 25 | ) 26 | 27 | set_target_properties(${UNITTEST_TARGET_NAME} PROPERTIES 28 | CXX_STANDARD 17 29 | CXX_STANDARD_REQUIRED ON 30 | CXX_EXTENSIONS OFF 31 | ) 32 | 33 | ############################################################################## 34 | # CTest 35 | ############################################################################## 36 | 37 | include(Catch) 38 | catch_discover_tests(${PROJECT_NAME}.test) 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Copyright (c) 2017, 2018, 2020-2021 Matthew Rodusek 4 | 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /.github/workflows/analysis.yml: -------------------------------------------------------------------------------- 1 | name: "Code Scanning" 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | paths: 7 | - 'include/**.hpp' 8 | - 'test/**.cpp' 9 | - '**.cmake' 10 | - '.gitmodules' 11 | - 'CMakeLists.txt' 12 | - 'test/CMakeLists.txt' 13 | - '.github/workflows/analysis.yml' 14 | pull_request: 15 | branches: [master] 16 | paths: 17 | - 'include/**.hpp' 18 | - 'test/**.cpp' 19 | - '**.cmake' 20 | - '.gitmodules' 21 | - 'CMakeLists.txt' 22 | - 'test/CMakeLists.txt' 23 | - '.github/workflows/analysis.yml' 24 | 25 | jobs: 26 | analysis: 27 | name: CodeQL Analysis 28 | runs-on: ubuntu-20.04 29 | 30 | env: 31 | build-directory: build 32 | 33 | strategy: 34 | matrix: 35 | language: ['cpp'] 36 | 37 | steps: 38 | - name: Checkout repository 39 | uses: actions/checkout@v2 40 | with: 41 | submodules: true 42 | 43 | - name: Prepare Environment 44 | run: | 45 | cmake -E make_directory ${{env.build-directory}} 46 | 47 | - name: Initialize CodeQL 48 | uses: github/codeql-action/init@v1 49 | 50 | - name: Configure 51 | working-directory: ${{env.build-directory}} 52 | run: cmake .. -DCMAKE_BUILD_TYPE=Debug -DDELEGATE_COMPILE_UNIT_TESTS=On 53 | 54 | - name: Build 55 | working-directory: ${{env.build-directory}} 56 | run: cmake --build . 57 | 58 | - name: Perform CodeQL Analysis 59 | uses: github/codeql-action/analyze@v1 60 | -------------------------------------------------------------------------------- /.github/workflows/scan-build.yml: -------------------------------------------------------------------------------- 1 | name: "Scan Build" 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | paths: 7 | - 'include/**.hpp' 8 | - 'test/**.cpp' 9 | - '**.cmake' 10 | - '.gitmodules' 11 | - 'CMakeLists.txt' 12 | - 'test/CMakeLists.txt' 13 | - '.github/workflows/scan-build.yml' 14 | pull_request: 15 | branches: [master] 16 | paths: 17 | - 'include/**.hpp' 18 | - 'test/**.cpp' 19 | - '**.cmake' 20 | - '.gitmodules' 21 | - 'CMakeLists.txt' 22 | - 'test/CMakeLists.txt' 23 | - '.github/workflows/scan-build.yml' 24 | 25 | jobs: 26 | scan: 27 | runs-on: ubuntu-20.04 28 | 29 | env: 30 | build-directory: build 31 | artifact-directory: scan-result 32 | 33 | steps: 34 | - name: Clone 35 | uses: actions/checkout@v2 36 | with: 37 | submodules: true 38 | 39 | - name: Prepare Environment 40 | run: | 41 | sudo apt-get update 42 | sudo apt-get install -y clang clang-tools g++-multilib 43 | cmake -E make_directory ${{env.build-directory}} 44 | 45 | - name: Configure 46 | working-directory: ${{env.build-directory}} 47 | run: | 48 | scan-build -o ../${{env.artifact-directory}} cmake .. \ 49 | -DCMAKE_BUILD_TYPE=Debug \ 50 | -DDELEGATE_COMPILE_UNIT_TESTS=On 51 | 52 | - name: Scan 53 | working-directory: ${{env.build-directory}} 54 | run: | 55 | scan-build -o ../${{env.artifact-directory}} cmake --build . 56 | 57 | - uses: actions/upload-artifact@v2 58 | with: 59 | name: Scan Results 60 | path: ${{env.artifact-directory}}/ 61 | -------------------------------------------------------------------------------- /.github/workflows/build-macos.yaml: -------------------------------------------------------------------------------- 1 | name: macOS 2 | 3 | on: 4 | push: 5 | paths: 6 | - 'include/**.hpp' 7 | - 'test/**.cpp' 8 | - '**.cmake' 9 | - '.gitmodules' 10 | - 'CMakeLists.txt' 11 | - 'test/CMakeLists.txt' 12 | - '.github/workflows/build-macos.yml' 13 | pull_request: 14 | paths: 15 | - 'include/**.hpp' 16 | - 'test/**.cpp' 17 | - '**.cmake' 18 | - '.gitmodules' 19 | - 'CMakeLists.txt' 20 | - 'test/CMakeLists.txt' 21 | - '.github/workflows/build-macos.yml' 22 | 23 | jobs: 24 | test: 25 | name: macoS Xcode ${{matrix.compiler.version}} ${{matrix.arch}} ${{matrix.build_type}} 26 | runs-on: macos-latest 27 | 28 | env: 29 | build-directory: build 30 | 31 | strategy: 32 | fail-fast: false 33 | matrix: 34 | compiler: 35 | - { name: "xcode", version: "12.3" } 36 | arch: [x86_64] 37 | build_type: [Debug, Release] 38 | 39 | steps: 40 | - name: Clone 41 | uses: actions/checkout@v2 42 | with: 43 | submodules: true 44 | 45 | - name: Prepare Environment 46 | run: | 47 | ls -ls /Applications/ 48 | sudo xcode-select -switch /Applications/Xcode_${{matrix.compiler.version}}.app 49 | brew install ninja 50 | 51 | cmake -E make_directory ${{env.build-directory}} 52 | 53 | - name: Configure 54 | working-directory: ${{env.build-directory}} 55 | env: 56 | CC: clang 57 | CXX: clang++ 58 | run: | 59 | cmake .. \ 60 | -GNinja \ 61 | -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ 62 | -DDELEGATE_COMPILE_UNIT_TESTS=On 63 | 64 | - name: Build 65 | working-directory: ${{env.build-directory}} 66 | run: cmake --build . 67 | 68 | - name: Test 69 | working-directory: ${{env.build-directory}} 70 | run: ctest --output-on-failure 71 | 72 | sanitize: 73 | name: macOS Xcode ${{matrix.compiler.version}} '${{matrix.sanitizer}}' sanitizer 74 | runs-on: macos-latest 75 | needs: test 76 | 77 | env: 78 | build-directory: build 79 | 80 | strategy: 81 | matrix: 82 | sanitizer: [address, undefined] 83 | 84 | steps: 85 | - name: Clone 86 | uses: actions/checkout@v2 87 | with: 88 | submodules: true 89 | 90 | - name: Prepare Environment 91 | run: | 92 | brew install ninja 93 | cmake -E make_directory ${{env.build-directory}} 94 | 95 | - name: Configure 96 | working-directory: ${{env.build-directory}} 97 | env: 98 | CC: clang 99 | CXX: clang++ 100 | run: | 101 | cmake .. \ 102 | -GNinja \ 103 | -DCMAKE_BUILD_TYPE=Debug \ 104 | -DDELEGATE_COMPILE_UNIT_TESTS=On \ 105 | -DCMAKE_CXX_FLAGS="-fsanitize=${{matrix.sanitizer}}" 106 | 107 | - name: Build 108 | working-directory: ${{env.build-directory}} 109 | run: cmake --build . 110 | 111 | - name: Test (Sanitize) 112 | working-directory: ${{env.build-directory}} 113 | run: ctest --output-on-failure 114 | -------------------------------------------------------------------------------- /cmake/AddSelfContainmentTest.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | 3 | #.rst: 4 | # AddelfContainmentTest 5 | # --------- 6 | # 7 | # Creates a C or C++ library that includes each specified header file 8 | # independently to ensure that each header carries no expected ordering. 9 | # 10 | # This is used to avoid accidental dependencies based on the order of 11 | # inclusion. 12 | # 13 | # :: 14 | # 15 | # add_self_containment_test( 16 | # 17 | # TARGET 18 | # [EXTENSION ext] 19 | # HEADERS [headers]... 20 | # ) 21 | # 22 | # - The name of the target to create 23 | # [C|CXX] - The language check. By default, this is CXX 24 | # [headers]... - List of headers to compile 25 | # 26 | function(add_self_containment_test name) 27 | 28 | ############################### Setup output ############################### 29 | 30 | cmake_parse_arguments("CONTAINMENT" "" "TARGET;EXTENSION;DESTINATION" "HEADERS" ${ARGN}) 31 | 32 | if( TARGET "${name}" ) 33 | message(FATAL_ERROR "Specified target name already exists as a valid CMake target.") 34 | endif() 35 | 36 | if( CONTAINMENT_HEADERS ) 37 | set(header_files ${CONTAINMENT_HEADERS}) 38 | else() 39 | message(FATAL_ERROR "No HEADERS specified") 40 | endif() 41 | 42 | if( CONTAINMENT_EXTENSION ) 43 | set(extension "${CONTAINMENT_EXTENSION}" ) 44 | else() 45 | set(extension "cpp") 46 | endif() 47 | 48 | if( CONTAINMENT_DESTINATION ) 49 | set(output_dir "${CONTAINMENT_DESTINATION}") 50 | else() 51 | set(output_dir "${CMAKE_CURRENT_BINARY_DIR}/${name}") 52 | endif() 53 | 54 | if( NOT "${CONTAINMENT_TARGET}" STREQUAL "" AND TARGET "${CONTAINMENT_TARGET}") 55 | set(target "${CONTAINMENT_TARGET}") 56 | elseif( NOT TARGET "${CONTAINMENT_TARGET}" ) 57 | message(FATAL_ERROR "Specified TARGET is not a valid CMake target") 58 | elseif( "${CONTAINMENT_TARGET}" NOT STREQUAL "" ) 59 | message(FATAL_ERROR "TARGET not specified") 60 | endif() 61 | 62 | ############################### Create files ############################### 63 | 64 | set(source_files) 65 | foreach( header ${header_files} ) 66 | 67 | get_filename_component(path_segment "${header}" PATH) 68 | get_filename_component(absolute_header "${header}" ABSOLUTE) 69 | file(RELATIVE_PATH relative_header "${output_dir}/${path_segment}" "${absolute_header}") 70 | 71 | set(output_path "${output_dir}/${header}.${extension}") 72 | 73 | if( NOT EXISTS "${output_path}" OR "${absolute_header}" IS_NEWER_THAN "${output_path}" ) 74 | 75 | file(WRITE "${output_path}" "#include \"${relative_header}\" // IWYU pragma: keep") 76 | 77 | endif() 78 | 79 | set_property( DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES "${output_path}") 80 | set_property( DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${output_path}") 81 | 82 | list(APPEND source_files "${output_path}") 83 | 84 | endforeach() 85 | 86 | ###################### Create self-containment Target ###################### 87 | 88 | add_library("${name}" OBJECT 89 | ${source_files} 90 | ${header_files} 91 | ) 92 | 93 | # Iterate over the various CMake target properties, and propagate them 94 | # manually 95 | set(properties 96 | INCLUDE_DIRECTORIES 97 | SYSTEM_INCLUDE_DIRECTORIES 98 | LINK_LIBRARIES 99 | COMPILE_OPTIONS 100 | COMPILE_FEATURES 101 | COMPILE_DEFINITIONS 102 | ) 103 | 104 | # Append each property as a generator-expression 105 | foreach( property ${properties} ) 106 | set_property( 107 | TARGET "${name}" 108 | APPEND PROPERTY "${property}" 109 | "$" 110 | ) 111 | endforeach() 112 | 113 | endfunction() 114 | -------------------------------------------------------------------------------- /.github/workflows/build-windows.yaml: -------------------------------------------------------------------------------- 1 | name: Windows 2 | 3 | on: 4 | push: 5 | paths: 6 | - 'include/**.hpp' 7 | - 'test/**.cpp' 8 | - '**.cmake' 9 | - '.gitmodules' 10 | - 'CMakeLists.txt' 11 | - 'test/CMakeLists.txt' 12 | - '.github/workflows/build-windows.yml' 13 | pull_request: 14 | paths: 15 | - 'include/**.hpp' 16 | - 'test/**.cpp' 17 | - '**.cmake' 18 | - '.gitmodules' 19 | - 'CMakeLists.txt' 20 | - 'test/CMakeLists.txt' 21 | - '.github/workflows/build-windows.yml' 22 | 23 | jobs: 24 | test: 25 | name: Windows ${{matrix.compiler.name}} ${{matrix.arch}} ${{matrix.build_type}} 26 | runs-on: windows-latest 27 | env: 28 | build-directory: build 29 | 30 | strategy: 31 | fail-fast: false 32 | matrix: 33 | compiler: 34 | - { name: "gcc", cc: gcc, cxx: g++ } 35 | - { name: "clang", cc: clang, cxx: clang++ } 36 | - { name: "clang-cl", cc: clang-cl, cxx: clang-cl } 37 | - { name: "cl", cc: cl, cxx: cl } 38 | arch: [x86, x86_64] 39 | build_type: [Debug, Release] 40 | 41 | # Visual Studios specifies strange terminology for x86/x86_64 42 | include: 43 | - arch: x86 44 | vs_arch: Win32 45 | - arch: x86_64 46 | vs_arch: x64 47 | 48 | # GCC fails to compile 32-bit correctly with Github's Windows runner 49 | # configuration. 50 | exclude: 51 | - arch: x86 52 | compiler: { name: "gcc", cc: gcc, cxx: g++ } 53 | 54 | steps: 55 | 56 | - name: Clone 57 | uses: actions/checkout@v2 58 | with: 59 | submodules: true 60 | 61 | - name: Prepare Environment 62 | run: | 63 | curl -fsSL -o LLVM10.exe https://github.com/llvm/llvm-project/releases/download/llvmorg-10.0.0/LLVM-10.0.0-win64.exe ; 7z x LLVM10.exe -y -o"C:/Program Files/LLVM" 64 | 65 | cmake -E make_directory ${{env.build-directory}} 66 | 67 | - name: Prepare Architecture 68 | if: matrix.compiler.name == 'clang' && matrix.arch == 'x86' 69 | shell: bash 70 | run: echo "CXXFLAGS=${CXXFLAGS} -m32" >> ${GITHUB_ENV} 71 | 72 | - name: Configure (gcc) 73 | working-directory: ${{env.build-directory}} 74 | if: matrix.compiler.name == 'gcc' 75 | env: 76 | CC: gcc 77 | CXX: g++ 78 | run: | 79 | cmake .. -G"MinGW Makefiles" ` 80 | -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ` 81 | -DDELEGATE_COMPILE_UNIT_TESTS=On 82 | 83 | - name: Configure (clang) 84 | working-directory: ${{env.build-directory}} 85 | if: matrix.compiler.name == 'clang' 86 | run: | 87 | cmake .. -G"MinGW Makefiles" ` 88 | -DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang++.exe" ` 89 | -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ` 90 | -DDELEGATE_COMPILE_UNIT_TESTS=On 91 | 92 | - name: Configure (clang-cl) 93 | working-directory: ${{env.build-directory}} 94 | if: matrix.compiler.name == 'clang-cl' 95 | run: | 96 | cmake .. -G"Visual Studio 16 2019" -A ${{matrix.vs_arch}} -T ClangCL ` 97 | -DDELEGATE_COMPILE_UNIT_TESTS=On 98 | 99 | - name: Configure (MSVC) 100 | working-directory: ${{env.build-directory}} 101 | if: matrix.compiler.name == 'cl' 102 | run: | 103 | cmake .. -G"Visual Studio 16 2019" -A ${{matrix.vs_arch}} ` 104 | -DDELEGATE_COMPILE_UNIT_TESTS=On 105 | 106 | - name: Build 107 | working-directory: ${{env.build-directory}} 108 | run: cmake --build . --target Delegate.test --config ${{matrix.build_type}} 109 | 110 | - name: Test 111 | working-directory: ${{env.build-directory}} 112 | run: ctest --output-on-failure 113 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: "Code Coverage" 2 | 3 | on: 4 | push: 5 | paths: 6 | - 'include/**.hpp' 7 | - 'test/**.cpp' 8 | - '**.cmake' 9 | - '.gitmodules' 10 | - 'CMakeLists.txt' 11 | - 'test/CMakeLists.txt' 12 | - '.github/workflows/coverage.yml' 13 | pull_request: 14 | paths: 15 | - 'include/**.hpp' 16 | - 'test/**.cpp' 17 | - '**.cmake' 18 | - '.gitmodules' 19 | - 'CMakeLists.txt' 20 | - 'test/CMakeLists.txt' 21 | - '.github/workflows/coverage.yml' 22 | 23 | jobs: 24 | coverage: 25 | name: Ubuntu ${{matrix.compiler.cc}} Coverage 26 | runs-on: ubuntu-20.04 27 | 28 | env: 29 | build-directory: build 30 | 31 | strategy: 32 | matrix: 33 | compiler: 34 | - { cc: clang, cxx: clang++, version: "10" } 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v2 39 | with: 40 | submodules: true 41 | 42 | - name: Prepare Environment 43 | run: | 44 | sudo apt-get install -y ninja-build ${{matrix.compiler.cxx}}-${{matrix.compiler.version}} 45 | if [["${{matrix.compiler.cc}}" = "clang"]]; then 46 | sudo apt-get install -y llvm-${{matrix.compiler.version}} 47 | fi 48 | sudo apt-get install lcov 49 | cmake -E make_directory ${{env.build-directory}} 50 | 51 | - name: Configure 52 | working-directory: ${{env.build-directory}} 53 | env: 54 | CC: ${{matrix.compiler.cc}}-${{matrix.compiler.version}} 55 | CXX: ${{matrix.compiler.cxx}}-${{matrix.compiler.version}} 56 | run: | 57 | cmake .. \ 58 | -GNinja \ 59 | -DCMAKE_BUILD_TYPE=Debug \ 60 | -DDELEGATE_COMPILE_UNIT_TESTS=On \ 61 | -DCMAKE_CXX_FLAGS="--coverage" \ 62 | 63 | - name: Build 64 | working-directory: ${{env.build-directory}} 65 | run: cmake --build . 66 | 67 | - name: Test 68 | working-directory: ${{env.build-directory}} 69 | run: ctest --output-on-failure 70 | 71 | - name: Process Coverage Data 72 | working-directory: ${{env.build-directory}} 73 | run: | 74 | # Create a script for which gcov to use (needed for lcov) 75 | echo "#!/bin/bash" > gcov-executable.sh 76 | if [[ "${{matrix.compiler.cc}}" =~ "gcc" ]]; then 77 | echo 'gcov $@' >> gcov-executable.sh 78 | else 79 | echo 'llvm-cov-${{matrix.compiler.version}} gcov $@' >> gcov-executable.sh 80 | fi 81 | chmod +x gcov-executable.sh 82 | ./gcov-executable.sh $(find $(pwd) -name '*.o' -type f) 83 | 84 | # Generate coverage information 85 | lcov --capture \ 86 | --gcov-tool $(pwd)/gcov-executable.sh \ 87 | --directory . \ 88 | --output-file coverage_unfiltered.info 89 | 90 | # Strip symbols from 'test' directory 91 | lcov --remove coverage_unfiltered.info -o coverage.info \ 92 | --gcov-tool $(pwd)/gcov-executable.sh \ 93 | "/usr/*" \ 94 | "$(pwd)/*" \ 95 | "*/test/*" \ 96 | "*/third_party/*" 97 | 98 | - name: Generate Coverage 99 | uses: coverallsapp/github-action@v1.1.2 100 | with: 101 | github-token: ${{secrets.GITHUB_TOKEN}} 102 | path-to-lcov: ${{env.build-directory}}/coverage.info 103 | -------------------------------------------------------------------------------- /.github/workflows/build-ubuntu.yaml: -------------------------------------------------------------------------------- 1 | name: Ubuntu 2 | 3 | on: 4 | push: 5 | paths: 6 | - 'include/**.hpp' 7 | - 'test/**.cpp' 8 | - '**.cmake' 9 | - '.gitmodules' 10 | - 'CMakeLists.txt' 11 | - 'test/CMakeLists.txt' 12 | - '.github/workflows/build-ubuntu.yml' 13 | pull_request: 14 | paths: 15 | - 'include/**.hpp' 16 | - 'test/**.cpp' 17 | - '**.cmake' 18 | - '.gitmodules' 19 | - 'CMakeLists.txt' 20 | - 'test/CMakeLists.txt' 21 | - '.github/workflows/build-ubuntu.yml' 22 | 23 | jobs: 24 | test: 25 | name: Ubuntu ${{matrix.compiler.cc}} ${{matrix.arch}} ${{matrix.build_type}} 26 | runs-on: ubuntu-20.04 27 | 28 | env: 29 | build-directory: build 30 | 31 | strategy: 32 | fail-fast: false 33 | matrix: 34 | compiler: 35 | - { cc: gcc-10, cxx: g++-10 } 36 | - { cc: clang-10, cxx: clang++-10 } 37 | arch: [x86, x86_64] 38 | build_type: [Debug, Release] 39 | 40 | steps: 41 | 42 | - name: Checkout repository 43 | uses: actions/checkout@v2 44 | with: 45 | submodules: true 46 | 47 | - name: Prepare Environment 48 | run: | 49 | if [[ "${{matrix.arch}}" == "x86" ]]; then 50 | sudo dpkg --add-architecture i386 51 | fi 52 | sudo apt-get update 53 | sudo apt-get install -y ninja-build 54 | if [[ "${{matrix.compiler.cc}}" =~ "gcc" ]]; then 55 | sudo apt-get install -y ${{matrix.compiler.cxx}} ${{matrix.compiler.cxx}}-multilib 56 | else 57 | sudo apt-get install -y ${{matrix.compiler.cc}} g++-multilib 58 | fi 59 | cmake -E make_directory ${{env.build-directory}} 60 | 61 | - name: Prepare Architecture 62 | run: | 63 | if [[ "${{matrix.arch}}" = "x86" ]]; then 64 | echo "CXXFLAGS=${CXXFLAGS} -m32" >> ${GITHUB_ENV} 65 | fi 66 | 67 | - name: Configure 68 | working-directory: ${{env.build-directory}} 69 | env: 70 | CC: ${{matrix.compiler.cc}} 71 | CXX: ${{matrix.compiler.cxx}} 72 | run: | 73 | cmake .. \ 74 | -GNinja \ 75 | -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ 76 | -DDELEGATE_COMPILE_UNIT_TESTS=On 77 | 78 | - name: Build 79 | working-directory: ${{env.build-directory}} 80 | run: cmake --build . 81 | 82 | - name: Test 83 | working-directory: ${{env.build-directory}} 84 | run: ctest --output-on-failure 85 | 86 | sanitize: 87 | name: ${{matrix.compiler.cc}} '${{matrix.sanitizer}}' sanitizer 88 | runs-on: ubuntu-20.04 89 | needs: test 90 | 91 | env: 92 | build-directory: build 93 | 94 | strategy: 95 | matrix: 96 | compiler: 97 | - { cc: gcc, cxx: g++ } 98 | - { cc: clang, cxx: clang++ } 99 | sanitizer: [address, undefined] 100 | 101 | steps: 102 | - name: Clone 103 | uses: actions/checkout@v2 104 | with: 105 | submodules: true 106 | 107 | - name: Prepare Environment 108 | run: | 109 | sudo apt-get install -y ninja-build 110 | if [[ "${{matrix.compiler.cc}}" =~ "gcc" ]]; then 111 | sudo apt-get install -y ${{matrix.compiler.cxx}} ${{matrix.compiler.cxx}}-multilib 112 | else 113 | sudo apt-get install -y ${{matrix.compiler.cc}} g++-multilib 114 | fi 115 | cmake -E make_directory ${{env.build-directory}} 116 | 117 | - name: Configure 118 | working-directory: ${{env.build-directory}} 119 | env: 120 | CC: ${{matrix.compiler.cc}} 121 | CXX: ${{matrix.compiler.cxx}} 122 | run: | 123 | cmake .. \ 124 | -GNinja \ 125 | -DCMAKE_BUILD_TYPE=Debug \ 126 | -DCMAKE_CXX_FLAGS="-fsanitize=${{matrix.sanitizer}}" \ 127 | -DDELEGATE_COMPILE_UNIT_TESTS=On 128 | 129 | - name: Build 130 | working-directory: ${{env.build-directory}} 131 | run: cmake --build . 132 | 133 | - name: Test (Sanitize) 134 | working-directory: ${{env.build-directory}} 135 | run: ctest --output-on-failure 136 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.6) 2 | 3 | if (PROJECT_NAME) 4 | set(IS_SUBPROJECT TRUE) 5 | endif () 6 | 7 | set(CATCH_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/third_party/catch/contrib") 8 | set(DELEGATE_CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") 9 | set(DELEGATE_CMAKE_TEMPLATE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/templates") 10 | 11 | set(CMAKE_MODULE_PATH "${DELEGATE_CMAKE_MODULE_PATH}" "${CATCH_MODULE_PATH}" "${CMAKE_MODULE_PATH}") 12 | 13 | option(DELEGATE_COMPILE_UNIT_TESTS "Compile and run the unit tests for this library" ON) 14 | 15 | if (NOT CMAKE_TESTING_ENABLED AND DELEGATE_COMPILE_UNIT_TESTS) 16 | enable_testing() 17 | endif () 18 | 19 | project(Delegate 20 | VERSION "0.0.1" 21 | LANGUAGES CXX 22 | ) 23 | 24 | set(DELEGATE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR} CACHE INTERNAL "Major version of Delegate") 25 | set(DELEGATE_VERSION_MINOR ${PROJECT_VERSION_MINOR} CACHE INTERNAL "Minor version of Delegate") 26 | set(DELEGATE_VERSION_PATCH ${PROJECT_VERSION_PATCH} CACHE INTERNAL "Patch version of Delegate") 27 | set(DELEGATE_VERSION ${PROJECT_VERSION} CACHE INTERNAL "Version of Delegate") 28 | 29 | ############################################################################## 30 | # Targets 31 | ############################################################################## 32 | 33 | set(CMAKE_CXX_STANDARD 17) 34 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 35 | set(CMAKE_CXX_EXTENSIONS OFF) 36 | 37 | set(header_files 38 | include/delegate.hpp 39 | ) 40 | 41 | add_library(${PROJECT_NAME} INTERFACE) 42 | add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) 43 | 44 | target_include_directories(${PROJECT_NAME} 45 | INTERFACE $ 46 | INTERFACE $ 47 | ) 48 | 49 | if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" AND 50 | "${CMAKE_CXX_SIMULATE_ID}" STREQUAL "MSVC") 51 | # clang-cl does not appear to implement '-pedantic' or 'pedantic-errors', 52 | # so this case needs to be handled specifically 53 | add_compile_options(-Wall -Werror -Wextra) 54 | 55 | # clang-cl treats '-Wall' like '-Weverything' currently; so there are a few 56 | # warnings we need to manually disable. 57 | 58 | # Disable unnecessary compatibility and 'newline-eof'. This is a modern C++ 59 | # library -- so these serve no purpose 60 | add_compile_options(-Wno-c++98-compat -Wno-c++98-compat-pedantic) 61 | 62 | elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" OR 63 | "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") 64 | add_compile_options(-Wall -Werror -Wextra -pedantic -pedantic-errors) 65 | elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") 66 | add_compile_options(/W4 /WX) 67 | endif () 68 | 69 | include(AddSelfContainmentTest) 70 | 71 | if (DELEGATE_COMPILE_UNIT_TESTS) 72 | add_subdirectory("third_party/catch") 73 | 74 | add_self_containment_test(${PROJECT_NAME}.SelfContainmentTest 75 | TARGET ${PROJECT_NAME} 76 | HEADERS ${header_files} 77 | ) 78 | 79 | add_subdirectory("test") 80 | endif () 81 | 82 | ############################################################################## 83 | # Installation 84 | ############################################################################## 85 | 86 | if (IS_SUBPROJECT) 87 | return() 88 | endif () 89 | 90 | 91 | include(GNUInstallDirs) 92 | include(CMakePackageConfigHelpers) 93 | 94 | set(DELEGATE_CMAKE_CONFIG_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") 95 | 96 | # The generated *ConfigVersion is strongly tied to the architecture 97 | # it was generated on, and sets variables such as 'SIZEOF_VOID_P'. 98 | # Since this library is header-only, the host architecture does not 99 | # actually affect the target that consumes this project, so we fake 100 | # the variable to be empty, but reset it after. 101 | # 102 | # Otherwise a 64-bit creator would cause a 32-bit consumer to fail to 103 | # use this library, even though it's header-only. 104 | set(DELEGATE_CMAKE_SIZEOF_VOID_P ${CMAKE_SIZEOF_VOID_P}) 105 | set(CMAKE_SIZEOF_VOID_P "") 106 | write_basic_package_version_file( 107 | "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" 108 | VERSION "${PROJECT_VERSION}" 109 | COMPATIBILITY "SameMajorVersion" 110 | ) 111 | set(CMAKE_SIZEOF_VOID_P ${DELEGATE_CMAKE_SIZEOF_VOID_P}) 112 | 113 | configure_package_config_file( 114 | "${CMAKE_CURRENT_LIST_DIR}/cmake/${PROJECT_NAME}Config.cmake.in" 115 | "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" 116 | INSTALL_DESTINATION "${DELEGATE_CMAKE_CONFIG_DESTINATION}" 117 | ) 118 | 119 | # Targets 120 | install( 121 | TARGETS "${PROJECT_NAME}" 122 | EXPORT "${PROJECT_NAME}Targets" 123 | DESTINATION "${CMAKE_INSTALL_LIBDIR}" 124 | ) 125 | install( 126 | EXPORT "${PROJECT_NAME}Targets" 127 | NAMESPACE "${PROJECT_NAME}::" 128 | DESTINATION "${DELEGATE_CMAKE_CONFIG_DESTINATION}" 129 | ) 130 | install( 131 | FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" 132 | "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" 133 | DESTINATION "${DELEGATE_CMAKE_CONFIG_DESTINATION}" 134 | ) 135 | 136 | # Includes 137 | install( 138 | DIRECTORY "include/" 139 | DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" 140 | ) 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unbelievably Fast Delegate 2 | 3 | [![Ubuntu Build Status](https://github.com/bitwizeshift/Delegate/workflows/Ubuntu/badge.svg?branch=master)](https://github.com/bitwizeshift/Delegate/actions?query=workflow%3AUbuntu) 4 | [![macOS Build Status](https://github.com/bitwizeshift/Delegate/workflows/macOS/badge.svg?branch=master)](https://github.com/bitwizeshift/Delegate/actions?query=workflow%3AmacOS) 5 | [![Windows Build Status](https://github.com/bitwizeshift/Delegate/workflows/Windows/badge.svg?branch=master)](https://github.com/bitwizeshift/Delegate/actions?query=workflow%3AWindows) 6 | [![Coverage Status](https://coveralls.io/repos/github/bitwizeshift/Delegate/badge.svg?branch=master)](https://coveralls.io/github/bitwizeshift/Delegate?branch=master) 7 | [![Github Issues](https://img.shields.io/github/issues/bitwizeshift/Delegate.svg)](http://github.com/bitwizeshift/Delegate/issues) 8 |
9 | [![Github Releases](https://img.shields.io/github/v/release/bitwizeshift/Delegate.svg?include_prereleases)](https://github.com/bitwizeshift/Delegate/releases) 10 | 11 | **Delegate** is an unbelievably fast, lightweight, and 0-overhead function 12 | container. 13 | 14 | ## Teaser 15 | 16 | ```cpp 17 | cpp::delegate d = cpp::bind<&std::strlen>(); 18 | 19 | assert(d("hello world") == 11u); 20 | assert(d.has_target<&std::strlen>()); 21 | ``` 22 | 23 | See the [quick start guide](#quick-start) below for how to use this type. 24 | 25 | ## Features 26 | 27 | * [x] Uses absolutely no heap memory 28 | * [x] Invocations of bound functions execute as fast as raw function pointers 29 | * [x] Statically binds functions, member functions, and small callable objects 30 | * [x] `delegate` objects are trivially copyable and trivially destructible 31 | * [x] Written in modern C++17 32 | * [x] Single-header, **header-only** solution -- easily drops into any project 33 | 34 | ## Background 35 | 36 | The C++ standard library provides a few type-erased function containers, such as 37 | `std::function` and `std::packaged_task`, but neither of them provide any 38 | **guaranteed performance characteristics**. In particular, there is no telling 39 | how large an object is, what the cost is to relocating an object, and -- more 40 | importantly -- where that object *lives* either on the heap or on the stack. 41 | 42 | Although type-erasure usually has a cost, the reality of most programs is that 43 | bound functions are almost always known **at compile time**. The `delegate` 44 | type leverages this fact to provide a fast, 0-overhead, lightweight solution. 45 | 46 | The `delegate` object is tiny -- only 2 pointers in size, and trivially cheap 47 | to copy and destroy. There are only two costs to this design: 48 | 49 | 1. Functions must be either statically known, or small callable objects such as 50 | an empty lambda 51 | 2. To create a `delegate`, you must call a `bind` function. 52 | 53 | ## Quick-Start 54 | 55 | ### Binding to delegates 56 | 57 | To create a `cpp::delegate`, you need to pass it the result of a `cpp::bind` 58 | call. 59 | 60 | `cpp::bind` has overloads for the following bindable targets: 61 | 62 | * Statically-specified functions: `cpp::bind<&std::strlen>()` 63 | * Statically-specified members: `cpp::bind<&std::string::length>(&str)` 64 | * Local (viewed) functors: `cpp::bind(&func)` 65 | * Empty trivial functors: `cpp::bind>()` 66 | * Small functors: `cpp::bind([x}{/* some lambda */})` 67 | * Opaque function pointers: `cpp::bind( (void(*)) ::dlsym(...) )` 68 | 69 | For example: 70 | 71 | ```cpp 72 | std::string str{"hello world"}; 73 | 74 | cpp::delegate d = cpp::bind<&std::string::length>(&str); 75 | 76 | assert(d() == 11); 77 | ``` 78 | 79 | Bound functions don't need to be exactly the same as the signature, as implicit 80 | conversions are valid (such as the above example with `long` instead of 81 | `std::string::size_type`). 82 | 83 | Additionally, you can also leverage CTAD to deduce the function type for any 84 | function pointers or member function pointer inputs: 85 | 86 | ```cpp 87 | std::string str{"hello world"}; 88 | 89 | // deduces cpp::delegate 90 | cpp::delegate d = cpp::bind<&std::string::length>(&str); 91 | ``` 92 | 93 | ### Binding Opaque Functions 94 | 95 | `delegate` supports binding both statically specified and opaque specified 96 | function pointers. In general, most uses will be static -- where a user is 97 | already aware of what the function pointer is explicitly named, such as: 98 | 99 | ```cpp 100 | cpp::delegate d = cpp::bind<&std::strlen>(); 101 | ``` 102 | 103 | However there may be cases when interacting with other APIs, such as `::dlsym`, 104 | where the actual symbol for the function is not expressable. In such cases, the 105 | function may be bound opaquely: 106 | 107 | ```cpp 108 | auto func = reinterpret_cast(::dlsym(...)); 109 | 110 | cpp::delegate d = cpp::bind(func); 111 | ``` 112 | 113 | Although this overload also works with statically specified functions as well, 114 | it should be avoided for general use. Statically specifying functions helps the 115 | compiler for the purposes of inlining. Opaque pointers, on the other hand, may 116 | experience two separate levels of indirections for the invocation. 117 | 118 | ### Querying Bound Targets 119 | 120 | Like `std::function`, the underlying target of the bound `delegate` may be 121 | queried. This may be done using one of the available `cpp::delegate::has_target` 122 | functions. Each overload of `has_target` corresponds to the same inputs as the 123 | respective `cpp::bind` call. 124 | 125 | ```cpp 126 | cpp::delegate d = cpp::bind<&std::strlen>(); 127 | 128 | assert( d.has_target<&std::strlen>() ); 129 | ``` 130 | 131 | -------------------------------------------------------------------------------- 132 | 133 | **Note:** `has_target` will only return `true` if a target was bound using the 134 | equivalent `cpp::bind` function. This means that a statically-specified function 135 | bound with `cpp::bind` cannot be queried using the opaque `has_target` overload: 136 | 137 | ```cpp 138 | cpp::delegate d = cpp::bind<&std::strlen>(); 139 | 140 | assert( not d.has_target(&std::strlen) ); 141 | ``` 142 | 143 | Always make sure not to mix these up if there is a need to query targets! 144 | 145 | ## Optional Features 146 | 147 | Although not required, **Delegate** supports custom namespaces 148 | 149 | ### Using a Custom Namespace 150 | 151 | The `namespace` that `delegate` is defined in is configurable. By default, 152 | it is defined in `namespace cpp`; however this can be toggled by defining 153 | the preprocessor symbol `DELEGATE_NAMESPACE` to be the name of the desired 154 | namespace. 155 | 156 | This could be done either through a `#define` preprocessor directive: 157 | 158 | ```cpp 159 | #define DELEGATE_NAMESPACE example 160 | #include 161 | 162 | example::delegate d = example::bind<&std::strlen>(); 163 | ``` 164 | 165 | Or it could also be defined using the compile-time definition with `-D`, such 166 | as: 167 | 168 | `g++ -std=c++11 -DDELEGATE_NAMESPACE=example test.cpp` 169 | 170 | ```cpp 171 | #include 172 | 173 | example::delegate d = example::bind<&std::strlen>(); 174 | ``` 175 | 176 | ## License 177 | 178 | This project is licensed under the extremely permissive 179 | [Boost License](https://opensource.org/licenses/BSL-1.0): 180 | 181 | > Boost Software License - Version 1.0 - August 17th, 2003 182 | > 183 | > Copyright (c) 2017, 2018, 2020-2021 Matthew Rodusek 184 | > 185 | > Permission is hereby granted, free of charge, to any person or organization 186 | > obtaining a copy of the software and accompanying documentation covered by 187 | > this license (the "Software") to use, reproduce, display, distribute, 188 | > execute, and transmit the Software, and to prepare derivative works of the 189 | > Software, and to permit third-parties to whom the Software is furnished to 190 | > do so, all subject to the following: 191 | > 192 | > The copyright notices in the Software and this entire statement, including 193 | > the above license grant, this restriction and the following disclaimer, 194 | > must be included in all copies of the Software, in whole or in part, and 195 | > all derivative works of the Software, unless such copies or derivative 196 | > works are solely in the form of machine-executable object code generated by 197 | > a source language processor. 198 | > 199 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 200 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 201 | > FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 202 | > SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 203 | > FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 204 | > ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 205 | > DEALINGS IN THE SOFTWARE. 206 | -------------------------------------------------------------------------------- /test/src/delegate.test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #if defined(_MSC_VER) 7 | # pragma warning(push) 8 | // MSVC warns on implicit casts for the covariant functions. Since this is a 9 | // test, this diagnostic is useless 10 | # pragma warning(disable:4267) 11 | #endif 12 | 13 | namespace DELEGATE_NAMESPACE_INTERNAL { 14 | inline namespace bitwizeshift { 15 | namespace test { 16 | namespace { 17 | 18 | auto square(int x) -> int { 19 | return x * x; 20 | } 21 | auto square_out(int& x) -> int { 22 | return (x = (x * x)); 23 | } 24 | 25 | struct adder 26 | { 27 | int x; 28 | 29 | auto set(int y) -> int { 30 | return (x = y); 31 | } 32 | 33 | auto add(int y) const -> int 34 | { 35 | return x + y; 36 | } 37 | auto out_add(int* out, int y) const -> int 38 | { 39 | return ((*out) = (x + y)); 40 | } 41 | auto operator()(int y) const -> int { 42 | return x + y; 43 | } 44 | }; 45 | 46 | struct loader 47 | { 48 | template 49 | auto operator()(T* out, const T& in) const -> T& 50 | { 51 | return ((*out) = in); 52 | } 53 | }; 54 | 55 | struct square_out_functor 56 | { 57 | auto operator()(int* out, int x) -> int 58 | { 59 | return ((*out) = (x * x)); 60 | } 61 | }; 62 | 63 | } // namespace 64 | 65 | //------------------------------------------------------------------------------ 66 | // Constructors 67 | //------------------------------------------------------------------------------ 68 | 69 | TEST_CASE("delegate::delegate()") { 70 | const auto sut = delegate{}; 71 | 72 | SECTION("Creates unbound delegate") { 73 | REQUIRE(!static_cast(sut)); 74 | } 75 | SECTION("Throws when invoked") { 76 | REQUIRE_THROWS_AS(sut(), bad_delegate_call); 77 | } 78 | } 79 | 80 | TEST_CASE("delegate::delegate(function_bind_target)") { 81 | SECTION("Function being bound has same signature") { 82 | const delegate sut = bind<&square>(); 83 | 84 | SECTION("Calls bound function") { 85 | REQUIRE(sut(2) == 4); 86 | } 87 | SECTION("Has bound function") { 88 | REQUIRE(sut.has_target<&square>()); 89 | } 90 | } 91 | 92 | SECTION("Function being bound has similar signature") { 93 | const delegate sut = bind<&square>(); 94 | 95 | SECTION("Calls bound function") { 96 | REQUIRE(sut(2) == 4); 97 | } 98 | SECTION("Has bound function") { 99 | REQUIRE(sut.has_target<&square>()); 100 | } 101 | } 102 | 103 | SECTION("Delegate returns void") { 104 | delegate sut = bind<&square_out>(); 105 | 106 | SECTION("Calls bound function") { 107 | auto output = 2; 108 | sut(output); 109 | REQUIRE(output == 4); 110 | } 111 | SECTION("Has bound function") { 112 | REQUIRE(sut.has_target<&square_out>()); 113 | } 114 | } 115 | } 116 | 117 | //------------------------------------------------------------------------------ 118 | 119 | TEST_CASE("delegate::delegate(member_bind_target)") { 120 | SECTION("Function being bound has same signature") { 121 | auto a = adder{42}; 122 | 123 | const delegate sut = bind<&adder::set>(&a); 124 | 125 | SECTION("Calls bound function") { 126 | REQUIRE(sut(2) == 2); 127 | } 128 | SECTION("Has bound function") { 129 | REQUIRE(sut.has_target<&adder::set>(&a)); 130 | } 131 | } 132 | 133 | SECTION("Function being bound has similar signature") { 134 | auto a = adder{42}; 135 | 136 | const delegate sut = bind<&adder::set>(&a); 137 | 138 | SECTION("Calls bound function") { 139 | REQUIRE(sut(2) == 2); 140 | } 141 | SECTION("Has bound function") { 142 | REQUIRE(sut.has_target<&adder::set>(&a)); 143 | } 144 | } 145 | 146 | SECTION("Delegate returns void") { 147 | auto a = adder{42}; 148 | 149 | const delegate sut = bind<&adder::set>(&a); 150 | 151 | SECTION("Calls bound function") { 152 | const auto input = 2; 153 | sut(input); 154 | REQUIRE(a.x == input); 155 | } 156 | SECTION("Has bound function") { 157 | REQUIRE(sut.has_target<&adder::set>(&a)); 158 | } 159 | } 160 | } 161 | 162 | //------------------------------------------------------------------------------ 163 | 164 | TEST_CASE("delegate::delegate(member_bind_target)") { 165 | SECTION("Function being bound has same signature") { 166 | const auto x = 42; 167 | const auto a = adder{x}; 168 | 169 | const delegate sut = bind<&adder::add>(&a); 170 | 171 | SECTION("Calls bound function") { 172 | REQUIRE(sut(2) == 44); 173 | } 174 | SECTION("Has bound function") { 175 | REQUIRE(sut.has_target<&adder::add>(&a)); 176 | } 177 | } 178 | 179 | SECTION("Function being bound has similar signature") { 180 | const auto x = 42; 181 | const auto a = adder{x}; 182 | 183 | const delegate sut = bind<&adder::add>(&a); 184 | 185 | SECTION("Calls bound function") { 186 | REQUIRE(sut(2) == 44); 187 | } 188 | SECTION("Has bound function") { 189 | REQUIRE(sut.has_target<&adder::add>(&a)); 190 | } 191 | } 192 | 193 | SECTION("Delegate returns void") { 194 | const auto x = 42; 195 | const auto a = adder{x}; 196 | 197 | const delegate sut = bind<&adder::out_add>(&a); 198 | 199 | SECTION("Calls bound function") { 200 | auto out = 0; 201 | sut(&out, 2); 202 | REQUIRE(out == 44); 203 | } 204 | SECTION("Has bound function") { 205 | REQUIRE(sut.has_target<&adder::out_add>(&a)); 206 | } 207 | } 208 | } 209 | 210 | //------------------------------------------------------------------------------ 211 | 212 | TEST_CASE("delegate::delegate(callable_ref_bind_target)") { 213 | SECTION("Function being bound has same signature") { 214 | const auto x = 42; 215 | auto a = adder{x}; 216 | 217 | const delegate sut = bind(&a); 218 | 219 | SECTION("Calls bound function") { 220 | REQUIRE(sut(2) == 44); 221 | } 222 | SECTION("Has bound function") { 223 | REQUIRE(sut.has_target(&a)); 224 | } 225 | } 226 | 227 | SECTION("Function being bound has similar signature") { 228 | const auto x = 42; 229 | auto a = adder{x}; 230 | 231 | const delegate sut = bind(&a); 232 | 233 | SECTION("Calls bound function") { 234 | REQUIRE(sut(2) == 44); 235 | } 236 | SECTION("Has bound function") { 237 | REQUIRE(sut.has_target(&a)); 238 | } 239 | } 240 | 241 | SECTION("Delegate returns void") { 242 | auto a = loader{}; 243 | 244 | const delegate sut = bind(&a); 245 | 246 | SECTION("Calls bound function") { 247 | auto out = 0; 248 | const auto expected = 2; 249 | 250 | sut(&out, expected); 251 | 252 | REQUIRE(out == expected); 253 | } 254 | SECTION("Has bound function") { 255 | REQUIRE(sut.has_target(&a)); 256 | } 257 | } 258 | } 259 | 260 | //------------------------------------------------------------------------------ 261 | 262 | TEST_CASE("delegate::delegate(callable_ref_bind_target)") { 263 | SECTION("Function being bound has same signature") { 264 | const auto x = 42; 265 | const auto a = adder{x}; 266 | 267 | const delegate sut = bind(&a); 268 | 269 | SECTION("Calls bound function") { 270 | REQUIRE(sut(2) == 44); 271 | } 272 | SECTION("Has bound function") { 273 | REQUIRE(sut.has_target(&a)); 274 | } 275 | } 276 | 277 | SECTION("Function being bound has similar signature") { 278 | const auto x = 42; 279 | const auto a = adder{x}; 280 | 281 | const delegate sut = bind(&a); 282 | 283 | SECTION("Calls bound function") { 284 | REQUIRE(sut(2) == 44); 285 | } 286 | SECTION("Has bound function") { 287 | REQUIRE(sut.has_target(&a)); 288 | } 289 | } 290 | 291 | SECTION("Delegate returns void") { 292 | const auto a = loader{}; 293 | 294 | const delegate sut = bind(&a); 295 | 296 | SECTION("Calls bound function") { 297 | auto out = 0; 298 | const auto expected = 2; 299 | 300 | sut(&out, expected); 301 | 302 | REQUIRE(out == expected); 303 | } 304 | SECTION("Has bound function") { 305 | REQUIRE(sut.has_target(&a)); 306 | } 307 | } 308 | } 309 | 310 | //------------------------------------------------------------------------------ 311 | 312 | TEST_CASE("delegate::delegate(empty_callable_target)") { 313 | SECTION("Target is bound statically") { 314 | SECTION("Function being bound has same signature") { 315 | const delegate sut = bind>(); 316 | 317 | SECTION("Calls bound function") { 318 | REQUIRE(sut(42) == std::hash{}(42)); 319 | } 320 | SECTION("Has bound function") { 321 | REQUIRE(sut.has_target>()); 322 | } 323 | } 324 | 325 | SECTION("Function being bound has similar signature") { 326 | const delegate sut = bind>(); 327 | 328 | SECTION("Calls bound function") { 329 | REQUIRE(sut(42) == static_cast(std::hash{}(42))); 330 | } 331 | SECTION("Has bound function") { 332 | REQUIRE(sut.has_target>()); 333 | } 334 | } 335 | 336 | SECTION("Delegate returns void") { 337 | const delegate sut = bind(); 338 | 339 | SECTION("Calls bound function") { 340 | auto out = 0; 341 | sut(&out, 2); 342 | 343 | REQUIRE(out == 4); 344 | } 345 | SECTION("Has bound function") { 346 | REQUIRE(sut.has_target()); 347 | } 348 | } 349 | } 350 | 351 | SECTION("Target is bound at runtime") { 352 | SECTION("Function being bound has same signature") { 353 | const delegate sut = bind(std::hash{}); 354 | 355 | SECTION("Calls bound function") { 356 | REQUIRE(sut(42) == std::hash{}(42)); 357 | } 358 | SECTION("Has bound function") { 359 | REQUIRE(sut.has_target(std::hash{})); 360 | } 361 | } 362 | 363 | SECTION("Function being bound has similar signature") { 364 | const delegate sut = bind(std::hash{}); 365 | 366 | SECTION("Calls bound function") { 367 | REQUIRE(sut(42) == static_cast(std::hash{}(42))); 368 | } 369 | SECTION("Has bound function") { 370 | REQUIRE(sut.has_target(std::hash{})); 371 | } 372 | } 373 | 374 | SECTION("Delegate returns void") { 375 | const delegate sut = bind(square_out_functor{}); 376 | 377 | SECTION("Calls bound function") { 378 | auto out = 0; 379 | sut(&out, 2); 380 | 381 | REQUIRE(out == 4); 382 | } 383 | SECTION("Has bound function") { 384 | REQUIRE(sut.has_target(square_out_functor{})); 385 | } 386 | } 387 | } 388 | } 389 | 390 | //------------------------------------------------------------------------------ 391 | 392 | TEST_CASE("delegate::delegate(callable_bind_target)") { 393 | SECTION("Function being bound has same signature") { 394 | int x = 42; 395 | const auto target = [x](int y){ 396 | return x + y; 397 | }; 398 | const delegate sut = bind(target); 399 | 400 | SECTION("Calls bound function") { 401 | REQUIRE(sut(10) == 52); 402 | } 403 | SECTION("Has bound function") { 404 | REQUIRE(sut.has_target(target)); 405 | } 406 | } 407 | 408 | SECTION("Function being bound has similar signature") { 409 | int x = 42; 410 | const auto target = [x](int y) { 411 | return x + y; 412 | }; 413 | 414 | const delegate sut = bind(target); 415 | 416 | SECTION("Calls bound function") { 417 | REQUIRE(sut(10) == 52); 418 | } 419 | SECTION("Has bound function") { 420 | REQUIRE(sut.has_target(target)); 421 | } 422 | } 423 | 424 | SECTION("Delegate returns void") { 425 | int x = 42; 426 | const auto target = [x](int* out, int y) -> int{ 427 | return (*out = (x + y)); 428 | }; 429 | 430 | const delegate sut = bind(target); 431 | 432 | SECTION("Calls bound function") { 433 | auto out = 0; 434 | sut(&out, 10); 435 | 436 | REQUIRE(out == 52); 437 | } 438 | SECTION("Has bound function") { 439 | REQUIRE(sut.has_target(target)); 440 | } 441 | } 442 | } 443 | 444 | TEST_CASE("delegate::delegate(opaque_function_bind_target)") { 445 | SECTION("Function being bound has same signature") { 446 | const delegate sut = bind(&square); 447 | 448 | SECTION("Calls bound function") { 449 | REQUIRE(sut(2) == 4); 450 | } 451 | SECTION("Has bound function") { 452 | REQUIRE(sut.has_target(&square)); 453 | } 454 | } 455 | 456 | SECTION("Function being bound has similar signature") { 457 | const delegate sut = bind(&square); 458 | 459 | SECTION("Calls bound function") { 460 | REQUIRE(sut(2) == 4); 461 | } 462 | SECTION("Has bound function") { 463 | REQUIRE(sut.has_target(&square)); 464 | } 465 | } 466 | 467 | SECTION("Delegate returns void") { 468 | delegate sut = bind(&square_out); 469 | 470 | SECTION("Calls bound function") { 471 | auto output = 2; 472 | sut(output); 473 | REQUIRE(output == 4); 474 | } 475 | SECTION("Has bound function") { 476 | REQUIRE(sut.has_target(&square_out)); 477 | } 478 | } 479 | } 480 | 481 | //------------------------------------------------------------------------------ 482 | 483 | TEST_CASE("delegate::bind()") { 484 | SECTION("Function being bound has same signature") { 485 | auto sut = delegate{}; 486 | sut.bind<&square>(); 487 | 488 | SECTION("Calls bound function") { 489 | REQUIRE(sut(2) == 4); 490 | } 491 | SECTION("Has bound function") { 492 | REQUIRE(sut.has_target<&square>()); 493 | } 494 | } 495 | 496 | SECTION("Function being bound has similar signature") { 497 | auto sut = delegate{}; 498 | sut.bind<&square>(); 499 | 500 | SECTION("Calls bound function") { 501 | REQUIRE(sut(2) == 4); 502 | } 503 | SECTION("Has bound function") { 504 | REQUIRE(sut.has_target<&square>()); 505 | } 506 | } 507 | 508 | SECTION("Delegate returns void") { 509 | auto sut = delegate{}; 510 | sut.bind<&square_out>(); 511 | 512 | SECTION("Calls bound function") { 513 | auto output = 2; 514 | sut(output); 515 | REQUIRE(output == 4); 516 | } 517 | SECTION("Has bound function") { 518 | REQUIRE(sut.has_target<&square_out>()); 519 | } 520 | } 521 | } 522 | 523 | //------------------------------------------------------------------------------ 524 | 525 | TEST_CASE("delegate::bind(T*)") { 526 | SECTION("Function being bound has same signature") { 527 | auto a = adder{42}; 528 | auto sut = delegate{}; 529 | sut.bind<&adder::set>(&a); 530 | 531 | SECTION("Calls bound function") { 532 | REQUIRE(sut(2) == 2); 533 | } 534 | SECTION("Has bound function") { 535 | REQUIRE(sut.has_target<&adder::set>(&a)); 536 | } 537 | } 538 | 539 | SECTION("Function being bound has similar signature") { 540 | auto a = adder{42}; 541 | auto sut = delegate{}; 542 | sut.bind<&adder::set>(&a); 543 | 544 | SECTION("Calls bound function") { 545 | REQUIRE(sut(2) == 2); 546 | } 547 | SECTION("Has bound function") { 548 | REQUIRE(sut.has_target<&adder::set>(&a)); 549 | } 550 | } 551 | 552 | SECTION("Delegate returns void") { 553 | auto a = adder{42}; 554 | auto sut = delegate{}; 555 | sut.bind<&adder::set>(&a); 556 | 557 | SECTION("Calls bound function") { 558 | const auto input = 2; 559 | sut(input); 560 | REQUIRE(a.x == input); 561 | } 562 | SECTION("Has bound function") { 563 | REQUIRE(sut.has_target<&adder::set>(&a)); 564 | } 565 | } 566 | } 567 | 568 | //------------------------------------------------------------------------------ 569 | 570 | TEST_CASE("delegate::bind(const T*)") { 571 | SECTION("Function being bound has same signature") { 572 | const auto x = 42; 573 | const auto a = adder{x}; 574 | auto sut = delegate{}; 575 | sut.bind<&adder::add>(&a); 576 | 577 | SECTION("Calls bound function") { 578 | REQUIRE(sut(2) == 44); 579 | } 580 | SECTION("Has bound function") { 581 | REQUIRE(sut.has_target<&adder::add>(&a)); 582 | } 583 | } 584 | 585 | SECTION("Function being bound has similar signature") { 586 | const auto x = 42; 587 | const auto a = adder{x}; 588 | auto sut = delegate{}; 589 | sut.bind<&adder::add>(&a); 590 | 591 | SECTION("Calls bound function") { 592 | REQUIRE(sut(2) == 44); 593 | } 594 | SECTION("Has bound function") { 595 | REQUIRE(sut.has_target<&adder::add>(&a)); 596 | } 597 | } 598 | 599 | SECTION("Delegate returns void") { 600 | const auto x = 42; 601 | const auto a = adder{x}; 602 | auto sut = delegate{}; 603 | sut.bind<&adder::out_add>(&a); 604 | 605 | SECTION("Calls bound function") { 606 | auto out = 0; 607 | sut(&out, 2); 608 | REQUIRE(out == 44); 609 | } 610 | SECTION("Has bound function") { 611 | REQUIRE(sut.has_target<&adder::out_add>(&a)); 612 | } 613 | } 614 | } 615 | 616 | //------------------------------------------------------------------------------ 617 | 618 | TEST_CASE("delegate::bind(Callable*)") { 619 | SECTION("Function being bound has same signature") { 620 | const auto x = 42; 621 | auto a = adder{x}; 622 | auto sut = delegate{}; 623 | sut.bind(&a); 624 | 625 | SECTION("Calls bound function") { 626 | REQUIRE(sut(2) == 44); 627 | } 628 | SECTION("Has bound function") { 629 | REQUIRE(sut.has_target(&a)); 630 | } 631 | } 632 | 633 | SECTION("Function being bound has similar signature") { 634 | const auto x = 42; 635 | auto a = adder{x}; 636 | auto sut = delegate{}; 637 | sut.bind(&a); 638 | 639 | SECTION("Calls bound function") { 640 | REQUIRE(sut(2) == 44); 641 | } 642 | SECTION("Has bound function") { 643 | REQUIRE(sut.has_target(&a)); 644 | } 645 | } 646 | 647 | SECTION("Delegate returns void") { 648 | auto a = loader{}; 649 | auto sut = delegate{}; 650 | sut.bind(&a); 651 | 652 | SECTION("Calls bound function") { 653 | auto out = 0; 654 | const auto expected = 2; 655 | 656 | sut(&out, expected); 657 | 658 | REQUIRE(out == expected); 659 | } 660 | SECTION("Has bound function") { 661 | REQUIRE(sut.has_target(&a)); 662 | } 663 | } 664 | } 665 | 666 | //------------------------------------------------------------------------------ 667 | 668 | TEST_CASE("delegate::bind(const Callable*)") { 669 | SECTION("Function being bound has same signature") { 670 | const auto x = 42; 671 | const auto a = adder{x}; 672 | auto sut = delegate{}; 673 | sut.bind(&a); 674 | 675 | SECTION("Calls bound function") { 676 | REQUIRE(sut(2) == 44); 677 | } 678 | SECTION("Has bound function") { 679 | REQUIRE(sut.has_target(&a)); 680 | } 681 | } 682 | 683 | SECTION("Function being bound has similar signature") { 684 | const auto x = 42; 685 | const auto a = adder{x}; 686 | auto sut = delegate{}; 687 | sut.bind(&a); 688 | 689 | SECTION("Calls bound function") { 690 | REQUIRE(sut(2) == 44); 691 | } 692 | SECTION("Has bound function") { 693 | REQUIRE(sut.has_target(&a)); 694 | } 695 | } 696 | 697 | SECTION("Delegate returns void") { 698 | const auto a = loader{}; 699 | auto sut = delegate{}; 700 | sut.bind(&a); 701 | 702 | SECTION("Calls bound function") { 703 | auto out = 0; 704 | const auto expected = 2; 705 | 706 | sut(&out, expected); 707 | 708 | REQUIRE(out == expected); 709 | } 710 | SECTION("Has bound function") { 711 | REQUIRE(sut.has_target(&a)); 712 | } 713 | } 714 | } 715 | 716 | //------------------------------------------------------------------------------ 717 | 718 | TEST_CASE("delegate::bind()") { 719 | SECTION("Target is bound statically") { 720 | SECTION("Function being bound has same signature") { 721 | auto sut = delegate{}; 722 | sut.bind>(); 723 | 724 | SECTION("Calls bound function") { 725 | REQUIRE(sut(42) == std::hash{}(42)); 726 | } 727 | SECTION("Has bound function") { 728 | REQUIRE(sut.has_target>()); 729 | } 730 | } 731 | 732 | SECTION("Function being bound has similar signature") { 733 | auto sut = delegate{}; 734 | sut.bind>(); 735 | 736 | SECTION("Calls bound function") { 737 | REQUIRE(sut(42) == static_cast(std::hash{}(42))); 738 | } 739 | SECTION("Has bound function") { 740 | REQUIRE(sut.has_target>()); 741 | } 742 | } 743 | 744 | SECTION("Delegate returns void") { 745 | auto sut = delegate{}; 746 | sut.bind(); 747 | 748 | SECTION("Calls bound function") { 749 | auto out = 0; 750 | sut(&out, 2); 751 | 752 | REQUIRE(out == 4); 753 | } 754 | SECTION("Has bound function") { 755 | REQUIRE(sut.has_target()); 756 | } 757 | } 758 | } 759 | 760 | SECTION("Target is bound at runtime") { 761 | SECTION("Function being bound has same signature") { 762 | auto sut = delegate{}; 763 | sut.bind(std::hash{}); 764 | 765 | SECTION("Calls bound function") { 766 | REQUIRE(sut(42) == std::hash{}(42)); 767 | } 768 | SECTION("Has bound function") { 769 | REQUIRE(sut.has_target(std::hash{})); 770 | } 771 | } 772 | 773 | SECTION("Function being bound has similar signature") { 774 | auto sut = delegate{}; 775 | sut.bind(std::hash{}); 776 | 777 | SECTION("Calls bound function") { 778 | REQUIRE(sut(42) == static_cast(std::hash{}(42))); 779 | } 780 | SECTION("Has bound function") { 781 | REQUIRE(sut.has_target(std::hash{})); 782 | } 783 | } 784 | 785 | SECTION("Delegate returns void") { 786 | auto sut = delegate{}; 787 | sut.bind(square_out_functor{}); 788 | 789 | SECTION("Calls bound function") { 790 | auto out = 0; 791 | sut(&out, 2); 792 | 793 | REQUIRE(out == 4); 794 | } 795 | SECTION("Has bound function") { 796 | REQUIRE(sut.has_target(square_out_functor{})); 797 | } 798 | } 799 | } 800 | } 801 | 802 | //------------------------------------------------------------------------------ 803 | 804 | TEST_CASE("delegate::bind(Fn&&)") { 805 | SECTION("Function being bound has same signature") { 806 | int x = 42; 807 | const auto target = [x](int y){ 808 | return x + y; 809 | }; 810 | auto sut = delegate{}; 811 | sut.bind(target); 812 | 813 | SECTION("Calls bound function") { 814 | REQUIRE(sut(10) == 52); 815 | } 816 | SECTION("Has bound function") { 817 | REQUIRE(sut.has_target(target)); 818 | } 819 | } 820 | 821 | SECTION("Function being bound has similar signature") { 822 | int x = 42; 823 | const auto target = [x](int y) { 824 | return x + y; 825 | }; 826 | 827 | auto sut = delegate{}; 828 | sut.bind(target); 829 | 830 | SECTION("Calls bound function") { 831 | REQUIRE(sut(10) == 52); 832 | } 833 | SECTION("Has bound function") { 834 | REQUIRE(sut.has_target(target)); 835 | } 836 | } 837 | 838 | SECTION("Delegate returns void") { 839 | int x = 42; 840 | const auto target = [x](int* out, int y) -> int{ 841 | return (*out = (x + y)); 842 | }; 843 | 844 | auto sut = delegate{}; 845 | sut.bind(target); 846 | 847 | SECTION("Calls bound function") { 848 | auto out = 0; 849 | sut(&out, 10); 850 | 851 | REQUIRE(out == 52); 852 | } 853 | SECTION("Has bound function") { 854 | REQUIRE(sut.has_target(target)); 855 | } 856 | } 857 | } 858 | 859 | TEST_CASE("delegate::bind(UR(*)(UArgs...))") { 860 | SECTION("Function being bound has same signature") { 861 | auto sut = delegate{}; 862 | sut.bind(&square); 863 | 864 | SECTION("Calls bound function") { 865 | REQUIRE(sut(2) == 4); 866 | } 867 | SECTION("Has bound function") { 868 | REQUIRE(sut.has_target(&square)); 869 | } 870 | } 871 | 872 | SECTION("Function being bound has similar signature") { 873 | auto sut = delegate{}; 874 | sut.bind(&square); 875 | 876 | SECTION("Calls bound function") { 877 | REQUIRE(sut(2) == 4); 878 | } 879 | SECTION("Has bound function") { 880 | REQUIRE(sut.has_target(&square)); 881 | } 882 | } 883 | 884 | SECTION("Delegate returns void") { 885 | auto sut = delegate{}; 886 | sut.bind(&square_out); 887 | 888 | SECTION("Calls bound function") { 889 | auto output = 2; 890 | sut(output); 891 | REQUIRE(output == 4); 892 | } 893 | SECTION("Has bound function") { 894 | REQUIRE(sut.has_target(&square_out)); 895 | } 896 | } 897 | } 898 | 899 | //------------------------------------------------------------------------------ 900 | // Modifiers 901 | //------------------------------------------------------------------------------ 902 | 903 | TEST_CASE("delegate::reset()") { 904 | SECTION("Delegate has a value") { 905 | const auto to_bind = []{}; 906 | delegate sut = bind(&to_bind); 907 | const auto state_before = static_cast(sut); 908 | 909 | sut.reset(); 910 | 911 | SECTION("State changes") { 912 | const auto state_after = static_cast(sut); 913 | 914 | REQUIRE(state_before != state_after); 915 | } 916 | SECTION("Delegate no longer contains state") { 917 | REQUIRE(!static_cast(sut)); 918 | } 919 | SECTION("Invoking delegate throws bad_delegate_call") { 920 | REQUIRE_THROWS_AS(sut(), bad_delegate_call); 921 | } 922 | } 923 | 924 | SECTION("Delegate does not have a value") { 925 | auto sut = delegate{}; 926 | const auto state_before = static_cast(sut); 927 | 928 | sut.reset(); 929 | 930 | SECTION("Delegate does not contain state") { 931 | REQUIRE(!static_cast(sut)); 932 | } 933 | SECTION("State remains unchanged") { 934 | const auto state_after = static_cast(sut); 935 | 936 | REQUIRE(state_before == state_after); 937 | } 938 | SECTION("Invoking delegate throws bad_delegate_call") { 939 | REQUIRE_THROWS_AS(sut(), bad_delegate_call); 940 | } 941 | } 942 | } 943 | 944 | //------------------------------------------------------------------------------ 945 | // Observers 946 | //------------------------------------------------------------------------------ 947 | 948 | TEST_CASE("delegate::operator bool") { 949 | SECTION("Delegate is unbound") { 950 | SECTION("Convertible to false") { 951 | auto sut = delegate{}; 952 | 953 | REQUIRE_FALSE(static_cast(sut)); 954 | } 955 | } 956 | SECTION("Delegate is bound") { 957 | SECTION("Convertible to true") { 958 | delegate sut = bind>(); 959 | 960 | REQUIRE(static_cast(sut)); 961 | } 962 | } 963 | } 964 | 965 | TEST_CASE("delegate::operator(Args...)") { 966 | SECTION("Delegate is unbound") { 967 | const auto sut = delegate{}; 968 | 969 | SECTION("Throws bad_delegate_call exception") { 970 | REQUIRE_THROWS_AS(sut(), bad_delegate_call); 971 | } 972 | } 973 | 974 | SECTION("Delegate is bound") { 975 | const auto to_bind = [](auto ptr) { 976 | return ptr != nullptr; 977 | }; 978 | 979 | delegate)> sut = bind(&to_bind); 980 | 981 | SECTION("Delegate calls bound function with prvalue") { 982 | REQUIRE(sut(std::make_unique(5))); 983 | } 984 | 985 | SECTION("Delegate calls bound function with rvlaue") { 986 | auto input = std::make_unique(5); 987 | REQUIRE(sut(std::move(input))); 988 | } 989 | 990 | } 991 | } 992 | 993 | //------------------------------------------------------------------------------ 994 | 995 | TEST_CASE("delegate::has_target()") { 996 | SECTION("Delegate is unbound") { 997 | SECTION("has_target returns false") { 998 | auto sut = delegate{}; 999 | 1000 | REQUIRE_FALSE(sut.has_target()); 1001 | } 1002 | } 1003 | SECTION("Delegate is bound") { 1004 | SECTION("has_target returns true") { 1005 | delegate sut = bind>(); 1006 | 1007 | REQUIRE(sut.has_target()); 1008 | } 1009 | } 1010 | } 1011 | 1012 | TEST_CASE("delegate::has_target()") { 1013 | auto sut = delegate{bind<&square>()}; 1014 | 1015 | SECTION("Delegate has bound target") { 1016 | SECTION("Returns true") { 1017 | REQUIRE(sut.has_target<&square>()); 1018 | } 1019 | } 1020 | SECTION("Delegate has late-bound function of target") { 1021 | SECTION("Returns false") { 1022 | REQUIRE_FALSE(sut.has_target(&square)); 1023 | } 1024 | } 1025 | } 1026 | 1027 | } // namespace test 1028 | } // inline namespace bitwizeshift 1029 | } // namespace DELEGATE_NAMESPACE_INTERNAL 1030 | 1031 | #if defined(_MSC_VER) 1032 | # pragma warning(pop) 1033 | #endif 1034 | -------------------------------------------------------------------------------- /include/delegate.hpp: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * \file delegate.hpp 3 | * 4 | * \brief This header contains the definition of a light-weight utility for 5 | * type-erasing bound functions at compile-time. 6 | *****************************************************************************/ 7 | 8 | /* 9 | Copyright (c) 2017, 2018, 2020-2021 Matthew Rodusek 10 | Distributed under the Boost Software License, Version 1.0. 11 | (See accompanying file LICENSE_1_0.txt or copy at 12 | https://www.boost.org/LICENSE_1_0.txt) 13 | */ 14 | #ifndef DELEGATE_DELEGATE_HPP 15 | #define DELEGATE_DELEGATE_HPP 16 | 17 | #if defined(_MSC_VER) 18 | # pragma once 19 | #endif // defined(_MSC_VER) 20 | 21 | #include // std::enable_if, std::is_constructible, etc 22 | #include // std::invoke 23 | #include // placement new, std::launder 24 | #include // assert 25 | #include // std::forward 26 | #include // std::memcmp 27 | #include // std::runtime_error 28 | 29 | #if defined(__clang__) || defined(__GNUC__) 30 | # define DELEGATE_INLINE_VISIBILITY __attribute__((visibility("hidden"), always_inline)) 31 | #elif defined(_MSC_VER) 32 | # define DELEGATE_INLINE_VISIBILITY __forceinline 33 | #else 34 | # define DELEGATE_INLINE_VISIBILITY 35 | #endif 36 | 37 | #if defined(DELEGATE_NAMESPACE) 38 | # define DELEGATE_NAMESPACE_INTERNAL DELEGATE_NAMESPACE 39 | #else 40 | # define DELEGATE_NAMESPACE_INTERNAL cpp 41 | #endif 42 | #define DELEGATE_NS_IMPL DELEGATE_NAMESPACE_INTERNAL::bitwizeshift 43 | 44 | #if defined(NDEBUG) 45 | # define DELEGATE_ASSERT(x) ((void)0) 46 | #else 47 | # define DELEGATE_ASSERT(x) (x ? ((void)0) : [&]{ assert(x); }()) 48 | #endif 49 | 50 | #if defined(_MSC_VER) 51 | # pragma warning(push) 52 | // MSVC complains about a [[noreturn]] function returning non-void, but this 53 | // is needed to satisfy the stub interface 54 | # pragma warning(disable:4646) 55 | // MSVC warns that `alignas` will cause padding. Like alignment does. 56 | # pragma warning(disable:4324) 57 | #endif 58 | 59 | namespace DELEGATE_NAMESPACE_INTERNAL { 60 | inline namespace bitwizeshift { 61 | namespace detail { 62 | template 63 | struct is_equality_comparable : std::false_type{}; 64 | 65 | template 66 | struct is_equality_comparable()==std::declval())>> 67 | : std::true_type{}; 68 | 69 | //---------------------------------------------------------------------------- 70 | 71 | template 72 | struct effective_signature_impl; 73 | 74 | //---------------------------------------------------------------------------- 75 | 76 | template 77 | struct effective_signature_impl { 78 | using type = R(Args...); 79 | }; 80 | 81 | template 82 | struct effective_signature_impl { 83 | using type = R(Args...,...); 84 | }; 85 | 86 | template 87 | struct effective_signature_impl { 88 | using type = R(Args...); 89 | }; 90 | 91 | template 92 | struct effective_signature_impl { 93 | using type = R(Args...,...); 94 | }; 95 | 96 | //---------------------------------------------------------------------------- 97 | 98 | template 99 | struct effective_signature_impl { 100 | using type = R(Args...); 101 | }; 102 | 103 | template 104 | struct effective_signature_impl { 105 | using type = R(Args...,...); 106 | }; 107 | 108 | template 109 | struct effective_signature_impl { 110 | using type = R(Args...); 111 | }; 112 | 113 | template 114 | struct effective_signature_impl { 115 | using type = R(Args...,...); 116 | }; 117 | 118 | //---------------------------------------------------------------------------- 119 | 120 | template 121 | struct effective_signature_impl { 122 | using type = R(Args...); 123 | }; 124 | 125 | template 126 | struct effective_signature_impl { 127 | using type = R(Args...,...); 128 | }; 129 | 130 | template 131 | struct effective_signature_impl { 132 | using type = R(Args...); 133 | }; 134 | 135 | template 136 | struct effective_signature_impl { 137 | using type = R(Args...,...); 138 | }; 139 | 140 | //---------------------------------------------------------------------------- 141 | 142 | template 143 | using effective_signature = typename effective_signature_impl::type; 144 | 145 | } // namespace detail 146 | 147 | //============================================================================== 148 | // class : bad_delegate_call 149 | //============================================================================== 150 | 151 | //////////////////////////////////////////////////////////////////////////////// 152 | /// \brief An exception thrown when a `delegate` is invoked without a bound 153 | /// function 154 | //////////////////////////////////////////////////////////////////////////////// 155 | class bad_delegate_call : public std::runtime_error 156 | { 157 | public: 158 | bad_delegate_call(); 159 | }; 160 | 161 | //============================================================================== 162 | // Binding 163 | //============================================================================== 164 | 165 | inline namespace targets { 166 | 167 | template 168 | struct function_bind_target{}; 169 | 170 | template 171 | struct member_bind_target{ T* instance; }; 172 | 173 | template 174 | struct opaque_function_bind_target; 175 | 176 | template 177 | struct opaque_function_bind_target{ R(*target)(Args...); }; 178 | 179 | template 180 | struct callable_ref_bind_target{ Callable* target; }; 181 | 182 | template 183 | struct empty_callable_bind_target{}; 184 | 185 | template 186 | struct callable_bind_target{ Callable target; }; 187 | 188 | } // inline namespace targets 189 | 190 | /// \brief Binds a function pointer to create a function bind target 191 | /// 192 | /// \tparam Function the funtion to bind 193 | /// \return the created target 194 | template 195 | constexpr auto bind() noexcept -> function_bind_target; 196 | 197 | /// \brief Binds a member pointer to create a function bind target 198 | /// 199 | /// \pre `p != nullptr` 200 | /// 201 | /// \tparam MemberFunction the member functon 202 | /// \param p the instance pointer 203 | /// \return the created target 204 | template 205 | constexpr auto bind(T* p) noexcept -> member_bind_target; 206 | 207 | /// \brief Binds a pointer to a callable function as a bind target 208 | /// 209 | /// \pre `fn != nullptr` 210 | /// 211 | /// \param fn the callable object to bind 212 | /// \return the created target 213 | template 214 | constexpr auto bind(Callable* fn) noexcept -> callable_ref_bind_target; 215 | 216 | /// \brief Binds an opaque function pointer to create a function bind target 217 | /// 218 | /// \param fn the opaque function to pointer to bind 219 | /// \return the created target 220 | template 221 | constexpr auto bind(R(*fn)(Args...)) noexcept -> opaque_function_bind_target; 222 | 223 | /// \brief Binds an empty, default-constructible `Callable` object as a bind 224 | /// target 225 | /// 226 | /// \tparam Callable the target to bind 227 | /// \return the created target 228 | template 229 | constexpr auto bind() noexcept -> empty_callable_bind_target; 230 | 231 | /// \brief Binds an empty, default-constructible `Callable` object as a bind 232 | /// target 233 | /// 234 | /// \param callable an instance of the callable to bind 235 | /// \return the created target 236 | template && 239 | std::is_default_constructible_v 240 | )>> 241 | constexpr auto bind(Callable callable) noexcept -> empty_callable_bind_target; 242 | 243 | /// \brief Binds a trivially-copyable callable object as a bind target 244 | /// 245 | /// \param callable an instance of the callable to bind 246 | /// \return the created target 247 | template > && 250 | std::is_trivially_copyable_v> && 251 | std::is_trivially_destructible_v> 252 | )>> 253 | constexpr auto bind(Callable&& callable) noexcept -> callable_bind_target>; 254 | 255 | //============================================================================== 256 | // class : delegate 257 | //============================================================================== 258 | 259 | template 260 | class delegate; 261 | 262 | //////////////////////////////////////////////////////////////////////////////// 263 | /// \brief A lightweight 0-overhead wrapper for binding function objects. 264 | /// 265 | /// `delegate` objects have more guaranteed performance characteristics over the 266 | /// likes of `std::function`. Most functions will be statically bound using 267 | /// C++17's auto template parameters, which provides more hints ot the compiler 268 | /// while also ensuring that function pointers will optimize almost directly 269 | /// into a singular function call without indirection. 270 | /// 271 | /// This type is small -- weighing in at only 2 function pointers in size -- and 272 | /// trivial to copy. This allows for `delegate` objects to easily be passed 273 | /// into functions by registers. Additionally this ensures that any stored 274 | /// callable objects are always small and immediately close to the data. 275 | /// 276 | /// \warning 277 | /// Due to its small size, most stored callable objects for a `delegate` will 278 | /// simply *view* the lifetime of the captured object. As a result, care needs 279 | /// to be taken to ensure that any captured lifetime does no expire while it is 280 | /// in use by a delegate. Any operations that view lifetime can easily be seen 281 | /// as they only operate on pointers. 282 | /// 283 | /// See the `bind` series of free functions for more details. 284 | /// 285 | /// ### Examples 286 | /// 287 | /// ```cpp 288 | /// delegate d = bind<&std::strlen>(); 289 | /// 290 | /// assert(d("hello world") == 11); 291 | /// ``` 292 | /// 293 | /// \tparam R the return type 294 | /// \tparam Args the arguments to the function 295 | //////////////////////////////////////////////////////////////////////////////// 296 | template 297 | class delegate 298 | { 299 | // Make the storage size hold at least a function pointer or a void pointer, 300 | // whichever is larger. 301 | static constexpr auto storage_size = ( 302 | (sizeof(void*) < sizeof(void(*)())) 303 | ? sizeof(void(*)()) 304 | : sizeof(void*) 305 | ); 306 | 307 | static constexpr auto storage_align = ( 308 | alignof(void*) 309 | ); 310 | 311 | template 312 | using fits_storage = std::bool_constant<( 313 | (sizeof(U) <= storage_size) && 314 | (alignof(U) <= storage_align) 315 | )>; 316 | 317 | //---------------------------------------------------------------------------- 318 | // Constructors / Assignment 319 | //---------------------------------------------------------------------------- 320 | public: 321 | 322 | /// \brief Default constructs this delegate without a bound target 323 | constexpr delegate() noexcept; 324 | 325 | /// \brief Constructs this delegate bound to the specified `Function` 326 | /// 327 | /// \note 328 | /// The `Function` argument needs to be some callable object type, such as a 329 | /// function pointer, or a C++20 constexpr functor. 330 | /// 331 | /// ### Examples 332 | /// 333 | /// Basic Use: 334 | /// 335 | /// ```cpp 336 | /// delegate d1 = bind<&std::strlen>(); 337 | /// ``` 338 | /// 339 | /// \param target the target to bind 340 | template 343 | )>> 344 | constexpr delegate(function_bind_target target) noexcept; 345 | 346 | /// \{ 347 | /// \brief Makes a delegate object that binds the `MemberFunction` in the 348 | /// target 349 | /// 350 | /// \note 351 | /// Technically the `MemberFunction` needs to only be some callable object 352 | /// that accepts the target instance as the first argument. This could be a 353 | /// regular function pointer, a member function, or some C++20 constexpr 354 | /// functor 355 | /// 356 | /// \pre `target.target != nullptr`. You cannot bind a null instance to a 357 | /// member function. 358 | /// 359 | /// \warning 360 | /// A `delegate` constructed this way only *views* the lifetime of 361 | /// \p instance; it does not capture or contribute to its lifetime. Care must 362 | /// be taken to ensure that the constructed `delegate` does not outlive the 363 | /// bound lifetime -- or at least is not called after outliving it, otherwise 364 | /// this will be undefined behavior. 365 | /// 366 | /// ### Examples 367 | /// 368 | /// Basic Use: 369 | /// 370 | /// ```cpp 371 | /// std::string s; 372 | /// delegate d = bind<&std::string::size>(&s); 373 | /// ``` 374 | /// 375 | /// \param target the target to bind 376 | template 379 | )>> 380 | constexpr delegate(member_bind_target target) noexcept; 381 | template 384 | )>> 385 | constexpr delegate(member_bind_target target) noexcept; 386 | /// \} 387 | 388 | /// \{ 389 | /// \brief Constructs this delegate by binding an instance of some callable 390 | /// function 391 | /// 392 | /// \note 393 | /// The bound function may be any invocable object, such as a functor or a 394 | /// lambda. 395 | /// 396 | /// \pre `target.target != nullptr`. You cannot bind a null function 397 | /// 398 | /// \warning 399 | /// A `delegate` constructed this way only *views* the lifetime of 400 | /// the callable object; it does not capture or contribute to its lifetime. 401 | /// Care must be taken to ensure that the constructed `delegate` does not 402 | /// outlive the bound lifetime -- or at least is not called after outliving 403 | /// it, otherwise this will be undefined behavior. 404 | /// 405 | /// ### Examples 406 | /// 407 | /// Basic Use: 408 | /// 409 | /// ```cpp 410 | /// auto object = std::hash{}; 411 | /// delegate d = bind(&object); 412 | /// ``` 413 | /// 414 | /// \param target the target to bind 415 | template && 418 | !std::is_function_v 419 | )>> 420 | constexpr delegate(callable_ref_bind_target target) noexcept; 421 | template && 424 | !std::is_function_v 425 | )>> 426 | constexpr delegate(callable_ref_bind_target target) noexcept; 427 | /// \} 428 | 429 | /// \brief Constructs this delegate from some empty, default-constructible 430 | /// callable object 431 | /// 432 | /// \note 433 | /// Unlike the lifetime-viewing `make` functions, the acllable object is not 434 | /// referenced during capture; it is ephemerally constructed each time it is 435 | /// invoked. 436 | /// 437 | /// ### Examples 438 | /// 439 | /// Basic Use: 440 | /// 441 | /// ```cpp 442 | /// delegate d = bind>(); 443 | /// ``` 444 | /// 445 | /// Automatic empty detection: 446 | /// 447 | /// ```cpp 448 | /// delegate d = bind(std::hash{}); 449 | /// ``` 450 | /// 451 | /// \param target the target to bind 452 | template && 455 | std::is_default_constructible_v && 456 | std::is_invocable_r_v 457 | )>> 458 | constexpr delegate(empty_callable_bind_target target) noexcept; 459 | 460 | /// \brief Constructs a delegate object by binding a small-sized trivially 461 | /// copyable callable object 462 | /// 463 | /// \note 464 | /// This overload stores the small-sized callable object internally, allowing 465 | /// for the lifetime to effectively be captured. 466 | /// 467 | /// \param target the target to bind 468 | template > && 471 | !std::is_function_v> && 472 | fits_storage>::value && 473 | std::is_constructible_v,Fn> && 474 | std::is_trivially_destructible_v> && 475 | std::is_trivially_copyable_v> && 476 | std::is_invocable_r_v 477 | )>> 478 | delegate(callable_bind_target target) noexcept; 479 | 480 | /// \brief Constructs this delegate by binding an opaque function pointer 481 | /// 482 | /// \note 483 | /// This overload allows for binding opaque function pointers, such as the 484 | /// result of a `::dlsym` call. 485 | /// 486 | /// \pre `target.target != nullptr` 487 | /// 488 | /// \param target the target to bind 489 | template 492 | )>> 493 | delegate(opaque_function_bind_target target) noexcept; 494 | 495 | /// \brief Trivially copies the contents of \p other 496 | /// 497 | /// \param other the other delegate to copy 498 | delegate(const delegate& other) = default; 499 | 500 | //---------------------------------------------------------------------------- 501 | 502 | /// \brief Trivially assigns the contents from \p other 503 | /// 504 | /// \param other the other delegate to copy 505 | /// \return reference to `(*this)` 506 | auto operator=(const delegate& other) -> delegate& = default; 507 | 508 | //---------------------------------------------------------------------------- 509 | // Binding 510 | //---------------------------------------------------------------------------- 511 | public: 512 | 513 | /// \brief Binds the specified \p Function to this `delegate` 514 | /// 515 | /// \note 516 | /// The \p Function argument needs to be some callable object type, such as a 517 | /// function pointer, or a C++20 constexpr functor. 518 | /// 519 | /// \tparam Function the function argument 520 | /// \return reference to `(*this)` 521 | template 524 | )>> 525 | constexpr auto bind() noexcept -> delegate&; 526 | 527 | /// \{ 528 | /// \brief Binds the \p MemberFunction with the specified \p instance 529 | /// 530 | /// \note 531 | /// Technically the \p MemberFunction needs to only be some callable object 532 | /// that accepts \p instance as the first argument. This could be a regular 533 | /// function pointer, a member function, or some C++20 constexpr functor 534 | /// 535 | /// \pre `instance != nullptr`. You cannot bind a null instance to a member 536 | /// function. 537 | /// 538 | /// \warning 539 | /// A `delegate` constructed this way only *views* the lifetime of 540 | /// \p instance; it does not capture or contribute to its lifetime. Care must 541 | /// be taken to ensure that the constructed `delegate` does not outlive the 542 | /// bound lifetime -- or at least is not called after outliving it, otherwise 543 | /// this will be undefined behavior. 544 | /// 545 | /// \tparam MemberFunction The MemberFunction pointer to invoke 546 | /// \param instance an instance pointer 547 | /// \return reference to `(*this)` 548 | template 551 | )>> 552 | constexpr auto bind(const T* instance) noexcept -> delegate&; 553 | template 556 | )>> 557 | constexpr auto bind(T* instance) noexcept -> delegate&; 558 | /// \} 559 | 560 | /// \{ 561 | /// \brief Binds an instance of some callable function \p fn 562 | /// 563 | /// \note 564 | /// \p fn may be any invocable object, such as a functor or a lambda. 565 | /// 566 | /// \pre `fn != nullptr`. You cannot bind a null function 567 | /// 568 | /// \warning 569 | /// A `delegate` constructed this way only *views* the lifetime of 570 | /// \p fn; it does not capture or contribute to its lifetime. Care must 571 | /// be taken to ensure that the constructed `delegate` does not outlive the 572 | /// bound lifetime -- or at least is not called after outliving it, otherwise 573 | /// this will be undefined behavior. 574 | /// 575 | /// \param fn A pointer to the callable object to view 576 | /// \return reference to `(*this)` 577 | template && 580 | !std::is_function_v 581 | )>> 582 | constexpr auto bind(Fn* fn) noexcept -> delegate&; 583 | template && 586 | !std::is_function_v 587 | )>> 588 | constexpr auto bind(const Fn* fn) noexcept -> delegate&; 589 | /// \} 590 | 591 | /// \brief Constructs a delegate object from some empty, default-constructible 592 | /// callable object 593 | /// 594 | /// \note 595 | /// This overload only works with the type itself, such as an empty functor or 596 | /// `std::hash`-like object 597 | /// 598 | /// Unlike the lifetime-viewing `make` functions,the `Fn` is not referenced 599 | /// during capture 600 | /// 601 | /// \tparam Fn the function type to ephemerally instantiate on invocations 602 | /// \return the constructed delegate 603 | template && 606 | std::is_default_constructible_v && 607 | std::is_invocable_r_v 608 | )>> 609 | constexpr auto bind() noexcept -> delegate&; 610 | 611 | /// \brief Binds a default-constructible callable object 612 | /// 613 | /// \note 614 | /// This overload allows for C++20 non-capturing lambdas to be easily captured 615 | /// without requiring any extra storage overhead. This also allows for any 616 | /// functor without storage space, such as `std::hash`, to be ephemerally 617 | /// created during invocation rather than stored 618 | /// 619 | /// Unlike the lifetime-viewing `make` functions, \p fn is passed by-value 620 | /// here. 621 | /// 622 | /// \param fn the function to store 623 | /// \return reference to `(*this)` 624 | template && 627 | std::is_default_constructible_v && 628 | std::is_invocable_r_v 629 | )>> 630 | constexpr auto bind(Fn fn) noexcept -> delegate&; 631 | 632 | /// \brief Binds a small-sized trivially copyable callable object 633 | /// 634 | /// \note 635 | /// This overload stores the small-sized callable object internally, allowing 636 | /// for the lifetime to effectively be captured. 637 | /// 638 | /// \param fn the function to store 639 | /// \return reference to `(*this)` 640 | template > && 643 | !std::is_function_v> && 644 | fits_storage>::value && 645 | std::is_constructible_v,Fn> && 646 | std::is_trivially_destructible_v> && 647 | std::is_trivially_copyable_v> && 648 | std::is_invocable_r_v 649 | )>> 650 | constexpr auto bind(Fn&& fn) noexcept -> delegate&; 651 | 652 | /// \brief Binds a non-member function pointer to this `delegate` 653 | /// 654 | /// \note 655 | /// This overload allows for binding opaque function pointers, such as the 656 | /// result of a `::dlsym` call. 657 | /// 658 | /// \pre `fn != nullptr` 659 | /// 660 | /// \param fn the function to store 661 | /// \return reference to `(*this)` 662 | template 665 | )>> 666 | constexpr auto bind(R2(*fn)(Args2...)) noexcept -> delegate&; 667 | 668 | //---------------------------------------------------------------------------- 669 | 670 | /// \brief Resets this delegate so that it is no longer bounds 671 | /// 672 | /// \post `(*this).has_target()` will return `false` 673 | constexpr auto reset() noexcept -> void; 674 | 675 | //---------------------------------------------------------------------------- 676 | // Observers 677 | //---------------------------------------------------------------------------- 678 | public: 679 | 680 | /// \brief Contextually convertible to `true` if this `delegate` is already 681 | /// bound 682 | /// 683 | /// This is equivalent to call `has_target()` 684 | constexpr explicit operator bool() const noexcept; 685 | 686 | #if defined(__clang__) 687 | # pragma clang diagnostic push 688 | // clang incorrectly flags '\return' when 'R = void' as being an error, but 689 | // there is no way to conditionally document a return type 690 | # pragma clang diagnostic ignored "-Wdocumentation" 691 | #endif 692 | 693 | /// \brief Invokes the underlying bound function 694 | /// 695 | /// \param args the arguments to forward to the function 696 | /// \return the result of the bound function 697 | template >> 699 | constexpr auto operator()(UArgs&&...args) const -> R; 700 | 701 | #if defined(__clang__) 702 | # pragma clang diagnostic pop 703 | #endif 704 | 705 | //---------------------------------------------------------------------------- 706 | 707 | /// \brief Queries whether this `delegate` has any function bound to it 708 | /// 709 | /// \return `true` if a delegate is bound 710 | [[nodiscard]] 711 | constexpr auto has_target() const noexcept -> bool; 712 | 713 | /// \brief Queries whether this `delegate` has a `Function` object bound to it 714 | /// 715 | /// \tparam Function the function to query 716 | /// \return `true` if the delegate is bound with the specified `Function` 717 | template >> 719 | [[nodiscard]] 720 | constexpr auto has_target() const noexcept -> bool; 721 | 722 | /// \brief Queries whether this `delegate` has a `MemberFunction` object bound 723 | /// to it with the specified \p instance 724 | /// 725 | /// \tparam MemberFunction the function to query 726 | /// \param instance the instance to query aganst 727 | /// \return `true` if the delegate is bound with the specified `MemberFunction` 728 | template >> 730 | [[nodiscard]] 731 | constexpr auto has_target(const T* instance) const noexcept -> bool; 732 | 733 | /// \brief Queries whether this `delegate` has a `MemberFunction` object bound 734 | /// to it with the specified \p instance 735 | /// 736 | /// \tparam MemberFunction the function to query 737 | /// \param instance the instance to query aganst 738 | /// \return `true` if the delegate is bound with the specified `MemberFunction` 739 | template >> 741 | [[nodiscard]] 742 | constexpr auto has_target(T* instance) const noexcept -> bool; 743 | 744 | /// \{ 745 | /// \brief Queries whether this `delegate` is bound viewing the specified 746 | /// callable \p fn 747 | /// 748 | /// \param fn the function to query 749 | /// \return `true` if the delegate is bound with `fn` 750 | template && 753 | !std::is_function_v 754 | )>> 755 | [[nodiscard]] 756 | constexpr auto has_target(Fn* fn) const noexcept -> bool; 757 | template && 760 | !std::is_function_v 761 | )>> 762 | [[nodiscard]] 763 | constexpr auto has_target(const Fn* fn) const noexcept -> bool; 764 | /// \} 765 | 766 | /// \brief Queries whether this `delegate` is bound with the specified 767 | /// empty callable object Fn 768 | /// 769 | /// \tparam Fn the type to query 770 | /// \return `true` if the delegate is bound with `fn` 771 | template && 774 | std::is_default_constructible_v && 775 | std::is_invocable_r_v 776 | )>> 777 | [[nodiscard]] 778 | constexpr auto has_target() const noexcept -> bool; 779 | 780 | /// \brief Queries whether this `delegate` is bound with the specified 781 | /// empty function \p fn 782 | /// 783 | /// \param fn the function to query 784 | /// \return `true` if the delegate is bound with `fn` 785 | template && 788 | std::is_default_constructible_v && 789 | std::is_invocable_r_v 790 | )>> 791 | [[nodiscard]] 792 | constexpr auto has_target(Fn fn) const noexcept -> bool; 793 | 794 | /// \brief Queries whether this `delegate` is bound with the specified 795 | /// trivial non-empty function \p fn 796 | /// 797 | /// \param fn the function to query 798 | /// \return `true` if the delegate is bound with `fn` 799 | template && 802 | !std::is_function_v> && 803 | fits_storage::value && 804 | std::is_constructible_v && 805 | std::is_trivially_destructible_v && 806 | std::is_trivially_copyable_v && 807 | std::is_invocable_r_v 808 | )>> 809 | [[nodiscard]] 810 | auto has_target(const Fn& fn) const noexcept -> bool; 811 | 812 | /// \brief Queries this delegate has been bound with the late-bound function 813 | /// pointer \p fn 814 | /// 815 | /// \note 816 | /// This will only compare for functions that were bound using the 817 | /// `bind` or `make` overload for runtime function-pointer values; otherwise 818 | /// this will return `false`. 819 | /// 820 | /// \pre `fn != nullptr` 821 | /// 822 | /// \param fn the function to query 823 | /// \return `true` if this delegate is bound to the runtime function \p fn 824 | template 827 | )>> 828 | [[nodiscard]] 829 | constexpr auto has_target(R2(*fn)(Args2...)) const noexcept -> bool; 830 | 831 | //---------------------------------------------------------------------------- 832 | // Private Member Types 833 | //---------------------------------------------------------------------------- 834 | private: 835 | 836 | // [expr.reinterpret.cast/6] explicitly allows for a conversion between 837 | // any two function pointer-types -- provided that the function pointer type 838 | // is not used through the wrong pointer type. 839 | // So we normalize all pointers to a simple `void(*)()` to allow late-bound 840 | // pointers 841 | using any_function = void(*)(); 842 | 843 | using stub_function = R(*)(const delegate*, Args...); 844 | 845 | //---------------------------------------------------------------------------- 846 | // Stub Functions 847 | //---------------------------------------------------------------------------- 848 | private: 849 | 850 | #if defined(__clang__) 851 | # pragma clang diagnostic push 852 | // clang incorrectly flags '\return' when 'R = void' as being an error, but 853 | // there is no way to conditionally document a return type 854 | # pragma clang diagnostic ignored "-Wdocumentation" 855 | #endif 856 | 857 | /// \brief Throws an exception by default 858 | [[noreturn]] 859 | static auto null_stub(const delegate*, Args...) -> R; 860 | 861 | /// \brief A stub function for statically-specific functions (or other 862 | /// callables) 863 | /// 864 | /// \tparam Function the statically-specific functions 865 | /// \param args the arguments to forward to the function 866 | /// \return the result of the Function call 867 | template 868 | static auto function_stub(const delegate*, Args...args) -> R; 869 | 870 | /// \brief A stub function for statically-specific member functions (or other 871 | /// callables) 872 | /// 873 | /// \tparam MemberFunction the statically-specific member function 874 | /// \tparam T the type of the first pointer 875 | /// \param self an instance to the delegate that contains the instance pointer 876 | /// \param args the arguments to forward to the function 877 | /// \return the result of the MemberFunction call 878 | template 879 | static auto member_function_stub(const delegate* self, Args...args) -> R; 880 | 881 | /// \brief A stub function for non-owning view of callable objects 882 | /// 883 | /// \tparam Fn the function to reference 884 | /// \param self an instance to the delegate that contains \p fn 885 | /// \param args the arguments to forward to the function 886 | /// \return the result of invoking \p fn 887 | template 888 | static auto callable_view_stub(const delegate* self, Args...args) -> R; 889 | 890 | /// \brief A stub function empty callable objects 891 | /// 892 | /// \tparam Fn the empty function to invoke 893 | /// \param args the arguments to forward to the function 894 | /// \return the result of invoking \p fn 895 | template 896 | static auto empty_callable_stub(const delegate*, Args...args) -> R; 897 | 898 | /// \brief A stub function for small callable objects 899 | /// 900 | /// \tparam Fn the small-storage function to invoke 901 | /// \param self an instance to the delegate that contains the function 902 | /// \param args the arguments to forward to the function 903 | /// \return the result of invoking \p fn 904 | template 905 | static auto small_callable_stub(const delegate* self, Args...args) -> R; 906 | 907 | /// \brief A stub function for function pointers 908 | /// 909 | /// \tparam R2 the return type 910 | /// \tparam Args2 the arguments 911 | /// \param self an instance to the delegate that contains the function pointers 912 | /// \param args the arguments to forward to the function 913 | /// \return the result of invoking the function 914 | template 915 | static auto function_ptr_stub(const delegate* self, Args...args) -> R; 916 | 917 | #if defined(__clang__) 918 | # pragma clang diagnostic pop 919 | #endif 920 | 921 | //---------------------------------------------------------------------------- 922 | // Private Members 923 | //---------------------------------------------------------------------------- 924 | private: 925 | 926 | struct empty_type{}; 927 | 928 | ////////////////////////////////////////////////////////////////////////////// 929 | /// The underlying storage, which may be either a (possibly const) pointer, 930 | /// or a char buffer of storage. 931 | ////////////////////////////////////////////////////////////////////////////// 932 | union { 933 | empty_type m_empty; // Default type does nothing 934 | void* m_instance{}; 935 | const void* m_const_instance; 936 | any_function m_function; 937 | alignas(storage_align) unsigned char m_storage[storage_size]; 938 | }; 939 | stub_function m_stub; 940 | }; 941 | 942 | //------------------------------------------------------------------------------ 943 | // Deduction Guides 944 | //------------------------------------------------------------------------------ 945 | 946 | template 947 | delegate(targets::opaque_function_bind_target) 948 | -> delegate; 949 | 950 | template 951 | delegate(targets::function_bind_target) 952 | -> delegate>; 953 | 954 | template 955 | delegate(targets::member_bind_target) 956 | -> delegate>; 957 | 958 | //============================================================================== 959 | // class : bad_delegate_call 960 | //============================================================================== 961 | 962 | inline 963 | bad_delegate_call::bad_delegate_call() 964 | : runtime_error{"delegate called without being bound"} 965 | { 966 | 967 | } 968 | 969 | //------------------------------------------------------------------------------ 970 | // Binding 971 | //------------------------------------------------------------------------------ 972 | 973 | template 974 | inline constexpr DELEGATE_INLINE_VISIBILITY 975 | auto bind() 976 | noexcept -> function_bind_target 977 | { 978 | return {}; 979 | } 980 | 981 | template 982 | inline constexpr DELEGATE_INLINE_VISIBILITY 983 | auto bind(T* p) 984 | noexcept -> member_bind_target 985 | { 986 | DELEGATE_ASSERT(p != nullptr); 987 | return {p}; 988 | } 989 | 990 | template 991 | inline constexpr DELEGATE_INLINE_VISIBILITY 992 | auto bind(Callable* fn) 993 | noexcept -> callable_ref_bind_target 994 | { 995 | DELEGATE_ASSERT(fn != nullptr); 996 | return {fn}; 997 | } 998 | 999 | template 1000 | inline constexpr DELEGATE_INLINE_VISIBILITY 1001 | auto bind(R(*fn)(Args...)) 1002 | noexcept -> opaque_function_bind_target 1003 | { 1004 | DELEGATE_ASSERT(fn != nullptr); 1005 | return {fn}; 1006 | } 1007 | 1008 | template 1009 | inline constexpr DELEGATE_INLINE_VISIBILITY 1010 | auto bind() 1011 | noexcept -> empty_callable_bind_target 1012 | { 1013 | return {}; 1014 | } 1015 | 1016 | template 1017 | inline constexpr DELEGATE_INLINE_VISIBILITY 1018 | auto bind(Callable) 1019 | noexcept -> empty_callable_bind_target 1020 | { 1021 | return {}; 1022 | } 1023 | 1024 | template 1025 | inline constexpr DELEGATE_INLINE_VISIBILITY 1026 | auto bind(Callable&& callable) 1027 | noexcept -> callable_bind_target> 1028 | { 1029 | return {std::forward(callable)}; 1030 | } 1031 | 1032 | //============================================================================== 1033 | // class : delegate 1034 | //============================================================================== 1035 | 1036 | //------------------------------------------------------------------------------ 1037 | // Constructors 1038 | //------------------------------------------------------------------------------ 1039 | 1040 | template 1041 | inline constexpr 1042 | delegate::delegate() 1043 | noexcept 1044 | : m_empty{}, 1045 | m_stub{&null_stub} 1046 | { 1047 | 1048 | } 1049 | 1050 | template 1051 | template 1052 | inline constexpr DELEGATE_INLINE_VISIBILITY 1053 | delegate::delegate(function_bind_target) 1054 | noexcept 1055 | : m_empty{}, 1056 | m_stub{&function_stub} 1057 | { 1058 | 1059 | } 1060 | 1061 | template 1062 | template 1063 | inline constexpr DELEGATE_INLINE_VISIBILITY 1064 | delegate::delegate(member_bind_target target) 1065 | noexcept 1066 | : m_instance{target.instance}, 1067 | m_stub{&member_function_stub} 1068 | { 1069 | 1070 | } 1071 | 1072 | template 1073 | template 1074 | inline constexpr DELEGATE_INLINE_VISIBILITY 1075 | delegate::delegate(member_bind_target target) 1076 | noexcept 1077 | : m_const_instance{target.instance}, 1078 | m_stub{&member_function_stub} 1079 | { 1080 | 1081 | } 1082 | 1083 | template 1084 | template 1085 | inline constexpr DELEGATE_INLINE_VISIBILITY 1086 | delegate::delegate(callable_ref_bind_target target) 1087 | noexcept 1088 | : m_instance{target.target}, 1089 | m_stub{&callable_view_stub} 1090 | { 1091 | 1092 | } 1093 | 1094 | template 1095 | template 1096 | inline constexpr DELEGATE_INLINE_VISIBILITY 1097 | delegate::delegate(callable_ref_bind_target target) 1098 | noexcept 1099 | : m_const_instance{target.target}, 1100 | m_stub{&callable_view_stub} 1101 | { 1102 | 1103 | } 1104 | 1105 | template 1106 | template 1107 | inline DELEGATE_INLINE_VISIBILITY 1108 | delegate::delegate(callable_bind_target target) 1109 | noexcept 1110 | : m_empty{}, 1111 | m_stub{&small_callable_stub} 1112 | { 1113 | new (static_cast(m_storage)) Fn(std::move(target.target)); 1114 | } 1115 | 1116 | template 1117 | template 1118 | inline constexpr DELEGATE_INLINE_VISIBILITY 1119 | delegate::delegate(empty_callable_bind_target) 1120 | noexcept 1121 | : m_empty{}, 1122 | m_stub{&empty_callable_stub} 1123 | { 1124 | 1125 | } 1126 | 1127 | template 1128 | template 1129 | inline DELEGATE_INLINE_VISIBILITY 1130 | delegate::delegate(opaque_function_bind_target target) 1131 | noexcept 1132 | : m_function{reinterpret_cast(target.target)}, 1133 | m_stub{&function_ptr_stub} 1134 | { 1135 | 1136 | } 1137 | 1138 | //------------------------------------------------------------------------------ 1139 | // Binding 1140 | //------------------------------------------------------------------------------ 1141 | 1142 | template 1143 | template 1144 | inline constexpr DELEGATE_INLINE_VISIBILITY 1145 | auto delegate::bind() 1146 | noexcept -> delegate& 1147 | { 1148 | return ((*this) = delegate{ 1149 | targets::function_bind_target{} 1150 | }); 1151 | } 1152 | 1153 | template 1154 | template 1155 | inline constexpr DELEGATE_INLINE_VISIBILITY 1156 | auto delegate::bind(const T* instance) 1157 | noexcept -> delegate& 1158 | { 1159 | return ((*this) = delegate{ 1160 | targets::member_bind_target{instance} 1161 | }); 1162 | } 1163 | 1164 | template 1165 | template 1166 | inline constexpr DELEGATE_INLINE_VISIBILITY 1167 | auto delegate::bind(T* instance) 1168 | noexcept -> delegate& 1169 | { 1170 | return ((*this) = delegate{ 1171 | targets::member_bind_target{instance} 1172 | }); 1173 | } 1174 | 1175 | template 1176 | template 1177 | inline constexpr DELEGATE_INLINE_VISIBILITY 1178 | auto delegate::bind(Fn* fn) 1179 | noexcept -> delegate& 1180 | { 1181 | return ((*this) = delegate{ 1182 | targets::callable_ref_bind_target{fn} 1183 | }); 1184 | } 1185 | 1186 | template 1187 | template 1188 | inline constexpr DELEGATE_INLINE_VISIBILITY 1189 | auto delegate::bind(const Fn* fn) 1190 | noexcept -> delegate& 1191 | { 1192 | return ((*this) = delegate{ 1193 | targets::callable_ref_bind_target{fn} 1194 | }); 1195 | } 1196 | 1197 | template 1198 | template 1199 | inline constexpr DELEGATE_INLINE_VISIBILITY 1200 | auto delegate::bind() 1201 | noexcept -> delegate& 1202 | { 1203 | return ((*this) = delegate{ 1204 | targets::empty_callable_bind_target{} 1205 | }); 1206 | } 1207 | 1208 | template 1209 | template 1210 | inline constexpr DELEGATE_INLINE_VISIBILITY 1211 | auto delegate::bind(Fn) 1212 | noexcept -> delegate& 1213 | { 1214 | return ((*this) = delegate{ 1215 | targets::empty_callable_bind_target{} 1216 | }); 1217 | } 1218 | 1219 | template 1220 | template 1221 | inline constexpr DELEGATE_INLINE_VISIBILITY 1222 | auto delegate::bind(Fn&& fn) 1223 | noexcept -> delegate& 1224 | { 1225 | return ((*this) = delegate{ 1226 | targets::callable_bind_target>{ 1227 | std::forward(fn) 1228 | } 1229 | }); 1230 | } 1231 | 1232 | template 1233 | template 1234 | inline constexpr DELEGATE_INLINE_VISIBILITY 1235 | auto delegate::bind(R2(*fn)(Args2...)) 1236 | noexcept -> delegate& 1237 | { 1238 | return ((*this) = delegate{ 1239 | targets::opaque_function_bind_target{fn} 1240 | }); 1241 | } 1242 | 1243 | //------------------------------------------------------------------------------ 1244 | 1245 | template 1246 | inline constexpr DELEGATE_INLINE_VISIBILITY 1247 | auto delegate::reset() 1248 | noexcept -> void 1249 | { 1250 | m_stub = &null_stub; 1251 | } 1252 | 1253 | //------------------------------------------------------------------------------ 1254 | // Observers 1255 | //------------------------------------------------------------------------------ 1256 | 1257 | template 1258 | inline constexpr DELEGATE_INLINE_VISIBILITY 1259 | delegate::operator bool() 1260 | const noexcept 1261 | { 1262 | return has_target(); 1263 | } 1264 | 1265 | template 1266 | template 1267 | inline constexpr DELEGATE_INLINE_VISIBILITY 1268 | auto delegate::operator()(UArgs&&...args) 1269 | const -> R 1270 | { 1271 | return std::invoke(m_stub, this, std::forward(args)...); 1272 | } 1273 | 1274 | //------------------------------------------------------------------------------ 1275 | 1276 | template 1277 | inline constexpr DELEGATE_INLINE_VISIBILITY 1278 | auto delegate::has_target() 1279 | const noexcept -> bool 1280 | { 1281 | return m_stub != &null_stub; 1282 | } 1283 | 1284 | template 1285 | template 1286 | inline constexpr DELEGATE_INLINE_VISIBILITY 1287 | auto delegate::has_target() 1288 | const noexcept -> bool 1289 | { 1290 | return m_stub == &function_stub; 1291 | } 1292 | 1293 | template 1294 | template 1295 | inline constexpr DELEGATE_INLINE_VISIBILITY 1296 | auto delegate::has_target(const T* instance) 1297 | const noexcept -> bool 1298 | { 1299 | DELEGATE_ASSERT(instance != nullptr); 1300 | 1301 | return (m_stub == &member_function_stub) && 1302 | (m_const_instance == instance); 1303 | } 1304 | 1305 | template 1306 | template 1307 | inline constexpr DELEGATE_INLINE_VISIBILITY 1308 | auto delegate::has_target(T* instance) 1309 | const noexcept -> bool 1310 | { 1311 | DELEGATE_ASSERT(instance != nullptr); 1312 | 1313 | return (m_stub == &member_function_stub) && 1314 | (m_instance == instance); 1315 | } 1316 | 1317 | template 1318 | template 1319 | inline constexpr DELEGATE_INLINE_VISIBILITY 1320 | auto delegate::has_target(Fn* fn) 1321 | const noexcept -> bool 1322 | { 1323 | DELEGATE_ASSERT(fn != nullptr); 1324 | 1325 | return (m_stub == &callable_view_stub) && 1326 | (m_instance == fn); 1327 | } 1328 | 1329 | template 1330 | template 1331 | inline constexpr DELEGATE_INLINE_VISIBILITY 1332 | auto delegate::has_target(const Fn* fn) 1333 | const noexcept -> bool 1334 | { 1335 | DELEGATE_ASSERT(fn != nullptr); 1336 | 1337 | return (m_stub == &callable_view_stub) && 1338 | (m_const_instance == fn); 1339 | } 1340 | 1341 | template 1342 | template 1343 | inline constexpr DELEGATE_INLINE_VISIBILITY 1344 | auto delegate::has_target() 1345 | const noexcept -> bool 1346 | { 1347 | return (m_stub == &empty_callable_stub); 1348 | } 1349 | 1350 | template 1351 | template 1352 | inline constexpr DELEGATE_INLINE_VISIBILITY 1353 | auto delegate::has_target(Fn) 1354 | const noexcept -> bool 1355 | { 1356 | return has_target(); 1357 | } 1358 | 1359 | template 1360 | template 1361 | inline DELEGATE_INLINE_VISIBILITY 1362 | auto delegate::has_target(const Fn& fn) 1363 | const noexcept -> bool 1364 | { 1365 | // If 'Fn' has equality operators, use that. 1366 | // Otherwise fall back to comparing raw bytes 1367 | if constexpr (detail::is_equality_comparable::value) { 1368 | return (m_stub == &small_callable_stub) && 1369 | ((*reinterpret_cast(m_storage)) == fn); 1370 | } else { 1371 | static_assert( 1372 | std::has_unique_object_representations_v, 1373 | "Specified `Fn` type for `has_target` does not have a unique object " 1374 | "representation! " 1375 | "Target testing can only be reliably done for functions whose identity can " 1376 | "be established by the uniqueness of their bits." 1377 | ); 1378 | 1379 | return (m_stub == &small_callable_stub) && 1380 | (std::memcmp(m_storage, &fn, sizeof(Fn)) == 0); 1381 | } 1382 | } 1383 | 1384 | template 1385 | template 1386 | inline constexpr DELEGATE_INLINE_VISIBILITY 1387 | auto delegate::has_target(R2(*fn)(Args2...)) 1388 | const noexcept -> bool 1389 | { 1390 | DELEGATE_ASSERT(fn != nullptr); 1391 | 1392 | // The result of a comparing pointers of the wrong type is undefined behavior; 1393 | // but thanks to each of our function stubs being uniquely instantiated based 1394 | // on the template signature of the stub function, we can guarantee that if 1395 | // the stubs are the same function, then the underlying data must also be the 1396 | // same type which allows us to do this comparison without violating strict 1397 | // aliasing! 1398 | using underlying = R2(*)(Args2...); 1399 | 1400 | return (m_stub == &function_ptr_stub) && 1401 | (reinterpret_cast(m_function) == fn); 1402 | } 1403 | 1404 | //------------------------------------------------------------------------------ 1405 | // Stub Functions 1406 | //------------------------------------------------------------------------------ 1407 | 1408 | template 1409 | inline 1410 | auto delegate::null_stub(const delegate*, Args...) 1411 | -> R 1412 | { 1413 | // Default stub throws unconditionally 1414 | throw bad_delegate_call{}; 1415 | } 1416 | 1417 | template 1418 | template 1419 | inline 1420 | auto delegate::function_stub(const delegate*, Args...args) 1421 | -> R 1422 | { 1423 | if constexpr (std::is_void_v) { 1424 | std::invoke(Function, std::forward(args)...); 1425 | } else { 1426 | return std::invoke(Function, std::forward(args)...); 1427 | } 1428 | } 1429 | 1430 | template 1431 | template 1432 | inline 1433 | auto delegate::member_function_stub(const delegate* self, 1434 | Args...args) 1435 | -> R 1436 | { 1437 | // This stub is used for both `const` and non-`const` `T` types. To extract 1438 | // the pointer from the correct data member of the union, this uses an 1439 | // immediately-invoking lambda (IIL) with `if constexpr` 1440 | auto* const c = [&self]{ 1441 | if constexpr (std::is_const_v) { 1442 | return static_cast(self->m_const_instance); 1443 | } else { 1444 | return static_cast(self->m_instance); 1445 | } 1446 | }(); 1447 | 1448 | if constexpr (std::is_void_v) { 1449 | std::invoke(MemberFunction, *c, std::forward(args)...); 1450 | } else { 1451 | return std::invoke(MemberFunction, *c, std::forward(args)...); 1452 | } 1453 | } 1454 | 1455 | template 1456 | template 1457 | inline 1458 | auto delegate::callable_view_stub(const delegate* self, 1459 | Args...args) 1460 | -> R 1461 | { 1462 | // This stub is used for both `const` and non-`const` `Fn` types. To extract 1463 | // the pointer from the correct data member of the union, this uses an 1464 | // immediately-invoking lambda (IIL) with `if constexpr` 1465 | auto* const f = [&self]{ 1466 | if constexpr (std::is_const_v) { 1467 | return static_cast(self->m_const_instance); 1468 | } else { 1469 | return static_cast(self->m_instance); 1470 | } 1471 | }(); 1472 | 1473 | if constexpr (std::is_void_v) { 1474 | std::invoke(*f, std::forward(args)...); 1475 | } else { 1476 | return std::invoke(*f, std::forward(args)...); 1477 | } 1478 | } 1479 | 1480 | template 1481 | template 1482 | inline 1483 | auto delegate::empty_callable_stub(const delegate*, 1484 | Args...args) 1485 | -> R 1486 | { 1487 | if constexpr (std::is_void_v) { 1488 | std::invoke(Fn{}, std::forward(args)...); 1489 | } else { 1490 | return std::invoke(Fn{}, std::forward(args)...); 1491 | } 1492 | } 1493 | 1494 | template 1495 | template 1496 | inline 1497 | auto delegate::small_callable_stub(const delegate* self, 1498 | Args...args) 1499 | -> R 1500 | { 1501 | const auto& f = *std::launder(reinterpret_cast(self->m_storage)); 1502 | 1503 | if constexpr (std::is_void_v) { 1504 | std::invoke(f, std::forward(args)...); 1505 | } else { 1506 | return std::invoke(f, std::forward(args)...); 1507 | } 1508 | } 1509 | 1510 | template 1511 | template 1512 | inline 1513 | auto delegate::function_ptr_stub(const delegate* self, 1514 | Args...args) 1515 | -> R 1516 | { 1517 | const auto f = reinterpret_cast(self->m_function); 1518 | 1519 | if constexpr (std::is_void_v) { 1520 | std::invoke(f, std::forward(args)...); 1521 | } else { 1522 | return std::invoke(f, std::forward(args)...); 1523 | } 1524 | } 1525 | 1526 | } // inline namespace bitwizeshift 1527 | } // namespace DELEGATE_NAMESPACE_INTERNAL 1528 | 1529 | #if defined(_MSC_VER) 1530 | # pragma warning(pop) 1531 | #endif 1532 | 1533 | #endif /* DELEGATE_DELEGATE_HPP */ 1534 | --------------------------------------------------------------------------------