├── .clang-format ├── .devcontainer └── devcontainer.json ├── .gitattributes ├── .github └── workflows │ └── cmake.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── examples ├── .clang-format ├── CMakeLists.txt ├── listing_1.cpp ├── listing_2.cpp └── listing_3.cpp ├── include └── std23 │ ├── __functional_base.h │ ├── function.h │ ├── function_ref.h │ └── move_only_function.h └── tests ├── CMakeLists.txt ├── function ├── CMakeLists.txt ├── common_callables.cpp ├── common_callables.h ├── main.cpp ├── test_basics.cpp ├── test_ctad.cpp ├── test_nontype.cpp ├── test_nullable.cpp ├── test_reference_semantics.cpp └── test_value_semantics.cpp ├── function_ref ├── CMakeLists.txt ├── common_callables.cpp ├── common_callables.h ├── main.cpp ├── test_basics.cpp ├── test_call_pattern.cpp ├── test_const.cpp ├── test_const_noexcept.cpp ├── test_constexpr.cpp ├── test_constinit.cpp ├── test_ctad.cpp ├── test_noexcept.cpp ├── test_nontype.cpp ├── test_return_reference.cpp └── test_safety.cpp ├── include └── boost │ └── ut.hpp └── move_only_function ├── CMakeLists.txt ├── common_callables.cpp ├── common_callables.h ├── main.cpp ├── test_basics.cpp ├── test_cvref.cpp ├── test_inplace.cpp ├── test_noexcept.cpp ├── test_nontype.cpp ├── test_nullable.cpp ├── test_reference_semantics.cpp ├── test_return_reference.cpp ├── test_unique.cpp └── test_value_semantics.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Microsoft 3 | ColumnLimit: '80' 4 | AllowShortFunctionsOnASingleLine: InlineOnly 5 | AlwaysBreakTemplateDeclarations: MultiLine 6 | IndentRequiresClause: false 7 | RequiresClausePosition: WithPreceding 8 | SpaceAfterTemplateKeyword: 'false' 9 | Standard: Latest 10 | BreakBeforeBraces: Custom 11 | BraceWrapping: 12 | AfterCaseLabel: true 13 | AfterClass: true 14 | AfterControlStatement: true 15 | AfterEnum: true 16 | AfterFunction: true 17 | AfterNamespace: true 18 | AfterUnion: true 19 | AfterStruct: true 20 | AfterExternBlock: false 21 | BeforeCatch: true 22 | BeforeElse: true 23 | BeforeLambdaBody: true 24 | SplitEmptyFunction: false 25 | SplitEmptyRecord: false 26 | SplitEmptyNamespace: true 27 | 28 | ... 29 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | {"image":"mcr.microsoft.com/devcontainers/cpp:ubuntu-20.04"} -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: [ main ] 7 | 8 | jobs: 9 | build: 10 | runs-on: ${{ matrix.os }} 11 | 12 | strategy: 13 | matrix: 14 | os: [ windows-2022, ubuntu-20.04, macos-15 ] 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: seanmiddleditch/gha-setup-ninja@master 19 | 20 | - name: Set up GCC 21 | if: startsWith(matrix.os, 'ubuntu') 22 | uses: egor-tensin/setup-gcc@v1 23 | with: 24 | version: 11 25 | 26 | - name: Set up Developer Command Prompt 27 | if: startsWith(matrix.os, 'windows') 28 | uses: ilammy/msvc-dev-cmd@v1 29 | 30 | - name: Enable AddressSanitizer 31 | run: | 32 | echo "CXXFLAGS=-fsanitize=address" >> $GITHUB_ENV 33 | echo "LDFLAGS=-fsanitize=address" >> $GITHUB_ENV 34 | 35 | - name: Configure CMake 36 | run: cmake -B ${{github.workspace}}/build -G "Ninja Multi-Config" 37 | 38 | - name: Build 39 | run: | 40 | cmake --build ${{github.workspace}}/build --config Debug 41 | cmake --build ${{github.workspace}}/build --config RelWithDebInfo 42 | 43 | - name: Test 44 | working-directory: ${{github.workspace}}/build 45 | env: 46 | ASAN_OPTIONS: windows_hook_rtl_allocators=true 47 | run: | 48 | ctest --output-on-failure -C Debug 49 | ctest --output-on-failure -C RelWithDebInfo 50 | 51 | - name: Install 52 | run: > 53 | cmake --install ${{github.workspace}}/build 54 | --prefix ${{github.workspace}}/out 55 | 56 | - name: Compile Examples 57 | working-directory: ${{github.workspace}}/examples 58 | env: 59 | CMAKE_PREFIX_PATH: ${{github.workspace}}/out 60 | run: | 61 | cmake -B build -G Ninja 62 | cmake --build build 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd 364 | 365 | # CMake presets 366 | /CMakeSettings.json 367 | /CMakeUserPresets.json 368 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | get_property(is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 4 | if(is_multi_config) 5 | set(CMAKE_CONFIGURATION_TYPES Debug RelWithDebInfo CACHE STRING 6 | "Semicolon separated list of supported configuration types") 7 | mark_as_advanced(CMAKE_CONFIGURATION_TYPES) 8 | elseif(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CXX_FLAGS) 9 | message(WARNING "No CMAKE_BUILD_TYPE is selected") 10 | endif() 11 | 12 | project(nontype_functional VERSION 1.0.2 LANGUAGES CXX) 13 | 14 | if(NOT DEFINED PROJECT_IS_TOP_LEVEL) 15 | if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) 16 | set(PROJECT_IS_TOP_LEVEL TRUE) 17 | else() 18 | set(PROJECT_IS_TOP_LEVEL FALSE) 19 | endif() 20 | endif() 21 | 22 | include(CTest) 23 | include(CMakePackageConfigHelpers) 24 | 25 | set(CMAKE_CXX_STANDARD 20) 26 | set(CMAKE_CXX_EXTENSIONS OFF) 27 | set(CMAKE_CXX_STANDARD_REQUIRED YES) 28 | 29 | if(MSVC) 30 | add_compile_options($<$:/W3>) 31 | string(REPLACE "/GR" "/GR-" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) 32 | else() 33 | add_compile_options($<$:-Wall>) 34 | add_compile_options($<$:-Wconversion>) 35 | add_compile_options($<$:-Wsign-conversion>) 36 | add_compile_options($<$:-Wsign-compare>) 37 | add_compile_options($<$:-fno-rtti>) 38 | endif() 39 | 40 | add_library(nontype_functional INTERFACE) 41 | add_library(std23::nontype_functional ALIAS nontype_functional) 42 | target_sources(nontype_functional INTERFACE 43 | "$" 44 | "$" 45 | "$" 46 | "$" 47 | "$" 48 | "$" 49 | "$" 50 | "$" 51 | ) 52 | target_include_directories(nontype_functional 53 | INTERFACE $ 54 | $ 55 | ) 56 | target_compile_features(nontype_functional INTERFACE cxx_std_20) 57 | 58 | if(PROJECT_IS_TOP_LEVEL) 59 | set(_version_filename "nontype_functional-config-version.cmake") 60 | write_basic_package_version_file( 61 | ${_version_filename} 62 | COMPATIBILITY SameMajorVersion 63 | ) 64 | install(TARGETS nontype_functional 65 | EXPORT nontype_functional-config) 66 | install(EXPORT nontype_functional-config 67 | NAMESPACE std23:: 68 | DESTINATION lib/cmake/${PROJECT_NAME}) 69 | install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${_version_filename}" 70 | DESTINATION lib/cmake/${PROJECT_NAME}) 71 | install(FILES "$" 72 | DESTINATION include/std23) 73 | endif() 74 | 75 | if(PROJECT_IS_TOP_LEVEL AND BUILD_TESTING) 76 | add_subdirectory("tests") 77 | endif() 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2022, Zhihao Yuan 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nontype\ 2 | 3 | [![GitHub tag](https://img.shields.io/github/v/tag/zhihaoy/nontype_functional?sort=semver)](https://github.com/zhihaoy/nontype_functional/tags) 4 | [![GitHub license](https://img.shields.io/github/license/zhihaoy/nontype_functional)](https://github.com/zhihaoy/nontype_functional/blob/main/LICENSE) 5 | [![CMake](https://github.com/zhihaoy/nontype_functional/actions/workflows/cmake.yml/badge.svg)](https://github.com/zhihaoy/nontype_functional/actions/workflows/cmake.yml) 6 | 7 | 8 | Provide complete implementation of `std::function`, `std::function_ref`, and `std::move_only_function` equivalent to those in the C++26 `` header. 9 | 10 | ## Highlights 11 | 12 | - Macro-free implementation 13 | - The size of each specialization is two pointers 14 | - Not require RTTI 15 | - Support classes without `operator()` 16 | 17 | The implementation does not guarantee the best performance under all use cases but provides adequate code size & quality while maintaining full conformance.[^1] 18 | 19 | 20 | ## Supported toolchains 21 | 22 | | Toolset | Standard Library | Test Environment | 23 | | -------------------- | ---------------- | ------------------ | 24 | | GCC >= 11.1.0 | libstdc++ | Ubuntu 20.04 | 25 | | MSVC >= 14.30 | Microsoft STL | Visual Studio 2022 | 26 | | Clang >= 17.0.6 | libc++ | Xcode 16.0 | 27 | 28 | 29 | ## Installation 30 | 31 | It's a header-only library. You may also install and consume its CMake targets: 32 | 33 | ```cmake 34 | find_package(nontype_functional CONFIG REQUIRED) 35 | target_link_libraries("main" PRIVATE std23::nontype_functional) 36 | ``` 37 | 38 | 39 | ## Getting started 40 | 41 | ```cpp 42 | #include 43 | 44 | using std23::function_ref; 45 | 46 | void parse_ini(function_ref read_cb); 47 | 48 | ... 49 | 50 | #include 51 | 52 | int main() 53 | { 54 | auto fp = ::fopen("my.ini", "r"); 55 | parse_ini([fp](auto ptr, auto n) 56 | { return ::fread(ptr, 1, n, fp); }); 57 | ::fclose(fp); 58 | } 59 | ``` 60 | 61 | Ignore the fact that the code has no error handling or resource-safety; the callable wrappers, `function_ref` in the example, generalized the idea of *callbacks*. You can pass anything with a matching call pattern to another function, `parse_ini` in here, without turning the latter into a template. 62 | 63 | Now, what if you have an existing class that can read data, but it's not a function object? 64 | 65 | ```cpp 66 | class data_source 67 | { 68 | ... 69 | 70 | public: 71 | auto read(char *, size_t) -> size_t; 72 | }; 73 | ``` 74 | 75 | Then you may designate a named member function, `read` in this example, to serve the role of an `operator()`: 76 | 77 | ```cpp 78 | using std23::nontype; 79 | 80 | int main() 81 | { 82 | data_source input; 83 | parse_ini({nontype<&data_source::read>, input}); 84 | } 85 | ``` 86 | 87 | The `nontype` tag generalized the idea of *delegates* from other languages, like C♯. What replaces `operator()` doesn't have to be a member function. You can also use a free function or even a lambda: 88 | 89 | ```cpp 90 | int main() 91 | { 92 | auto fp = ::fopen("my.ini", "r"); 93 | parse_ini({nontype<[](FILE *fh, auto ptr, auto n) 94 | { return ::fread(ptr, 1, n, fh); }>, 95 | fp}); 96 | ::fclose(fp); 97 | } 98 | ``` 99 | 100 | Feels like creating a member function for `FILE` on the fly, isn't it? 101 | 102 | 103 | ## Roadmap 104 | 105 | - [x] 0.8 – `std::function_ref` & `std::function` 106 | - [x] 0.9 – `std::move_only_function` 107 | - [x] 1.0 – `nontype_t` constructors for `move_only_function` 108 | - [ ] 1.1 – `copyable_function` from P2548 109 | - [ ] 1.2 – Support C++20 modules 110 | 111 | 112 | ## See also 113 | 114 | cppreference page for [`std::function`](https://en.cppreference.com/w/cpp/utility/functional/function) 115 |
116 | cppreference page for [`std::move_only_function`](https://en.cppreference.com/w/cpp/utility/functional/move_only_function) 117 |
118 | cppreference page for [`std::function_ref`](https://en.cppreference.com/w/cpp/utility/functional/function_ref) 119 | 120 | 121 | [^1]: Except for `std::function`'s `target()` member function, which is unimplemented because it requires RTTI. -------------------------------------------------------------------------------- /examples/.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: InheritParentConfig 3 | ColumnLimit: '70' -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(std23-functional-examples CXX) 3 | 4 | find_package(nontype_functional 1.0 CONFIG) 5 | 6 | add_library(examples OBJECT) 7 | target_sources(examples PRIVATE 8 | "listing_1.cpp" 9 | "listing_2.cpp" 10 | "listing_3.cpp" 11 | ) 12 | target_compile_definitions(examples 13 | PRIVATE $<$:_CRT_SECURE_NO_WARNINGS>) 14 | target_link_libraries(examples PRIVATE std23::nontype_functional) 15 | -------------------------------------------------------------------------------- /examples/listing_1.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using std23::function_ref; 4 | 5 | void parse_ini(function_ref read_cb); 6 | 7 | #include 8 | 9 | int main() 10 | { 11 | auto fp = ::fopen("my.ini", "r"); 12 | parse_ini([fp](auto ptr, auto n) 13 | { return ::fread(ptr, 1, n, fp); }); 14 | ::fclose(fp); 15 | } 16 | -------------------------------------------------------------------------------- /examples/listing_2.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using std23::function_ref; 4 | 5 | void parse_ini(function_ref read_cb); 6 | 7 | #include 8 | 9 | class data_source 10 | { 11 | /* ... */ 12 | 13 | public: 14 | auto read(char *, size_t) -> size_t; 15 | }; 16 | 17 | using std23::nontype; 18 | 19 | int main() 20 | { 21 | data_source input; 22 | parse_ini({nontype<&data_source::read>, input}); 23 | } 24 | -------------------------------------------------------------------------------- /examples/listing_3.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using std23::function_ref; 4 | using std23::nontype; 5 | 6 | void parse_ini(function_ref read_cb); 7 | 8 | #include 9 | 10 | int main() 11 | { 12 | auto fp = ::fopen("my.ini", "r"); 13 | parse_ini({nontype<[](FILE *fh, auto ptr, auto n) 14 | { return ::fread(ptr, 1, n, fh); }>, 15 | fp}); 16 | ::fclose(fp); 17 | } 18 | -------------------------------------------------------------------------------- /include/std23/__functional_base.h: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDE_STD23____FUNCTIONAL__BASE 2 | #define INCLUDE_STD23____FUNCTIONAL__BASE 3 | 4 | #include 5 | #include 6 | 7 | namespace std23 8 | { 9 | 10 | template struct nontype_t // freestanding 11 | { 12 | explicit nontype_t() = default; 13 | }; 14 | 15 | template inline constexpr nontype_t nontype{}; // freestanding 16 | 17 | using std::in_place_type; 18 | using std::in_place_type_t; 19 | using std::initializer_list; 20 | using std::nullptr_t; 21 | 22 | template 23 | requires std::is_invocable_r_v 24 | constexpr R invoke_r(F &&f, Args &&...args) // freestanding 25 | noexcept(std::is_nothrow_invocable_r_v) 26 | { 27 | if constexpr (std::is_void_v) 28 | std::invoke(std::forward(f), std::forward(args)...); 29 | else 30 | return std::invoke(std::forward(f), std::forward(args)...); 31 | } 32 | 33 | // See also: https://www.agner.org/optimize/calling_conventions.pdf 34 | template 35 | inline constexpr auto _select_param_type = [] 36 | { 37 | if constexpr (std::is_trivially_copyable_v) 38 | return std::type_identity(); 39 | else 40 | return std::add_rvalue_reference(); 41 | }; 42 | 43 | template 44 | using _param_t = std::invoke_result_t)>::type; 45 | 46 | template 47 | inline constexpr bool _is_not_self = 48 | not std::is_same_v, Self>; 49 | 50 | template class> 51 | inline constexpr bool _looks_nullable_to_impl = std::is_member_pointer_v; 52 | 53 | template class Self> 54 | inline constexpr bool _looks_nullable_to_impl = 55 | std::is_function_v; 56 | 57 | template class Self> 58 | inline constexpr bool _looks_nullable_to_impl, Self> = true; 59 | 60 | template class Self> 61 | inline constexpr bool _looks_nullable_to = 62 | _looks_nullable_to_impl, Self>; 63 | 64 | template inline constexpr bool _is_not_nontype_t = true; 65 | template inline constexpr bool _is_not_nontype_t> = false; 66 | 67 | template struct _adapt_signature; 68 | 69 | template requires std::is_function_v 70 | struct _adapt_signature 71 | { 72 | using type = F; 73 | }; 74 | 75 | template using _adapt_signature_t = _adapt_signature::type; 76 | 77 | template struct _not_qualifying_this 78 | {}; 79 | 80 | template struct _not_qualifying_this 81 | { 82 | using type = R(Args...); 83 | }; 84 | 85 | template 86 | struct _not_qualifying_this 87 | { 88 | using type = R(Args...) noexcept; 89 | }; 90 | 91 | template 92 | struct _not_qualifying_this : _not_qualifying_this 93 | {}; 94 | 95 | template 96 | struct _not_qualifying_this 97 | : _not_qualifying_this 98 | {}; 99 | 100 | template 101 | struct _not_qualifying_this 102 | : _not_qualifying_this 103 | {}; 104 | 105 | template 106 | struct _not_qualifying_this : _not_qualifying_this 107 | {}; 108 | 109 | template 110 | struct _not_qualifying_this 111 | : _not_qualifying_this 112 | {}; 113 | 114 | template 115 | struct _not_qualifying_this 116 | : _not_qualifying_this 117 | {}; 118 | 119 | template 120 | struct _not_qualifying_this 121 | : _not_qualifying_this 122 | {}; 123 | 124 | template 125 | struct _not_qualifying_this : _not_qualifying_this 126 | {}; 127 | 128 | template 129 | struct _not_qualifying_this 130 | : _not_qualifying_this 131 | {}; 132 | 133 | template 134 | struct _not_qualifying_this 135 | : _not_qualifying_this 136 | {}; 137 | 138 | template 139 | struct _not_qualifying_this 140 | : _not_qualifying_this 141 | {}; 142 | 143 | template 144 | struct _not_qualifying_this 145 | : _not_qualifying_this 146 | {}; 147 | 148 | template 149 | struct _not_qualifying_this 150 | : _not_qualifying_this 151 | {}; 152 | 153 | template 154 | struct _not_qualifying_this 155 | : _not_qualifying_this 156 | {}; 157 | 158 | template 159 | struct _not_qualifying_this 160 | : _not_qualifying_this 161 | {}; 162 | 163 | template 164 | struct _not_qualifying_this 165 | : _not_qualifying_this 166 | {}; 167 | 168 | template 169 | struct _not_qualifying_this 170 | : _not_qualifying_this 171 | {}; 172 | 173 | template 174 | struct _not_qualifying_this 175 | : _not_qualifying_this 176 | {}; 177 | 178 | template 179 | struct _not_qualifying_this 180 | : _not_qualifying_this 181 | {}; 182 | 183 | template 184 | struct _not_qualifying_this 185 | : _not_qualifying_this 186 | {}; 187 | 188 | template 189 | struct _not_qualifying_this 190 | : _not_qualifying_this 191 | {}; 192 | 193 | template 194 | struct _not_qualifying_this 195 | : _not_qualifying_this 196 | {}; 197 | 198 | template struct _drop_first_arg_to_invoke; 199 | 200 | template 201 | struct _drop_first_arg_to_invoke 202 | { 203 | using type = R(Args...); 204 | }; 205 | 206 | template 207 | struct _drop_first_arg_to_invoke 208 | { 209 | using type = R(Args...) noexcept; 210 | }; 211 | 212 | template requires std::is_object_v 213 | struct _drop_first_arg_to_invoke 214 | { 215 | using type = std::invoke_result_t(); 216 | }; 217 | 218 | template requires std::is_function_v 219 | struct _drop_first_arg_to_invoke : _not_qualifying_this 220 | {}; 221 | 222 | template 223 | using _drop_first_arg_to_invoke_t = _drop_first_arg_to_invoke::type; 224 | 225 | } // namespace std23 226 | 227 | #endif 228 | -------------------------------------------------------------------------------- /include/std23/function.h: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDE_STD23_FUNCTION 2 | #define INCLUDE_STD23_FUNCTION 3 | 4 | #include "__functional_base.h" 5 | 6 | #include 7 | #include 8 | 9 | namespace std23 10 | { 11 | 12 | template struct _opt_fn_sig; 13 | 14 | template struct _opt_fn_sig 15 | { 16 | using function_type = R(Args...); 17 | 18 | template 19 | static constexpr bool is_invocable_using = 20 | std::is_invocable_r_v; 21 | }; 22 | 23 | template struct _copyable_function 24 | { 25 | struct lvalue_callable 26 | { 27 | virtual R operator()(Args...) const = 0; 28 | virtual constexpr ~lvalue_callable() = default; 29 | 30 | void copy_into(std::byte *storage) const { copy_into_(storage); } 31 | void move_into(std::byte *storage) noexcept { move_into_(storage); } 32 | 33 | protected: 34 | virtual void copy_into_(void *) const = 0; 35 | virtual void move_into_(void *) noexcept = 0; 36 | }; 37 | 38 | template struct empty_object : lvalue_callable 39 | { 40 | void copy_into_(void *location) const override 41 | { 42 | ::new (location) Self; 43 | } 44 | 45 | void move_into_(void *location) noexcept override 46 | { 47 | ::new (location) Self; 48 | } 49 | }; 50 | 51 | struct constructible_lvalue : lvalue_callable 52 | { 53 | [[noreturn]] R operator()(Args...) const override 54 | { 55 | #if defined(_MSC_VER) 56 | __assume(0); 57 | #else 58 | __builtin_unreachable(); 59 | #endif 60 | } 61 | }; 62 | 63 | template class stored_object : constructible_lvalue 64 | { 65 | std::conditional_t, T, std::unique_ptr> p_; 66 | 67 | public: 68 | template 69 | explicit stored_object(F &&f) 70 | requires(_is_not_self and 71 | not std::is_pointer_v) 72 | : p_(std::make_unique(std::forward(f))) 73 | {} 74 | 75 | explicit stored_object(T p) noexcept requires std::is_pointer_v 76 | : p_(p) 77 | {} 78 | 79 | protected: 80 | decltype(auto) get() const 81 | { 82 | if constexpr (std::is_pointer_v) 83 | return p_; 84 | else 85 | return *p_; 86 | } 87 | 88 | void copy_into_(void *location) const override 89 | { 90 | ::new (location) Self(get()); 91 | } 92 | 93 | void move_into_(void *location) noexcept override 94 | { 95 | ::new (location) Self(std::move(*this)); 96 | } 97 | }; 98 | 99 | template 100 | class stored_object : constructible_lvalue 101 | { 102 | T &target_; 103 | 104 | public: 105 | explicit stored_object(T &target) noexcept : target_(target) {} 106 | 107 | protected: 108 | decltype(auto) get() const { return target_; } 109 | 110 | void copy_into_(void *location) const override 111 | { 112 | ::new (location) Self(*this); 113 | } 114 | 115 | void move_into_(void *location) noexcept override 116 | { 117 | ::new (location) Self(*this); 118 | } 119 | }; 120 | 121 | struct empty_target_object final : empty_object 122 | { 123 | [[noreturn]] R operator()(Args...) const override 124 | { 125 | throw std::bad_function_call{}; 126 | } 127 | }; 128 | 129 | template 130 | struct unbound_target_object final : empty_object> 131 | { 132 | R operator()(Args... args) const override 133 | { 134 | return std23::invoke_r(f, static_cast(args)...); 135 | } 136 | }; 137 | 138 | template 139 | class target_object final : stored_object> 140 | { 141 | using base = stored_object>; 142 | 143 | public: 144 | template 145 | explicit target_object(F &&f) noexcept( 146 | std::is_nothrow_constructible_v) 147 | requires _is_not_self 148 | : base(std::forward(f)) 149 | {} 150 | 151 | R operator()(Args... args) const override 152 | { 153 | return std23::invoke_r(this->get(), static_cast(args)...); 154 | } 155 | }; 156 | 157 | template 158 | class bound_target_object final 159 | : stored_object> 160 | { 161 | using base = stored_object>; 162 | 163 | public: 164 | template 165 | explicit bound_target_object(U &&obj) noexcept( 166 | std::is_nothrow_constructible_v) 167 | requires _is_not_self 168 | : base(std::forward(obj)) 169 | {} 170 | 171 | R operator()(Args... args) const override 172 | { 173 | return std23::invoke_r(f, this->get(), 174 | static_cast(args)...); 175 | } 176 | }; 177 | }; 178 | 179 | template::function_type> 180 | class function; 181 | 182 | template class function 183 | { 184 | using signature = _opt_fn_sig; 185 | 186 | template 187 | static constexpr bool is_invocable_using = 188 | signature::template is_invocable_using; 189 | 190 | template using lvalue = std::decay_t &; 191 | 192 | using copyable_function = _copyable_function...>; 193 | 194 | using lvalue_callable = copyable_function::lvalue_callable; 195 | using empty_target_object = copyable_function::empty_target_object; 196 | 197 | struct typical_target_object : lvalue_callable 198 | { 199 | union 200 | { 201 | void (*fp)() = nullptr; 202 | void *p; 203 | }; 204 | }; 205 | 206 | template 207 | using target_object_for = 208 | copyable_function::template target_object>; 209 | 210 | template 211 | using unbound_target_object = 212 | copyable_function::template unbound_target_object; 213 | 214 | template 215 | using bound_target_object_for = 216 | copyable_function::template bound_target_object< 217 | f, std::unwrap_ref_decay_t>; 218 | 219 | template> 220 | static bool constexpr is_viable_initializer = 221 | std::is_copy_constructible_v and std::is_constructible_v; 222 | 223 | alignas(typical_target_object) 224 | std::byte storage_[sizeof(typical_target_object)]; 225 | 226 | auto storage_location() noexcept -> void * { return &storage_; } 227 | 228 | auto target() noexcept 229 | { 230 | return std::launder(reinterpret_cast(&storage_)); 231 | } 232 | 233 | auto target() const noexcept 234 | { 235 | return std::launder( 236 | reinterpret_cast(&storage_)); 237 | } 238 | 239 | public: 240 | using result_type = R; 241 | 242 | function() noexcept { ::new (storage_location()) empty_target_object; } 243 | function(nullptr_t) noexcept : function() {} 244 | 245 | template 246 | function(F &&f) noexcept( 247 | std::is_nothrow_constructible_v, F>) 248 | requires _is_not_self and is_invocable_using> and 249 | is_viable_initializer 250 | { 251 | using T = target_object_for; 252 | static_assert(sizeof(T) <= sizeof(storage_)); 253 | 254 | if constexpr (_looks_nullable_to) 255 | { 256 | if (f == nullptr) 257 | { 258 | std::construct_at(this); 259 | return; 260 | } 261 | } 262 | 263 | ::new (storage_location()) T(std::forward(f)); 264 | } 265 | 266 | template 267 | function(nontype_t) noexcept requires is_invocable_using 268 | { 269 | ::new (storage_location()) unbound_target_object; 270 | } 271 | 272 | template 273 | function(nontype_t, U &&obj) noexcept( 274 | std::is_nothrow_constructible_v, U>) 275 | requires is_invocable_using> and 276 | is_viable_initializer 277 | { 278 | using T = bound_target_object_for; 279 | static_assert(sizeof(T) <= sizeof(storage_)); 280 | 281 | ::new (storage_location()) T(std::forward(obj)); 282 | } 283 | 284 | function(function const &other) { other.target()->copy_into(storage_); } 285 | function(function &&other) noexcept { other.target()->move_into(storage_); } 286 | 287 | function &operator=(function const &other) 288 | { 289 | if (&other != this) 290 | { 291 | auto tmp = other; 292 | swap(tmp); 293 | } 294 | 295 | return *this; 296 | } 297 | 298 | function &operator=(function &&other) noexcept 299 | { 300 | if (&other != this) 301 | { 302 | std::destroy_at(this); 303 | return *std::construct_at(this, std::move(other)); 304 | } 305 | else 306 | return *this; 307 | } 308 | 309 | void swap(function &other) noexcept { std::swap(*this, other); } 310 | friend void swap(function &lhs, function &rhs) noexcept { lhs.swap(rhs); } 311 | 312 | ~function() { std::destroy_at(target()); } 313 | 314 | explicit operator bool() const noexcept 315 | { 316 | constexpr empty_target_object null; 317 | return __builtin_memcmp(storage_, (void *)&null, sizeof(void *)) != 0; 318 | } 319 | 320 | friend bool operator==(function const &f, nullptr_t) noexcept { return !f; } 321 | 322 | R operator()(Args... args) const 323 | { 324 | return (*target())(std::forward(args)...); 325 | } 326 | }; 327 | 328 | template struct _strip_noexcept; 329 | 330 | template struct _strip_noexcept 331 | { 332 | using type = R(Args...); 333 | }; 334 | 335 | template struct _strip_noexcept 336 | { 337 | using type = R(Args...); 338 | }; 339 | 340 | template using _strip_noexcept_t = _strip_noexcept::type; 341 | 342 | template requires std::is_function_v 343 | function(F *) -> function<_strip_noexcept_t>; 344 | 345 | template 346 | function(T) -> function<_strip_noexcept_t< 347 | _drop_first_arg_to_invoke_t>>; 348 | 349 | template 350 | function(nontype_t) 351 | -> function<_strip_noexcept_t<_adapt_signature_t>>; 352 | 353 | template 354 | function(nontype_t, T) 355 | -> function<_strip_noexcept_t<_drop_first_arg_to_invoke_t>>; 356 | 357 | } // namespace std23 358 | 359 | #endif 360 | -------------------------------------------------------------------------------- /include/std23/function_ref.h: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDE_STD23_FUNCTION__REF 2 | #define INCLUDE_STD23_FUNCTION__REF 3 | 4 | #include "__functional_base.h" 5 | 6 | #include 7 | 8 | namespace std23 9 | { 10 | 11 | template struct _qual_fn_sig; 12 | 13 | template struct _qual_fn_sig 14 | { 15 | using function = R(Args...); 16 | static constexpr bool is_noexcept = false; 17 | 18 | template 19 | static constexpr bool is_invocable_using = 20 | std::is_invocable_r_v; 21 | 22 | template using cv = T; 23 | }; 24 | 25 | template struct _qual_fn_sig 26 | { 27 | using function = R(Args...); 28 | static constexpr bool is_noexcept = true; 29 | 30 | template 31 | static constexpr bool is_invocable_using = 32 | std::is_nothrow_invocable_r_v; 33 | 34 | template using cv = T; 35 | }; 36 | 37 | template 38 | struct _qual_fn_sig : _qual_fn_sig 39 | { 40 | template using cv = T const; 41 | }; 42 | 43 | template 44 | struct _qual_fn_sig 45 | : _qual_fn_sig 46 | { 47 | template using cv = T const; 48 | }; 49 | 50 | struct _function_ref_base 51 | { 52 | union storage 53 | { 54 | void *p_ = nullptr; 55 | void const *cp_; 56 | void (*fp_)(); 57 | 58 | constexpr storage() noexcept = default; 59 | 60 | template requires std::is_object_v 61 | constexpr explicit storage(T *p) noexcept : p_(p) 62 | {} 63 | 64 | template requires std::is_object_v 65 | constexpr explicit storage(T const *p) noexcept : cp_(p) 66 | {} 67 | 68 | template requires std::is_function_v 69 | constexpr explicit storage(T *p) noexcept 70 | : fp_(reinterpret_cast(p)) 71 | {} 72 | }; 73 | 74 | template constexpr static auto get(storage obj) 75 | { 76 | if constexpr (std::is_const_v) 77 | return static_cast(obj.cp_); 78 | else if constexpr (std::is_object_v) 79 | return static_cast(obj.p_); 80 | else 81 | return reinterpret_cast(obj.fp_); 82 | } 83 | }; 84 | 85 | template::function> 86 | class function_ref; // freestanding 87 | 88 | template 89 | class function_ref // freestanding 90 | : _function_ref_base 91 | { 92 | using signature = _qual_fn_sig; 93 | 94 | template using cv = signature::template cv; 95 | template using cvref = cv &; 96 | static constexpr bool noex = signature::is_noexcept; 97 | 98 | template 99 | static constexpr bool is_invocable_using = 100 | signature::template is_invocable_using; 101 | 102 | typedef R fwd_t(storage, _param_t...) noexcept(noex); 103 | fwd_t *fptr_ = nullptr; 104 | storage obj_; 105 | 106 | public: 107 | template 108 | function_ref(F *f) noexcept 109 | requires std::is_function_v and is_invocable_using 110 | : fptr_( 111 | [](storage fn_, _param_t... args) noexcept(noex) -> R 112 | { 113 | if constexpr (std::is_void_v) 114 | get(fn_)(static_cast(args)...); 115 | else 116 | return get(fn_)(static_cast(args)...); 117 | }), 118 | obj_(f) 119 | { 120 | assert(f != nullptr && "must reference a function"); 121 | } 122 | 123 | template> 124 | constexpr function_ref(F &&f) noexcept 125 | requires(_is_not_self and 126 | not std::is_member_pointer_v and 127 | is_invocable_using>) 128 | : fptr_( 129 | [](storage fn_, _param_t... args) noexcept(noex) -> R 130 | { 131 | cvref obj = *get(fn_); 132 | if constexpr (std::is_void_v) 133 | obj(static_cast(args)...); 134 | else 135 | return obj(static_cast(args)...); 136 | }), 137 | obj_(std::addressof(f)) 138 | {} 139 | 140 | template 141 | function_ref &operator=(T) 142 | requires(_is_not_self and not std::is_pointer_v and 143 | _is_not_nontype_t) 144 | = delete; 145 | 146 | template 147 | constexpr function_ref(nontype_t) noexcept 148 | requires is_invocable_using 149 | : fptr_( 150 | [](storage, _param_t... args) noexcept(noex) -> R { 151 | return std23::invoke_r( 152 | f, static_cast(args)...); 153 | }) 154 | { 155 | using F = decltype(f); 156 | if constexpr (std::is_pointer_v or std::is_member_pointer_v) 157 | static_assert(f != nullptr, "NTTP callable must be usable"); 158 | } 159 | 160 | template> 161 | constexpr function_ref(nontype_t, U &&obj) noexcept 162 | requires(not std::is_rvalue_reference_v and 163 | is_invocable_using>) 164 | : fptr_( 165 | [](storage this_, _param_t... args) noexcept(noex) -> R 166 | { 167 | cvref obj = *get(this_); 168 | return std23::invoke_r( 169 | f, obj, static_cast(args)...); 170 | }), 171 | obj_(std::addressof(obj)) 172 | { 173 | using F = decltype(f); 174 | if constexpr (std::is_pointer_v or std::is_member_pointer_v) 175 | static_assert(f != nullptr, "NTTP callable must be usable"); 176 | } 177 | 178 | template 179 | constexpr function_ref(nontype_t, cv *obj) noexcept 180 | requires is_invocable_using 181 | : fptr_( 182 | [](storage this_, _param_t... args) noexcept(noex) -> R 183 | { 184 | return std23::invoke_r( 185 | f, get>(this_), 186 | static_cast(args)...); 187 | }), 188 | obj_(obj) 189 | { 190 | using F = decltype(f); 191 | if constexpr (std::is_pointer_v or std::is_member_pointer_v) 192 | static_assert(f != nullptr, "NTTP callable must be usable"); 193 | 194 | if constexpr (std::is_member_pointer_v) 195 | assert(obj != nullptr && "must reference an object"); 196 | } 197 | 198 | constexpr R operator()(Args... args) const noexcept(noex) 199 | { 200 | return fptr_(obj_, std::forward(args)...); 201 | } 202 | }; 203 | 204 | template requires std::is_function_v 205 | function_ref(F *) -> function_ref; 206 | 207 | template 208 | function_ref(nontype_t) -> function_ref<_adapt_signature_t>; 209 | 210 | template 211 | function_ref(nontype_t, T &&) 212 | -> function_ref<_drop_first_arg_to_invoke_t>; 213 | 214 | } // namespace std23 215 | 216 | #endif 217 | -------------------------------------------------------------------------------- /include/std23/move_only_function.h: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDE_STD23_MOVE__ONLY__FUNCTION 2 | #define INCLUDE_STD23_MOVE__ONLY__FUNCTION 3 | 4 | #include "__functional_base.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace std23 11 | { 12 | 13 | template struct _cv_fn_sig 14 | {}; 15 | 16 | template struct _cv_fn_sig 17 | { 18 | using function = R(Args...); 19 | template using cv = T; 20 | }; 21 | 22 | template struct _cv_fn_sig 23 | { 24 | using function = R(Args...); 25 | template using cv = T const; 26 | }; 27 | 28 | template struct _ref_quals_fn_sig : _cv_fn_sig 29 | { 30 | template using ref = T; 31 | }; 32 | 33 | template 34 | struct _ref_quals_fn_sig : _cv_fn_sig 35 | { 36 | template using ref = T &; 37 | }; 38 | 39 | template 40 | struct _ref_quals_fn_sig : _cv_fn_sig 41 | { 42 | template using ref = T &; 43 | }; 44 | 45 | template 46 | struct _ref_quals_fn_sig : _cv_fn_sig 47 | { 48 | template using ref = T &&; 49 | }; 50 | 51 | template 52 | struct _ref_quals_fn_sig : _cv_fn_sig 53 | { 54 | template using ref = T &&; 55 | }; 56 | 57 | template struct _noex_traits 58 | { 59 | static constexpr bool is_noexcept = V; 60 | }; 61 | 62 | template 63 | struct _full_fn_sig : _ref_quals_fn_sig, _noex_traits 64 | {}; 65 | 66 | template 67 | struct _full_fn_sig : _ref_quals_fn_sig, 68 | _noex_traits 69 | {}; 70 | 71 | template 72 | struct _full_fn_sig : _ref_quals_fn_sig, 73 | _noex_traits 74 | {}; 75 | 76 | template 77 | struct _full_fn_sig : _ref_quals_fn_sig, 78 | _noex_traits 79 | {}; 80 | 81 | template 82 | struct _full_fn_sig 83 | : _ref_quals_fn_sig, _noex_traits 84 | {}; 85 | 86 | template 87 | struct _full_fn_sig 88 | : _ref_quals_fn_sig, _noex_traits 89 | {}; 90 | 91 | template 92 | struct _full_fn_sig 93 | : _ref_quals_fn_sig, _noex_traits 94 | {}; 95 | 96 | constexpr inline struct 97 | { 98 | constexpr auto operator()(auto &&rhs) const 99 | { 100 | return new auto(decltype(rhs)(rhs)); 101 | } 102 | 103 | constexpr auto operator()(auto *rhs) const noexcept { return rhs; } 104 | 105 | template 106 | constexpr auto operator()(std::reference_wrapper rhs) const noexcept 107 | { 108 | return std::addressof(rhs.get()); 109 | } 110 | 111 | } _take_reference; 112 | 113 | template 114 | constexpr auto _build_reference = [](auto &&...args) 115 | { return new T(decltype(args)(args)...); }; 116 | 117 | template 118 | constexpr auto _build_reference = [](auto &&...args) noexcept -> T * 119 | { return {decltype(args)(args)...}; }; 120 | 121 | template 122 | constexpr auto _build_reference> = 123 | [](auto &rhs) noexcept { return std::addressof(rhs); }; 124 | 125 | struct _move_only_pointer 126 | { 127 | union value_type 128 | { 129 | void *p_ = nullptr; 130 | void const *cp_; 131 | void (*fp_)(); 132 | } val; 133 | 134 | _move_only_pointer() = default; 135 | _move_only_pointer(_move_only_pointer const &) = delete; 136 | _move_only_pointer &operator=(_move_only_pointer const &) = delete; 137 | 138 | constexpr _move_only_pointer(_move_only_pointer &&other) noexcept 139 | : val(std::exchange(other.val, {})) 140 | {} 141 | 142 | template requires std::is_object_v 143 | constexpr explicit _move_only_pointer(T *p) noexcept : val{.p_ = p} 144 | {} 145 | 146 | template requires std::is_object_v 147 | constexpr explicit _move_only_pointer(T const *p) noexcept : val{.cp_ = p} 148 | {} 149 | 150 | template requires std::is_function_v 151 | constexpr explicit _move_only_pointer(T *p) noexcept 152 | : val{.fp_ = reinterpret_cast(p)} 153 | {} 154 | 155 | template requires std::is_object_v 156 | constexpr _move_only_pointer &operator=(T *p) noexcept 157 | { 158 | val.p_ = p; 159 | return *this; 160 | } 161 | 162 | template requires std::is_object_v 163 | constexpr _move_only_pointer &operator=(T const *p) noexcept 164 | { 165 | val.cp_ = p; 166 | return *this; 167 | } 168 | 169 | template requires std::is_function_v 170 | constexpr _move_only_pointer &operator=(T *p) noexcept 171 | { 172 | val.fp_ = reinterpret_cast(p); 173 | return *this; 174 | } 175 | 176 | constexpr _move_only_pointer &operator=(_move_only_pointer &&other) noexcept 177 | { 178 | val = std::exchange(other.val, {}); 179 | return *this; 180 | } 181 | }; 182 | 183 | template struct _callable_trait 184 | { 185 | using handle = _move_only_pointer::value_type; 186 | 187 | typedef auto call_t(handle, Args...) noexcept(noex) -> R; 188 | typedef void destroy_t(handle) noexcept; 189 | 190 | struct vtable 191 | { 192 | call_t *call = 0; 193 | destroy_t *destroy = [](handle) noexcept {}; 194 | }; 195 | 196 | static inline constinit vtable const abstract_base; 197 | 198 | template constexpr static auto get(handle val) 199 | { 200 | if constexpr (std::is_const_v) 201 | return static_cast(val.cp_); 202 | else if constexpr (std::is_object_v) 203 | return static_cast(val.p_); 204 | else 205 | return reinterpret_cast(val.fp_); 206 | } 207 | 208 | // See also: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=71954 209 | template class quals> 210 | static inline constinit vtable const callable_target{ 211 | .call = [](handle this_, Args... args) noexcept(noex) -> R 212 | { 213 | if constexpr (std::is_lvalue_reference_v or std::is_pointer_v) 214 | { 215 | using Tp = std::remove_reference_t>; 216 | return std23::invoke_r(*get(this_), 217 | static_cast(args)...); 218 | } 219 | else 220 | { 221 | using Fp = quals::type; 222 | return std23::invoke_r(static_cast(*get(this_)), 223 | static_cast(args)...); 224 | } 225 | }, 226 | .destroy = 227 | [](handle this_) noexcept 228 | { 229 | if constexpr (not std::is_lvalue_reference_v and 230 | not std::is_pointer_v) 231 | delete get(this_); 232 | }, 233 | }; 234 | 235 | template 236 | static inline constinit vtable const unbound_callable_target{ 237 | .call = [](handle, Args... args) noexcept(noex) -> R 238 | { return std23::invoke_r(f, static_cast(args)...); }, 239 | }; 240 | 241 | template class quals> 242 | static inline constinit vtable const bound_callable_target{ 243 | .call = [](handle this_, Args... args) noexcept(noex) -> R 244 | { 245 | if constexpr (std::is_pointer_v) 246 | { 247 | using Tp = std::remove_pointer_t; 248 | return std23::invoke_r(f, get(this_), 249 | static_cast(args)...); 250 | } 251 | else if constexpr (std::is_lvalue_reference_v) 252 | { 253 | using Tp = std::remove_reference_t; 254 | return std23::invoke_r(f, *get(this_), 255 | static_cast(args)...); 256 | } 257 | else 258 | { 259 | using Fp = quals::type; 260 | return std23::invoke_r(f, static_cast(*get(this_)), 261 | static_cast(args)...); 262 | } 263 | }, 264 | .destroy = 265 | [](handle this_) noexcept 266 | { 267 | if constexpr (not std::is_lvalue_reference_v and 268 | not std::is_pointer_v) 269 | delete get(this_); 270 | }, 271 | }; 272 | 273 | template 274 | static inline constinit vtable const boxed_callable_target{ 275 | .call = [](handle this_, Args... args) noexcept(noex) -> R { 276 | return std23::invoke_r(f, get(this_), 277 | static_cast(args)...); 278 | }, 279 | .destroy = 280 | [](handle this_) noexcept 281 | { 282 | using D = std::unique_ptr::deleter_type; 283 | static_assert(std::is_trivially_default_constructible_v); 284 | if (auto p = get(this_)) 285 | D()(p); 286 | }, 287 | }; 288 | }; 289 | 290 | template class Primary> 291 | inline constexpr bool _is_specialization_of = false; 292 | 293 | template class Primary, class... Args> 294 | inline constexpr bool _is_specialization_of, Primary> = true; 295 | 296 | template class Primary> 297 | inline constexpr bool _does_not_specialize = 298 | not _is_specialization_of, Primary>; 299 | 300 | template::function> 301 | class move_only_function; 302 | 303 | template 304 | class move_only_function 305 | { 306 | using signature = _full_fn_sig; 307 | 308 | template using cv = signature::template cv; 309 | template using ref = signature::template ref; 310 | 311 | static constexpr bool noex = signature::is_noexcept; 312 | static constexpr bool is_const = std::is_same_v, void const>; 313 | static constexpr bool is_lvalue_only = std::is_same_v, int &>; 314 | static constexpr bool is_rvalue_only = std::is_same_v, int &&>; 315 | 316 | template using cvref = ref>; 317 | template 318 | struct inv_quals_f 319 | : std::conditional, cv &> 320 | {}; 321 | template using inv_quals = inv_quals_f::type; 322 | 323 | template 324 | static constexpr bool is_invocable_using = 325 | std::conditional_t, 326 | std::is_invocable_r>::value; 327 | 328 | template 329 | static constexpr bool is_callable_from = 330 | is_invocable_using> and is_invocable_using>; 331 | 332 | template 333 | static constexpr bool is_callable_as_if_from = 334 | is_invocable_using>; 335 | 336 | using trait = _callable_trait...>; 337 | using vtable = trait::vtable; 338 | 339 | std::reference_wrapper vtbl_ = trait::abstract_base; 340 | _move_only_pointer obj_; 341 | 342 | public: 343 | using result_type = R; 344 | 345 | move_only_function() = default; 346 | move_only_function(nullptr_t) noexcept : move_only_function() {} 347 | 348 | template> 349 | move_only_function(F &&f) noexcept( 350 | std::is_nothrow_invocable_v) 351 | requires _is_not_self and 352 | _does_not_specialize and 353 | is_callable_from and std::is_constructible_v 354 | { 355 | if constexpr (_looks_nullable_to) 356 | { 357 | if (f == nullptr) 358 | return; 359 | } 360 | 361 | vtbl_ = trait::template callable_target, 362 | inv_quals_f>; 363 | obj_ = _take_reference(std::forward(f)); 364 | } 365 | 366 | template 367 | move_only_function(nontype_t) noexcept 368 | requires is_invocable_using 369 | : vtbl_(trait::template unbound_callable_target) 370 | {} 371 | 372 | template> 373 | move_only_function(nontype_t, T &&x) noexcept( 374 | std::is_nothrow_invocable_v) 375 | requires is_callable_as_if_from and 376 | std::is_constructible_v 377 | : vtbl_(trait::template bound_callable_target< 378 | f, std::unwrap_ref_decay_t, inv_quals_f>), 379 | obj_(_take_reference(std::forward(x))) 380 | {} 381 | 382 | template 383 | move_only_function(nontype_t, std::unique_ptr &&x) noexcept 384 | requires std::is_base_of_v and is_callable_as_if_from 385 | : vtbl_(trait::template boxed_callable_target), obj_(x.release()) 386 | {} 387 | 388 | template 389 | explicit move_only_function(in_place_type_t, Inits &&...inits) noexcept( 390 | std::is_nothrow_invocable_v), Inits...>) 391 | requires is_callable_from and std::is_constructible_v 392 | : vtbl_(trait::template callable_target, 393 | inv_quals_f>), 394 | obj_(_build_reference(std::forward(inits)...)) 395 | { 396 | static_assert(std::is_same_v, T>); 397 | } 398 | 399 | template 400 | explicit move_only_function(in_place_type_t, initializer_list ilist, 401 | Inits &&...inits) noexcept( // 402 | std::is_nothrow_invocable_v), 403 | decltype((ilist)), Inits...>) 404 | requires is_callable_from and 405 | std::is_constructible_v 406 | : vtbl_(trait::template callable_target, 407 | inv_quals_f>), 408 | obj_(_build_reference(ilist, std::forward(inits)...)) 409 | { 410 | static_assert(std::is_same_v, T>); 411 | } 412 | 413 | template 414 | explicit move_only_function(nontype_t, in_place_type_t, 415 | Inits &&...inits) noexcept( // 416 | std::is_nothrow_invocable_v), Inits...>) 417 | requires is_callable_as_if_from and 418 | std::is_constructible_v 419 | : vtbl_(trait::template bound_callable_target< 420 | f, std::unwrap_reference_t, inv_quals_f>), 421 | obj_(_build_reference(std::forward(inits)...)) 422 | { 423 | static_assert(std::is_same_v, T>); 424 | } 425 | 426 | template 427 | explicit move_only_function(nontype_t t, 428 | in_place_type_t>, 429 | Inits &&...inits) noexcept( // 430 | std::is_nothrow_constructible_v, Inits...>) 431 | requires std::is_base_of_v and is_callable_as_if_from and 432 | std::is_constructible_v, 433 | Inits...> 434 | : move_only_function(t, 435 | std::unique_ptr(std::forward(inits)...)) 436 | {} 437 | 438 | template 439 | explicit move_only_function(nontype_t, in_place_type_t, 440 | initializer_list ilist, 441 | Inits &&...inits) noexcept( // 442 | std::is_nothrow_invocable_v), 443 | decltype((ilist)), Inits...>) 444 | requires is_callable_as_if_from and 445 | std::is_constructible_v 446 | : vtbl_(trait::template bound_callable_target< 447 | f, std::unwrap_reference_t, inv_quals_f>), 448 | obj_(_build_reference(ilist, std::forward(inits)...)) 449 | { 450 | static_assert(std::is_same_v, T>); 451 | } 452 | 453 | move_only_function(move_only_function &&) = default; 454 | move_only_function &operator=(move_only_function &&) = default; 455 | 456 | void swap(move_only_function &other) noexcept 457 | { 458 | std::swap(*this, other); 459 | } 460 | 461 | friend void swap(move_only_function &lhs, move_only_function &rhs) noexcept 462 | { 463 | lhs.swap(rhs); 464 | } 465 | 466 | ~move_only_function() { vtbl_.get().destroy(obj_.val); } 467 | 468 | explicit operator bool() const noexcept 469 | { 470 | return &vtbl_.get() != &trait::abstract_base; 471 | } 472 | 473 | friend bool operator==(move_only_function const &f, nullptr_t) noexcept 474 | { 475 | return !f; 476 | } 477 | 478 | R operator()(Args... args) noexcept(noex) 479 | requires(!is_const and !is_lvalue_only and !is_rvalue_only) 480 | { 481 | return vtbl_.get().call(obj_.val, std::forward(args)...); 482 | } 483 | 484 | R operator()(Args... args) const noexcept(noex) 485 | requires(is_const and !is_lvalue_only and !is_rvalue_only) 486 | { 487 | return vtbl_.get().call(obj_.val, std::forward(args)...); 488 | } 489 | 490 | R operator()(Args... args) &noexcept(noex) 491 | requires(!is_const and is_lvalue_only and !is_rvalue_only) 492 | { 493 | return vtbl_.get().call(obj_.val, std::forward(args)...); 494 | } 495 | 496 | R operator()(Args... args) const &noexcept(noex) 497 | requires(is_const and is_lvalue_only and !is_rvalue_only) 498 | { 499 | return vtbl_.get().call(obj_.val, std::forward(args)...); 500 | } 501 | 502 | R operator()(Args... args) &&noexcept(noex) 503 | requires(!is_const and !is_lvalue_only and is_rvalue_only) 504 | { 505 | return vtbl_.get().call(obj_.val, std::forward(args)...); 506 | } 507 | 508 | R operator()(Args... args) const &&noexcept(noex) 509 | requires(is_const and !is_lvalue_only and is_rvalue_only) 510 | { 511 | return vtbl_.get().call(obj_.val, std::forward(args)...); 512 | } 513 | }; 514 | 515 | } // namespace std23 516 | 517 | #endif 518 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(kris-ut INTERFACE) 2 | target_sources(kris-ut INTERFACE "include/boost/ut.hpp") 3 | target_include_directories(kris-ut 4 | INTERFACE $ 5 | ) 6 | target_compile_definitions(kris-ut INTERFACE BOOST_UT_DISABLE_MODULE) 7 | 8 | add_subdirectory(function_ref) 9 | add_subdirectory(move_only_function) 10 | add_subdirectory(function) 11 | -------------------------------------------------------------------------------- /tests/function/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(run-function) 2 | target_sources(run-function PRIVATE 3 | "main.cpp" 4 | "test_basics.cpp" 5 | "common_callables.h" 6 | "common_callables.cpp" 7 | "test_value_semantics.cpp" 8 | "test_ctad.cpp" 9 | "test_reference_semantics.cpp" 10 | "test_nullable.cpp" 11 | "test_nontype.cpp" 12 | ) 13 | target_link_libraries(run-function PRIVATE nontype_functional kris-ut) 14 | set_target_properties(run-function PROPERTIES OUTPUT_NAME run) 15 | add_test(function run) 16 | -------------------------------------------------------------------------------- /tests/function/common_callables.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | int f() 4 | { 5 | return BODYN(0); 6 | } 7 | 8 | int f_good() noexcept 9 | { 10 | return BODYN(1); 11 | } 12 | -------------------------------------------------------------------------------- /tests/function/common_callables.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "std23/function.h" 4 | 5 | #include 6 | 7 | using namespace boost::ut; 8 | 9 | using std23::function; 10 | using std23::nontype; 11 | using std23::nontype_t; 12 | 13 | #ifdef _MSC_VER 14 | #define BODYN(n) ((::boost::ut::log << __FUNCSIG__ << '\n'), n) 15 | #else 16 | #define BODYN(n) ((::boost::ut::log << __PRETTY_FUNCTION__ << '\n'), n) 17 | #endif 18 | 19 | int f(); 20 | int f_good() noexcept; 21 | -------------------------------------------------------------------------------- /tests/function/main.cpp: -------------------------------------------------------------------------------- 1 | int main() 2 | {} -------------------------------------------------------------------------------- /tests/function/test_basics.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | suite basics = [] 4 | { 5 | using namespace bdd; 6 | 7 | "basics"_test = [] 8 | { 9 | given("a default constructed function object") = [] 10 | { 11 | function fn; 12 | 13 | then("it is empty") = [&] 14 | { 15 | expect(!fn); 16 | expect(fn == nullptr); 17 | expect(nullptr == fn); 18 | }; 19 | 20 | when("called") = [&] 21 | { expect(throws(fn)); }; 22 | }; 23 | 24 | given("a function object initialized from function") = [] 25 | { 26 | function fn = f; 27 | 28 | then("it is not empty") = [&] 29 | { 30 | expect(bool(fn)); 31 | expect(fn != nullptr); 32 | expect(nullptr != fn); 33 | }; 34 | 35 | when("called") = [&] { expect(fn() == 0_i); }; 36 | }; 37 | 38 | given("a function object initialized from closure") = [] 39 | { 40 | function fn = [] { return 42; }; 41 | 42 | then("it is not empty") = [&] 43 | { 44 | expect(bool(fn)); 45 | expect(fn != nullptr); 46 | expect(nullptr != fn); 47 | }; 48 | 49 | when("called") = [&] { expect(fn() == 42_i); }; 50 | }; 51 | }; 52 | }; -------------------------------------------------------------------------------- /tests/function/test_ctad.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | template 4 | inline constexpr bool deduction_enabled = type_traits::is_valid( 5 | [](auto x) -> decltype(void(function(std::move(x)))) {}); 6 | 7 | void test_ctad() 8 | { 9 | { 10 | function fn = [i = 0] { return i; }; 11 | static_assert(std::is_same_v>); 12 | static_assert(std::is_same_v); 13 | } 14 | 15 | { 16 | function fn = [i = 0](int &&, int const) mutable 17 | { return (void)i, "lit"; }; 18 | static_assert( 19 | std::is_same_v>); 20 | static_assert(std::is_same_v); 21 | } 22 | 23 | { 24 | function fn = f_good; 25 | static_assert(std::is_same_v>, 26 | "[conv.fctptr]/1"); 27 | static_assert(std::is_same_v); 28 | } 29 | 30 | { 31 | function fn = [i = 0]() noexcept { return i; }; 32 | static_assert(std::is_same_v>, 33 | "[func.wrap.func.con]/16"); 34 | static_assert(std::is_same_v); 35 | } 36 | 37 | struct lvalue_only 38 | { 39 | void operator()(double &) & {} 40 | }; 41 | 42 | struct rvalue_only 43 | { 44 | void operator()(double &) && {} 45 | }; 46 | 47 | { 48 | lvalue_only lv; 49 | function fn = lv; 50 | static_assert(std::is_same_v>); 51 | static_assert(std::is_same_v); 52 | } 53 | 54 | static_assert(deduction_enabled); 55 | static_assert(not deduction_enabled, 56 | "function requires F to be Lvalue-Callable"); 57 | } -------------------------------------------------------------------------------- /tests/function/test_nontype.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | #include 4 | #include 5 | 6 | namespace 7 | { 8 | 9 | struct A 10 | { 11 | A() = default; 12 | A(int a) : val{a} {} 13 | 14 | int add(int a) 15 | { 16 | val += a; 17 | return val; 18 | } 19 | 20 | void set(int a) &noexcept { val = a; } 21 | 22 | int val = 0; 23 | }; 24 | 25 | int neg(A const &a) noexcept 26 | { 27 | return -a.val; 28 | } 29 | 30 | } // namespace 31 | 32 | suite nttp_callable = [] 33 | { 34 | using namespace bdd; 35 | 36 | feature("type-erase a bound instance method") = [] 37 | { 38 | given("an object without call operator") = [] 39 | { 40 | A obj; 41 | 42 | when("binding a member function to a reference") = [=]() mutable 43 | { 44 | function fn = {nontype<&A::set>, std::ref(obj)}; 45 | 46 | then("you can observe reference semantics") = [&] 47 | { 48 | fn(11); 49 | expect(obj.val == 11_i); 50 | }; 51 | 52 | when("rebinding a different memfn with a pointer") = [=] 53 | { 54 | auto copy = obj; 55 | mut(fn) = {nontype<&A::add>, ©}; 56 | 57 | then("you can observe reference semantics too") = [&] 58 | { 59 | fn(6); 60 | expect(copy.val == 17_i); 61 | }; 62 | }; 63 | 64 | when("rebinding the same memfn and an object") = [=] 65 | { 66 | mut(fn) = {nontype<&A::add>, obj}; 67 | 68 | then("you can observe value semantics instead") = [&] 69 | { 70 | fn(6); 71 | expect(obj.val == 11_i); 72 | }; 73 | }; 74 | }; 75 | 76 | when("binding a free function to a reference") = [=]() mutable 77 | { 78 | function fn{nontype, std::cref(obj)}; 79 | 80 | then("you can observe reference semantics") = [&] 81 | { 82 | expect(fn() == 0_i); 83 | 84 | obj.val = -3; 85 | expect(fn() == 3_i); 86 | }; 87 | 88 | when("rebinding a pointer-to-data-member to a reference") = [=] 89 | { 90 | auto copy = obj; 91 | mut(fn) = {nontype<&A::val>, std::ref(copy)}; 92 | 93 | then("you can observe reference semantics too") = [&] 94 | { 95 | expect(fn() == -3_i); 96 | 97 | copy.val = 15; 98 | expect(fn() == 15_i); 99 | }; 100 | }; 101 | 102 | when("rebinding a PMD to a pointer-like object") = [=] 103 | { 104 | then("you may get reference semantics if copies of the " 105 | "object share states") = [&] 106 | { 107 | auto ptr = std::make_shared(6); 108 | mut(fn) = {nontype<&A::val>, ptr}; 109 | 110 | expect(fn() == 6_i); 111 | 112 | ptr->val = 17; 113 | expect(fn() == 17_i); 114 | }; 115 | 116 | then("you may get value semantics if copies of the object " 117 | "are distinct") = [&] 118 | { 119 | auto opt = std::make_optional(6); 120 | mut(fn) = {nontype<&A::val>, opt}; 121 | 122 | expect(fn() == 6_i); 123 | 124 | opt->val = 17; 125 | expect(fn() == 6_i); 126 | }; 127 | }; 128 | 129 | when("rebinding a stateless closure to a copy") = [=]() mutable 130 | { 131 | obj = {}; 132 | fn = {nontype<[](A &obj) { return ++obj.val; }>, obj}; 133 | 134 | then("you can observe value semantics") = [&] 135 | { 136 | expect(fn() == 1_i); 137 | expect(obj.val == 0_i); 138 | expect(fn() == 2_i); 139 | expect(obj.val == 0_i); 140 | }; 141 | }; 142 | 143 | test("non-member does not dereference pointers") = [=]() mutable 144 | { 145 | obj = {}; 146 | fn = {nontype<[](A *obj) { return ++obj->val; }>, &obj}; 147 | 148 | then("observe reference semantics as requested") = [&] 149 | { 150 | expect(fn() == 1_i); 151 | expect(obj.val == 1_i); 152 | expect(fn() == 2_i); 153 | expect(obj.val == 2_i); 154 | }; 155 | }; 156 | }; 157 | }; 158 | }; 159 | 160 | feature("type-erase an unbound instance method") = [] 161 | { 162 | given("a function pointer NTTP") = [obj = A{6}] 163 | { 164 | function fn = nontype; 165 | 166 | then("the signature is copied") = [&] 167 | { 168 | expect(fn({-3}) == 3_i); 169 | expect(fn(obj) == -6_i); 170 | }; 171 | 172 | then("you can rebind a pointer-to-member") = [=] 173 | { 174 | mut(fn) = nontype<&A::val>; 175 | 176 | expect(fn({-3}) == -3_i); 177 | expect(fn(obj) == 6_i); 178 | }; 179 | }; 180 | }; 181 | }; 182 | 183 | using T = function; 184 | 185 | static_assert(std::is_constructible_v, A>); 186 | static_assert(std::is_constructible_v, A const>, 187 | "function does not call the source object"); 188 | static_assert(not std::is_invocable_v); 189 | 190 | static_assert(std::is_nothrow_constructible_v, A *>); 191 | static_assert(std::is_nothrow_constructible_v, 192 | std::reference_wrapper>); 193 | static_assert(not std::is_constructible_v, A const *>, 194 | "...unless we stored a pointer"); 195 | static_assert(not std::is_constructible_v, 196 | std::reference_wrapper>, 197 | "...or reference_wrapper"); 198 | 199 | using V = function; 200 | 201 | inline constexpr auto pure = [](A &, int) { return 0; }; 202 | static_assert(std::is_constructible_v, A const &>, 203 | "function has its own copy of target object"); 204 | static_assert(not std::is_invocable_v); 205 | 206 | inline constexpr auto rvalue_only = [](A &&, int) { return 0; }; 207 | static_assert(not std::is_constructible_v, A>, 208 | "function's target object is used as an lvalue"); 209 | static_assert(std::is_invocable_v); 210 | 211 | static_assert(std::is_constructible_v, A &>); 212 | static_assert(std::is_constructible_v, A *>); 213 | static_assert(std::is_constructible_v, A &>); 214 | static_assert(not std::is_constructible_v, A *>, 215 | "do not call a closure through a pointer to first arg"); 216 | 217 | using U = function; 218 | 219 | static_assert(std::is_constructible_v, A const>); 220 | static_assert(std::is_constructible_v, A const *>); 221 | static_assert(std::is_constructible_v, A const>); 222 | static_assert(not std::is_constructible_v, A const *>, 223 | "do not call a free function through a pointer to first arg"); 224 | 225 | using W = function; 226 | 227 | static_assert(std::is_nothrow_constructible_v>); 228 | static_assert(std::is_nothrow_constructible_v>, 229 | "unbounded cases are always noexcept"); 230 | static_assert(std::is_nothrow_assignable_v>); 231 | static_assert(std::is_nothrow_assignable_v>); 232 | -------------------------------------------------------------------------------- /tests/function/test_nullable.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | suite nullable = [] 4 | { 5 | using namespace bdd; 6 | 7 | feature("function recognizes certain nullable callable objects") = [&] 8 | { 9 | given("a non-empty function object") = [&] 10 | { 11 | function fn = f; 12 | expect(fn != nullptr); 13 | 14 | when("it is assigned from a null function pointer") = [=]() mutable 15 | { 16 | decltype(f) *p = {}; 17 | fn = p; 18 | 19 | then("it becomes empty") = [&] 20 | { 21 | expect(!fn); 22 | expect(throws(fn)); 23 | }; 24 | }; 25 | }; 26 | 27 | given("a function initialized from a null pointer to member") = [] 28 | { 29 | struct A 30 | { 31 | int data = 99; 32 | } a; 33 | int A::*pm = {}; 34 | function fn = pm; 35 | 36 | then("it is empty") = [&] 37 | { 38 | expect(!fn); 39 | expect(throws(std::bind_front(fn, a))); 40 | }; 41 | 42 | when("it is assigned from a PM constant") = [=]() mutable 43 | { 44 | fn = &A::data; 45 | 46 | then("it is not empty") = [&] 47 | { 48 | expect(bool(fn)); 49 | expect(fn(a) == 99_i); 50 | }; 51 | }; 52 | }; 53 | 54 | given("an empty function object") = [] 55 | { 56 | function fn; 57 | expect(!fn); 58 | 59 | when("it is assigned from an empty std::function") = [=]() mutable 60 | { 61 | fn = std::function(); 62 | 63 | then("it is not empty but behaves like empty") = [&] 64 | { 65 | expect(fn != nullptr); 66 | expect(throws(fn)); 67 | }; 68 | }; 69 | 70 | when("it initialises function of a different signature") = [=] 71 | { 72 | function fn2 = fn; 73 | 74 | then("that function object is empty") = [&] 75 | { 76 | expect(!fn2); 77 | expect(throws(fn2)); 78 | }; 79 | }; 80 | }; 81 | }; 82 | }; -------------------------------------------------------------------------------- /tests/function/test_reference_semantics.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | suite reference_semantics = [] 4 | { 5 | using namespace bdd; 6 | 7 | feature("function supports reference_wrapper") = [] 8 | { 9 | struct counter 10 | { 11 | int n = 0; 12 | int operator()() { return ++n; } 13 | }; 14 | 15 | given("a stateful callable object") = [] 16 | { 17 | counter obj; 18 | 19 | when("passing it to function via refwrap") = [&] 20 | { 21 | function fn = std::ref(obj); 22 | 23 | then("calling function affects the original object") = [&] 24 | { 25 | expect(fn() == 1_i); 26 | expect(fn() == 2_i); 27 | 28 | expect(obj.n == 2_i); 29 | }; 30 | 31 | then("a copy of the function also references the source") = 32 | [fn, &obj] 33 | { 34 | expect(fn() == 3_i); 35 | expect(obj.n == 3_i); 36 | }; 37 | 38 | when("reseting the function with a copy of the source") = [&] 39 | { 40 | fn = obj; 41 | 42 | then("they maintain disjointed states") = [&] 43 | { 44 | obj.n = 10; 45 | 46 | expect(fn() == 4_i); 47 | }; 48 | 49 | when("the function reattaches to the source") = [&] 50 | { 51 | fn = std::ref(obj); 52 | 53 | then("they share states again") = [&] 54 | { 55 | expect(obj() == 11_i); 56 | expect(fn() == 12_i); 57 | }; 58 | }; 59 | }; 60 | }; 61 | }; 62 | }; 63 | }; 64 | 65 | using T = function; 66 | using X = decltype([](int) {}); 67 | 68 | static_assert(std::is_constructible_v); 69 | static_assert(std::is_constructible_v>); 70 | static_assert(std::is_assignable_v); 71 | static_assert(std::is_nothrow_assignable_v>); 72 | 73 | // extension 74 | static_assert(std::is_nothrow_constructible_v>); 75 | static_assert(std::is_nothrow_constructible_v); 76 | static_assert(std::is_nothrow_constructible_v); 77 | static_assert(std::is_nothrow_assignable_v); 78 | static_assert(std::is_nothrow_assignable_v); 79 | -------------------------------------------------------------------------------- /tests/function/test_value_semantics.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | #include 4 | #include 5 | 6 | static void noop(int &) 7 | {} 8 | 9 | inline constexpr auto behaves_like_noop = [](auto &f) 10 | { 11 | int x = 0; 12 | f(x); 13 | return x == 0_i; 14 | }; 15 | 16 | inline constexpr auto behaves_like_null = [](auto &f) 17 | { 18 | int x = 0; 19 | return throws(std::bind_front(f, std::ref(x))); 20 | }; 21 | 22 | inline constexpr auto behaves_like_nonpure = [](auto &f) 23 | { 24 | int x = 0; 25 | f(x); 26 | return x == 1_i; 27 | }; 28 | 29 | suite value_semantics = [] 30 | { 31 | using namespace bdd; 32 | 33 | feature("function is copyable") = [] 34 | { 35 | struct counter 36 | { 37 | int n = 0; 38 | int operator()() { return n++; } 39 | }; 40 | 41 | given("a stateful callable object") = [] 42 | { 43 | counter cf; 44 | 45 | when("a function object is initialized from it") = [&] 46 | { 47 | function fn = cf; 48 | 49 | then("it owns a copy of that callable object") = [&] 50 | { 51 | expect(fn() == 0_i); 52 | expect(fn() == 1_i); 53 | expect(fn() == 2_i); 54 | expect(cf() == 0_i); 55 | expect(cf() == 1_i); 56 | expect(fn() == 3_i); 57 | }; 58 | 59 | when("a copy of the function object is obtained") = [&] 60 | { 61 | function fn2 = fn; 62 | 63 | then("we get a distinct copy") = [&] 64 | { 65 | expect(fn() == 4_i); 66 | expect(fn() == 5_i); 67 | expect(fn2() == 4_i); 68 | }; 69 | 70 | when("the original is set to empty") = [&] 71 | { 72 | fn = nullptr; 73 | 74 | then("the copy is unaffected") = [&] 75 | { 76 | expect(!fn); 77 | expect(bool(fn2)); 78 | }; 79 | 80 | when("assign from the copy") = [&] 81 | { 82 | fn = fn2; 83 | 84 | then("we continue from the new state") = [&] 85 | { 86 | expect(fn() == 5_i); 87 | expect(fn() == 6_i); 88 | expect(fn2() == 5_i); 89 | }; 90 | }; 91 | }; 92 | }; 93 | }; 94 | }; 95 | }; 96 | 97 | feature("function is movable") = [] 98 | { 99 | auto closure = [n = 1.](int x) mutable { return n *= x; }; 100 | 101 | given("a function object that owns a stateful closure") = [&] 102 | { 103 | function fn = closure; 104 | 105 | when("its state updates") = [&] 106 | { 107 | expect(fn(2) == 2.0_d); 108 | expect(fn(3) == 6.0_d); 109 | 110 | then("it can be reset by move assignment") = [&] 111 | { 112 | function other = closure; 113 | fn = std::move(other); 114 | 115 | expect(fn(3) == 3.0_d); 116 | 117 | when("it is moved into a new object") = [&] 118 | { 119 | auto fn2{std::move(fn)}; 120 | 121 | then("the object inherits its states") = [&] 122 | { 123 | expect(bool(fn2)); 124 | expect(fn2(3) == 9.0_d); 125 | }; 126 | }; 127 | }; 128 | }; 129 | }; 130 | }; 131 | 132 | feature("function is swappable") = [] 133 | { 134 | given("three function objects") = [] 135 | { 136 | function a = noop; 137 | function b = nullptr; 138 | function c = [](int &out) noexcept { out += 1; }; 139 | 140 | then("they can be self swapped") = [=]() mutable 141 | { 142 | swap(a, a); 143 | expect(behaves_like_noop(a)); 144 | 145 | swap(b, b); 146 | expect(behaves_like_null(b)); 147 | 148 | swap(c, c); 149 | expect(behaves_like_nonpure(c)); 150 | }; 151 | 152 | when("swapping a with b") = [=]() mutable 153 | { 154 | swap(a, b); 155 | 156 | expect(behaves_like_null(a)); 157 | expect(behaves_like_noop(b)); 158 | }; 159 | 160 | when("swapping b with c") = [=]() mutable 161 | { 162 | swap(b, c); 163 | 164 | expect(behaves_like_nonpure(b)); 165 | expect(behaves_like_null(c)); 166 | }; 167 | 168 | when("swapping a with c") = [=]() mutable 169 | { 170 | swap(a, c); 171 | 172 | expect(behaves_like_nonpure(a)); 173 | expect(behaves_like_noop(c)); 174 | }; 175 | 176 | when("rotate them") = [&] 177 | { 178 | std::array ls{a, b, c}; 179 | auto first = begin(ls); 180 | std::rotate(first, std::next(first), end(ls)); 181 | 182 | expect(behaves_like_null(ls[0])); // b 183 | expect(behaves_like_nonpure(ls[1])); // c 184 | expect(behaves_like_noop(ls[2])); // a 185 | }; 186 | }; 187 | }; 188 | }; 189 | 190 | using T = function; 191 | using R = T::result_type; 192 | 193 | static_assert(std::is_nothrow_default_constructible_v); 194 | static_assert(std::is_nothrow_constructible_v); 195 | static_assert(std::is_copy_constructible_v); 196 | static_assert(std::is_copy_assignable_v); 197 | static_assert(std::is_nothrow_assignable_v); 198 | static_assert(std::is_nothrow_move_constructible_v); 199 | static_assert(std::is_nothrow_move_assignable_v); 200 | static_assert(std::is_nothrow_swappable_v); 201 | 202 | static_assert(std::is_same_v, R>); 203 | static_assert(std::is_same_v, R>); 204 | 205 | struct move_only 206 | { 207 | move_only(move_only const &) = delete; 208 | move_only(move_only &&) = default; 209 | 210 | void operator()(int) {} 211 | }; 212 | 213 | static_assert(std::is_move_constructible_v); 214 | static_assert(std::is_invocable_r_v); 215 | 216 | static_assert(not std::is_constructible_v, 217 | "target object must be copy-constructible"); 218 | static_assert(not std::is_constructible_v, 219 | move_only>, 220 | "bounded target object must be copy-constructible"); 221 | 222 | struct reject_rvalue 223 | { 224 | reject_rvalue(reject_rvalue &&) = delete; 225 | reject_rvalue(reject_rvalue const &) = default; 226 | 227 | void operator()(int) {} 228 | }; 229 | 230 | static_assert(std::is_copy_constructible_v); 231 | static_assert(std::is_invocable_r_v); 232 | 233 | static_assert(std::is_constructible_v); 234 | static_assert(not std::is_constructible_v, 235 | "target object must be initialized"); 236 | 237 | static_assert(std::is_constructible_v, 238 | reject_rvalue &>); 239 | static_assert(not std::is_constructible_v< 240 | T, nontype_t<&reject_rvalue::operator()>, reject_rvalue>, 241 | "bounded target object must be initialized"); 242 | -------------------------------------------------------------------------------- /tests/function_ref/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(run-function_ref) 2 | target_sources(run-function_ref PRIVATE 3 | "main.cpp" 4 | "common_callables.h" 5 | "test_constexpr.cpp" 6 | "test_basics.cpp" 7 | "test_nontype.cpp" 8 | "common_callables.cpp" 9 | "test_noexcept.cpp" 10 | "test_const.cpp" 11 | "test_safety.cpp" 12 | "test_const_noexcept.cpp" 13 | "test_ctad.cpp" 14 | "test_call_pattern.cpp" 15 | "test_constinit.cpp" 16 | "test_return_reference.cpp" 17 | ) 18 | target_link_libraries(run-function_ref PRIVATE nontype_functional kris-ut) 19 | set_target_properties(run-function_ref PROPERTIES OUTPUT_NAME run) 20 | add_test(function_ref run) 21 | -------------------------------------------------------------------------------- /tests/function_ref/common_callables.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | void foo(function_ref f) 4 | { 5 | f(); 6 | } 7 | 8 | int f() 9 | { 10 | return BODYN(free_function); 11 | } 12 | 13 | int A::g() 14 | { 15 | return BODYN('g'); 16 | } 17 | 18 | int A::k() const 19 | { 20 | return BODYN('k'); 21 | } 22 | 23 | int h(A) 24 | { 25 | return BODYN(free_function); 26 | } 27 | 28 | int C::operator()() 29 | { 30 | return BODYN(non_const); 31 | } 32 | 33 | int C::operator()() const 34 | { 35 | return BODYN(const_); 36 | } 37 | -------------------------------------------------------------------------------- /tests/function_ref/common_callables.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "std23/function_ref.h" 4 | 5 | #include 6 | 7 | #ifdef _MSC_VER 8 | #define BODYN(n) ((::boost::ut::log << __FUNCSIG__ << '\n'), n) 9 | #else 10 | #define BODYN(n) ((::boost::ut::log << __PRETTY_FUNCTION__ << '\n'), n) 11 | #endif 12 | 13 | using std23::function_ref; 14 | using std23::nontype; 15 | using std23::nontype_t; 16 | 17 | using namespace boost::ut; 18 | 19 | template struct int_c : detail::op 20 | { 21 | using value_type = decltype(N); 22 | static constexpr auto value = N; 23 | 24 | [[nodiscard]] constexpr operator value_type() const { return N; } 25 | [[nodiscard]] constexpr auto get() const { return N; } 26 | }; 27 | 28 | inline constexpr int_c<0> free_function; 29 | inline constexpr int_c<1> function_template; 30 | inline constexpr int_c<2> non_const; 31 | inline constexpr int_c<3> const_; 32 | 33 | template inline constexpr int_c ch; 34 | 35 | void foo(function_ref f); 36 | 37 | int f(); 38 | 39 | template T g() 40 | { 41 | return BODYN(function_template); 42 | } 43 | 44 | struct A 45 | { 46 | int g(); 47 | int k() const; 48 | 49 | int data = 99; 50 | }; 51 | 52 | int h(A); 53 | 54 | struct C 55 | { 56 | int operator()(); 57 | int operator()() const; 58 | }; 59 | -------------------------------------------------------------------------------- /tests/function_ref/main.cpp: -------------------------------------------------------------------------------- 1 | int main() 2 | {} 3 | -------------------------------------------------------------------------------- /tests/function_ref/test_basics.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | constexpr auto call = [](function_ref fr) { return fr(); }; 4 | 5 | suite basics = [] 6 | { 7 | using namespace bdd; 8 | 9 | "basics"_test = [] 10 | { 11 | given("a function") = [] { expect(call(f) == free_function); }; 12 | 13 | given("a closure") = [] 14 | { expect(call([] { return BODYN(1); }) == 1_i); }; 15 | 16 | given("a function template specialization") = [] 17 | { expect(call(g) == function_template); }; 18 | 19 | given("a mutable callable object") = [] 20 | { 21 | C c; 22 | expect(call(c) == non_const); 23 | }; 24 | 25 | given("an immutable callable object") = [] 26 | { 27 | C const c; 28 | expect(call(c) == const_); 29 | }; 30 | }; 31 | }; 32 | -------------------------------------------------------------------------------- /tests/function_ref/test_call_pattern.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | struct Base 4 | { 5 | int &n; 6 | }; 7 | 8 | class Track : Base 9 | { 10 | public: 11 | Track(int &target) : Base{target} {} 12 | Track(Track const &other) : Base(other) { ++n; } 13 | }; 14 | 15 | static_assert(not std::is_trivially_copy_constructible_v); 16 | static_assert(not std::is_trivially_move_constructible_v); 17 | 18 | static void make_call(Track &&) 19 | {} 20 | 21 | using namespace std::string_view_literals; 22 | inline constexpr auto some_str = "if you see this, then we're fine"sv; 23 | 24 | static auto f_str() 25 | { 26 | return some_str.data(); 27 | }; 28 | 29 | suite call_pattern = [] 30 | { 31 | using namespace bdd; 32 | 33 | "call_pattern"_test = [] 34 | { 35 | given("a signature that takes parameter by value") = 36 | [] class C, class T>(C ty) 37 | { 38 | #if !defined(_MSC_VER) || (_MSC_VER != 1933 && _MSC_VER != 1934) 39 | boost::ut::log << ty; 40 | 41 | T fr = make_call; 42 | int n = 0; 43 | 44 | when("passing rvalue argument") = [=]() mutable 45 | { 46 | fr(Track{n}); 47 | 48 | then("no copy is made") = [&] { expect(n == 0_i); }; 49 | }; 50 | 51 | when("passing lvalue argument") = [=]() mutable 52 | { 53 | Track t{n}; 54 | fr(t); 55 | 56 | then("made at most one copy") = [&] { expect(n <= 1_i); }; 57 | }; 58 | #endif 59 | } | std::tuple(type>, 60 | type>); 61 | 62 | given("a function_ref that has a nontrivial return type") = [] 63 | { 64 | function_ref fr = f_str; 65 | 66 | then("its erased functions can return different types") = [&] 67 | { 68 | expect(fr() == some_str); 69 | 70 | auto fn = [i = 0] { return (void)i, some_str.data(); }; 71 | fr = decltype(fr)(fn); 72 | 73 | expect(fr() == some_str); 74 | }; 75 | }; 76 | }; 77 | }; 78 | -------------------------------------------------------------------------------- /tests/function_ref/test_const.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | constexpr auto call = [](function_ref f) { return f(); }; 4 | 5 | struct C_mut 6 | { 7 | int operator()() { return BODYN(non_const); } 8 | }; 9 | 10 | suite const_qualified = [] 11 | { 12 | using namespace bdd; 13 | 14 | feature("callable as const") = [] 15 | { 16 | given("a closure") = [] 17 | { expect(call([] { return BODYN(1); }) == 1_i); }; 18 | 19 | given("a mutable callable object") = [] 20 | { 21 | C c; 22 | expect(call(c) == const_); 23 | }; 24 | 25 | given("an immutable callable object") = [] 26 | { 27 | C const c; 28 | expect(call(c) == const_); 29 | }; 30 | 31 | given("a prvalue") = [] { expect(call(C{}) == const_); }; 32 | given("a const xvalue") = [] 33 | { 34 | C const cc; 35 | expect(call(static_cast(cc)) == const_); 36 | }; 37 | }; 38 | 39 | feature("call from const member function") = [] 40 | { 41 | given("an object without operator()") = [] 42 | { 43 | A a; 44 | 45 | when("binding by name") = [&] { 46 | expect(call({nontype<&A::k>, a}) == ch<'k'>); 47 | }; 48 | 49 | when("binding by pointer") = [&] { 50 | expect(call({nontype<&A::k>, &a}) == ch<'k'>); 51 | }; 52 | 53 | when("binding by reference_wrapper") = [&] 54 | { 55 | std::reference_wrapper r = a; 56 | expect(call({nontype<&A::k>, r}) == ch<'k'>); 57 | }; 58 | 59 | when("binding free function by name") = [&] { 60 | expect(call({nontype, a}) == free_function); 61 | }; 62 | }; 63 | }; 64 | }; 65 | 66 | static_assert(not std::is_invocable_v, 68 | "const-qualified signature cannot reference mutable lambda"); 69 | 70 | static_assert(std::is_invocable_v); 71 | static_assert(std::is_invocable_v); 72 | static_assert(std::is_invocable_v); 73 | static_assert(not std::is_invocable_v, 74 | "const object is not callable"); 75 | static_assert(not std::is_invocable_v); 76 | 77 | static_assert(not std::is_invocable_v, 78 | "const-qualified rvalue is not callable"); 79 | static_assert(not std::is_invocable_v, 80 | "const-qualified lvalue is not callable"); 81 | static_assert(not std::is_invocable_v); 82 | static_assert(not std::is_invocable_v); 83 | static_assert(not std::is_invocable_v); 84 | 85 | using T = function_ref; 86 | using U = function_ref; 87 | 88 | static_assert(std::is_constructible_v, A &>); 89 | static_assert(not std::is_constructible_v, A>, 90 | "cannot bind rvalue"); 91 | static_assert(not std::is_constructible_v, A const>); 92 | 93 | static_assert(not std::is_constructible_v, A &>); 94 | static_assert(not std::is_constructible_v, A>); 95 | 96 | static_assert(std::is_constructible_v, A &>); 97 | static_assert(not std::is_constructible_v, A>); 98 | 99 | static_assert(not std::is_constructible_v, A>, 100 | "free function does not bind rvalue either"); 101 | static_assert(not std::is_constructible_v, A const>); 102 | 103 | static_assert(not std::is_constructible_v, A *>, 104 | "free function does not bind pointers"); 105 | static_assert(not std::is_constructible_v, A const *>); 106 | 107 | static_assert(not std::is_constructible_v, A *>); 108 | static_assert(not std::is_constructible_v, A const *>); -------------------------------------------------------------------------------- /tests/function_ref/test_const_noexcept.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | struct D 4 | { 5 | int operator()() noexcept { return BODYN(11); } 6 | int operator()() const noexcept { return BODYN(22); } 7 | }; 8 | 9 | constexpr auto unqual_call = [](function_ref f) { return f(); }; 10 | constexpr auto call = [](function_ref f) { return f(); }; 11 | 12 | struct A_nice 13 | { 14 | int g() noexcept { return BODYN('g'); } 15 | int h() const noexcept { return BODYN('h'); } 16 | }; 17 | 18 | suite const_noexcept_qualified = [] 19 | { 20 | using namespace bdd; 21 | 22 | feature("callable is const noexcept") = [] 23 | { 24 | given("a const object and a non-const object") = [] 25 | { 26 | D d; 27 | D const cd; 28 | 29 | when("unqualified signature call non-const object") = [&] 30 | { expect(unqual_call(d) == 11_i); }; 31 | 32 | when("qualified signature call non-const object") = [&] 33 | { expect(call(d) == 22_i); }; 34 | 35 | when("unqualified signature call const object") = [&] 36 | { expect(unqual_call(cd) == 22_i); }; 37 | 38 | when("qualified signature call const object") = [&] 39 | { expect(call(cd) == 22_i); }; 40 | }; 41 | 42 | given("a noexcept closure") = [] 43 | { expect(call([]() noexcept { return BODYN(42); }) == 42_i); }; 44 | }; 45 | 46 | feature("call from const noexcept member function") = [] 47 | { 48 | given("a non-const object with no qualified member function") = [] 49 | { 50 | A a; 51 | 52 | then("pointer to data member is deemed a pure accessor") = [&] 53 | { 54 | expect(call({nontype<&A::data>, a}) == 99_i); 55 | expect(call({nontype<&A::data>, &a}) == 99_i); 56 | 57 | std::reference_wrapper r = a; 58 | expect(call({nontype<&A::data>, r}) == 99_i); 59 | }; 60 | }; 61 | 62 | given("a non-const object with qualified member functions") = [] 63 | { 64 | A_nice w; 65 | 66 | then("can call const member function") = [&] 67 | { 68 | expect(call({nontype<&A_nice::h>, w}) == ch<'h'>); 69 | expect(call({nontype<&A_nice::h>, &w}) == ch<'h'>); 70 | }; 71 | 72 | then("can call const member function on const lvalue") = [&] { 73 | expect(call({nontype<&A_nice::h>, std::as_const(w)}) == 74 | ch<'h'>); 75 | }; 76 | }; 77 | }; 78 | }; 79 | 80 | static_assert( 81 | not std::is_invocable_v, 82 | "noexcept signature does not accept potentially-throwing lambda"); 83 | 84 | static_assert( 85 | not std::is_invocable_v, 87 | "const noexcept signature does not accept noexcept but mutable lambda"); 88 | 89 | using U = function_ref; 90 | 91 | static_assert(not std::is_constructible_v, A>, 92 | "cannot bind rvalue"); 93 | 94 | static_assert(not std::is_constructible_v, A &>, 95 | "not qualified"); 96 | static_assert(not std::is_constructible_v, A &>, 97 | "not noexcept-qualified"); 98 | static_assert(not std::is_constructible_v, A_nice &>, 99 | "not const-qualified"); 100 | 101 | static_assert(not std::is_constructible_v, A_nice>, 102 | "cannot bind rvalue"); 103 | 104 | static_assert(not std::is_constructible_v, A &>, 105 | "not noexcept"); 106 | int h_good(A) noexcept; 107 | static_assert(std::is_constructible_v, A &>); 108 | -------------------------------------------------------------------------------- /tests/function_ref/test_constexpr.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | namespace 4 | { 5 | 6 | constexpr int cf() 7 | { 8 | return 2; 9 | } 10 | 11 | struct B 12 | { 13 | int data = 101; 14 | }; 15 | 16 | constexpr int constexpr_test1() 17 | { 18 | function_ref fr = nontype; 19 | fr = nontype; // cf is a constexpr function 20 | return fr(); 21 | } 22 | 23 | constexpr int constexpr_test2() 24 | { 25 | B b; 26 | function_ref fmr = nontype<&B::data>; 27 | return fmr(b); 28 | } 29 | 30 | static_assert(constexpr_test1() == 2); 31 | static_assert(constexpr_test2() == 101); 32 | 33 | } // namespace 34 | -------------------------------------------------------------------------------- /tests/function_ref/test_constinit.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | namespace 4 | { 5 | 6 | static C c; 7 | constinit function_ref cb{c}; 8 | 9 | static A a; 10 | constinit function_ref cb_memref{nontype<&A::k>, a}; 11 | constinit function_ref cb_memptr{nontype<&A::k>, &a}; 12 | 13 | suite constant_initialized = [] 14 | { 15 | using namespace bdd; 16 | 17 | feature("constant-initialized global callback") = [] 18 | { 19 | given("a global callback") = [] 20 | { 21 | then("you can use it without capturing") = [] 22 | { expect(cb() == non_const); }; 23 | 24 | when("rebinding it to a different function") = [] 25 | { 26 | cb = f; 27 | 28 | then("its functionality is replaced") = [] 29 | { expect(cb() == free_function); }; 30 | }; 31 | 32 | when("rebinding it to a different global callback") = [] 33 | { 34 | cb = cb_memptr; 35 | 36 | then("its functionality is replaced") = [] 37 | { expect(cb() == cb_memref()); }; 38 | }; 39 | }; 40 | }; 41 | }; 42 | 43 | } // namespace 44 | -------------------------------------------------------------------------------- /tests/function_ref/test_ctad.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | int f_good() noexcept; 4 | int h_good(A) noexcept; 5 | 6 | void test_ctad() 7 | { 8 | { 9 | auto fr = function_ref(f); 10 | static_assert(std::is_same_v>); 11 | } 12 | 13 | { 14 | auto fr = function_ref(f_good); 15 | static_assert( 16 | std::is_same_v>); 17 | } 18 | 19 | { 20 | auto fr = function_ref(h); 21 | static_assert(std::is_same_v>); 22 | } 23 | 24 | { 25 | auto fr = function_ref(h_good); 26 | static_assert( 27 | std::is_same_v>); 28 | } 29 | 30 | { 31 | auto fr = function_ref(nontype); 32 | static_assert(std::is_same_v>); 33 | } 34 | 35 | { 36 | auto fr = function_ref(nontype); 37 | static_assert( 38 | std::is_same_v>); 39 | } 40 | 41 | { 42 | auto fr = function_ref(nontype); 43 | static_assert(std::is_same_v>); 44 | } 45 | 46 | { 47 | auto fr = function_ref(nontype); 48 | static_assert( 49 | std::is_same_v>); 50 | } 51 | 52 | { 53 | A a; 54 | auto fr = function_ref(nontype<&A::g>, &a); 55 | static_assert(std::is_same_v>); 56 | } 57 | 58 | { 59 | A a; 60 | auto fr = function_ref(nontype<&A::k>, &a); 61 | static_assert(std::is_same_v>, 62 | "bound member function's qualifier is not deduced"); 63 | } 64 | 65 | { 66 | A a; 67 | auto fr = function_ref(nontype<&A::data>, &a); 68 | static_assert(std::is_same_v>); 69 | 70 | auto fc = function_ref(nontype<&A::data>, std::as_const(a)); 71 | static_assert(std::is_same_v>, 72 | "type to retrieve data member depends on object"); 73 | } 74 | 75 | { 76 | A a; 77 | auto fr = function_ref(nontype<&h>, a); 78 | static_assert(std::is_same_v>); 79 | } 80 | 81 | { 82 | A a; 83 | auto fr = function_ref(nontype<&h_good>, a); 84 | static_assert( 85 | std::is_same_v>); 86 | } 87 | } -------------------------------------------------------------------------------- /tests/function_ref/test_noexcept.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | constexpr auto call = [](function_ref f) 4 | { 5 | static_assert(noexcept(f())); 6 | return f(); 7 | }; 8 | 9 | int f_good() noexcept 10 | { 11 | return BODYN(free_function); 12 | } 13 | 14 | struct A_good 15 | { 16 | int g() noexcept { return BODYN('g'); } 17 | }; 18 | 19 | int h_good(A) noexcept 20 | { 21 | return BODYN('h'); 22 | } 23 | 24 | suite noexcept_qualified = [] 25 | { 26 | using namespace bdd; 27 | 28 | feature("call will be noexcept") = [] 29 | { 30 | given("a noexcept function") = [] 31 | { expect(call(f_good) == free_function); }; 32 | 33 | given("a noexcept closure") = [] 34 | { expect(call([]() noexcept { return BODYN(1); }) == 1_i); }; 35 | 36 | given("a noexcept structual callable") = [] 37 | { expect(call(nontype) == free_function); }; 38 | }; 39 | 40 | feature("call from noexcept member function") = [] 41 | { 42 | given("an object without operator()") = [] 43 | { 44 | A_good x; 45 | 46 | when("binding by name") = [&] { 47 | expect(call({nontype<&A_good::g>, x}) == ch<'g'>); 48 | }; 49 | 50 | when("binding by pointer") = [&] { 51 | expect(call({nontype<&A_good::g>, &x}) == ch<'g'>); 52 | }; 53 | 54 | when("binding by reference_wrapper") = [&] 55 | { 56 | std::reference_wrapper r = x; 57 | expect(call({nontype<&A_good::g>, r}) == ch<'g'>); 58 | }; 59 | }; 60 | 61 | given("an object without a noexcept member function") = [] 62 | { 63 | A a; 64 | 65 | then("you can treat member access as a nothrow call") = [&] { 66 | expect(call({nontype<&A::data>, a}) == 99_i); 67 | }; 68 | 69 | then("you can treat a noexcept free function as its memfn") = [&] { 70 | expect(call({nontype, a}) == ch<'h'>); 71 | }; 72 | }; 73 | }; 74 | }; 75 | 76 | static_assert(std::is_invocable_v); 77 | static_assert(not std::is_invocable_v, 78 | "function may throw"); 79 | 80 | static_assert(std::is_invocable_v); 81 | static_assert(not std::is_invocable_v, 82 | "operator() may throw"); 83 | 84 | static_assert(std::is_invocable_v>); 85 | static_assert(not std::is_invocable_v>, 86 | "function may throw regardless whether its pointer is constant"); 87 | 88 | using T = function_ref; 89 | using U = function_ref; 90 | 91 | static_assert(std::is_constructible_v, A &>); 92 | static_assert(not std::is_constructible_v, A &>, 93 | "member function may throw"); 94 | 95 | static_assert(std::is_constructible_v, A &>); 96 | static_assert(not std::is_constructible_v, A &>, 97 | "explicit member function may throw"); -------------------------------------------------------------------------------- /tests/function_ref/test_nontype.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | constexpr auto call = [](function_ref f) { return f(); }; 4 | 5 | suite nttp_callable = [] 6 | { 7 | using namespace bdd; 8 | 9 | feature("unbound instance method") = [] 10 | { 11 | given("a function") = [] { expect(call(nontype) == free_function); }; 12 | 13 | given("a function template specialization") = [] 14 | { expect(call(nontype>) == function_template); }; 15 | 16 | given("a closure") = [] 17 | { expect(call(nontype<[] { return BODYN(42); }>) == 42_i); }; 18 | }; 19 | 20 | feature("bound instance method") = [] 21 | { 22 | given("a const object and a non-const object") = [] 23 | { 24 | A a; 25 | A const b; 26 | 27 | when("binding non-const method to non-const object") = [&] 28 | { 29 | expect(call({nontype<&A::g>, a}) == ch<'g'>); 30 | expect(call({nontype<&A::g>, &a}) == ch<'g'>); 31 | }; 32 | 33 | when("binding const method to non-const object") = [&] 34 | { 35 | expect(call({nontype<&A::k>, a}) == ch<'k'>); 36 | expect(call({nontype<&A::k>, &a}) == ch<'k'>); 37 | }; 38 | 39 | when("binding const method to const object") = [&] 40 | { 41 | expect(call({nontype<&A::k>, b}) == ch<'k'>); 42 | expect(call({nontype<&A::k>, &b}) == ch<'k'>); 43 | }; 44 | 45 | when("binding pointer to data member to object") = [&] 46 | { 47 | expect(call({nontype<&A::data>, a}) == 99_i); 48 | expect(call({nontype<&A::data>, &a}) == 99_i); 49 | }; 50 | 51 | when("binding free function to object") = [&] { 52 | expect(call({nontype, a}) == free_function); 53 | }; 54 | 55 | when("binding closure to object") = [&] { 56 | expect(call({nontype<[](A p) { return BODYN(p.data); }>, a}) == 57 | 99_i); 58 | }; 59 | 60 | when("binding closure to pointer") = [&] { 61 | expect(call({nontype<[](A *p) { return BODYN(p->data); }>, 62 | &a}) == 99_i); 63 | }; 64 | 65 | when("passing objects using reference_wrapper") = [&] 66 | { 67 | std::reference_wrapper r = b; 68 | expect(call({nontype<&A::data>, r}) == 99_i); 69 | expect(call({nontype, r}) == free_function); 70 | }; 71 | }; 72 | }; 73 | }; 74 | -------------------------------------------------------------------------------- /tests/function_ref/test_return_reference.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | template T &&identity_fn(T &&x) 4 | { 5 | return x; 6 | } 7 | 8 | struct identity_fobj 9 | { 10 | int x = 42; 11 | 12 | int const &operator()() const { return x; } 13 | }; 14 | 15 | suite return_reference = [] 16 | { 17 | using namespace bdd; 18 | 19 | feature("reference return value") = [] 20 | { 21 | given("a variable") = [] 22 | { 23 | int x; 24 | function_ref fn = identity_fn; 25 | expect(std::addressof(fn(x)) == &x); 26 | }; 27 | 28 | given("a data member") = [] 29 | { 30 | identity_fobj obj; 31 | 32 | when("bind to a non-const wrapper") = [&] 33 | { 34 | function_ref fn = obj; 35 | expect(std::addressof(fn()) == &obj.x); 36 | }; 37 | 38 | when("bind to a const wrapper") = [&] 39 | { 40 | function_ref fn = obj; 41 | expect(std::addressof(fn()) == &obj.x); 42 | }; 43 | }; 44 | 45 | given("a pointer to member object") = [] 46 | { 47 | identity_fobj obj; 48 | 49 | when("used as an unbound method") = [=] 50 | { 51 | function_ref fn = 52 | nontype<&identity_fobj::x>; 53 | expect(std::addressof(fn(obj)) == &obj.x); 54 | }; 55 | 56 | when("used as a non-const bound method") = [=]() mutable 57 | { 58 | function_ref fn = {nontype<&identity_fobj::x>, obj}; 59 | expect(std::addressof(fn()) == &obj.x); 60 | }; 61 | 62 | when("used as a const bound method") = [=] 63 | { 64 | function_ref fn = {nontype<&identity_fobj::x>, obj}; 65 | expect(std::addressof(fn()) == &obj.x); 66 | }; 67 | }; 68 | }; 69 | }; 70 | -------------------------------------------------------------------------------- /tests/function_ref/test_safety.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | static_assert(std::is_invocable_v); 4 | static_assert(std::is_invocable_v); 5 | 6 | auto get_h() 7 | { 8 | return h; 9 | } 10 | 11 | int h_another(A const &) 12 | { 13 | return 'h'; 14 | } 15 | 16 | suite safety = [] 17 | { 18 | using namespace bdd; 19 | 20 | "safety"_test = [] 21 | { 22 | given("a function_ref initialized from a function pointer") = [] 23 | { 24 | function_ref fr = get_h(); 25 | A a; 26 | 27 | then("it is not dangling") = [&] 28 | { expect(fr(a) == free_function); }; 29 | 30 | when("it rebinds to a different function pointer") = [&] 31 | { 32 | fr = &h_another; 33 | 34 | then("it does not dangle either") = [&] 35 | { expect(fr(a) == ch<'h'>); }; 36 | }; 37 | 38 | when("it rebinds to a nttp pointer to member function") = [&] 39 | { 40 | fr = nontype<&A::g>; 41 | 42 | then("it never dangles") = [&] { expect(fr(a) == ch<'g'>); }; 43 | }; 44 | }; 45 | }; 46 | }; 47 | 48 | using T = function_ref; 49 | 50 | static_assert(not std::is_default_constructible_v, 51 | "function_ref is not nullable"); 52 | static_assert(not std::is_constructible_v, 53 | "function_ref doesn't initialize from member pointers"); 54 | static_assert(not std::is_constructible_v); 55 | static_assert(not std::is_constructible_v); 56 | 57 | using U = function_ref; 58 | 59 | static_assert(std::is_trivially_copy_constructible_v); 60 | static_assert(std::is_trivially_copy_assignable_v); 61 | static_assert(std::is_nothrow_constructible_v); 62 | static_assert(not std::is_assignable_v, 63 | "function_ref does not assign from types that may dangle"); 64 | static_assert(not std::is_assignable_v, 65 | "function_ref rejects lvalue of those types as well"); 66 | static_assert(not std::is_assignable_v); 67 | static_assert(not std::is_assignable_v); 68 | -------------------------------------------------------------------------------- /tests/move_only_function/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(run-move_only_function) 2 | target_sources(run-move_only_function PRIVATE 3 | "main.cpp" 4 | "test_basics.cpp" 5 | "common_callables.h" 6 | "common_callables.cpp" 7 | "test_cvref.cpp" 8 | "test_nullable.cpp" 9 | "test_value_semantics.cpp" 10 | "test_inplace.cpp" 11 | "test_reference_semantics.cpp" 12 | "test_noexcept.cpp" 13 | "test_nontype.cpp" 14 | "test_return_reference.cpp" 15 | "test_unique.cpp" 16 | ) 17 | target_compile_options(run-move_only_function PRIVATE 18 | $<$:-Wno-self-move> 19 | $<$:-fsized-deallocation>) 20 | target_link_libraries(run-move_only_function PRIVATE nontype_functional kris-ut) 21 | set_target_properties(run-move_only_function PROPERTIES OUTPUT_NAME run) 22 | add_test(move_only_function run) 23 | -------------------------------------------------------------------------------- /tests/move_only_function/common_callables.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | int f() 4 | { 5 | return BODYN(free_function); 6 | } 7 | 8 | int A::g() 9 | { 10 | return BODYN(empty); 11 | } 12 | 13 | int h(A) 14 | { 15 | return BODYN(free_function); 16 | } 17 | -------------------------------------------------------------------------------- /tests/move_only_function/common_callables.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "std23/move_only_function.h" 4 | 5 | #include 6 | 7 | using namespace boost::ut; 8 | 9 | using std23::move_only_function; 10 | using std23::nontype; 11 | using std23::nontype_t; 12 | 13 | #ifdef _MSC_VER 14 | #define BODYN(n) ((::boost::ut::log << __FUNCSIG__ << '\n'), n) 15 | #else 16 | #define BODYN(n) ((::boost::ut::log << __PRETTY_FUNCTION__ << '\n'), n) 17 | #endif 18 | 19 | template struct int_c : detail::op 20 | { 21 | using value_type = decltype(N); 22 | static constexpr auto value = N; 23 | 24 | [[nodiscard]] constexpr operator value_type() const noexcept { return N; } 25 | [[nodiscard]] constexpr auto get() const { return N; } 26 | }; 27 | 28 | inline constexpr int_c<0> free_function; 29 | inline constexpr int_c<1> function_template; 30 | inline constexpr int_c<2> empty; 31 | inline constexpr int_c<3> const_; 32 | inline constexpr int_c<4> lref; 33 | inline constexpr int_c<5> const_lref; 34 | inline constexpr int_c<6> rref; 35 | inline constexpr int_c<7> const_rref; 36 | inline constexpr int_c<8> noexcept_; 37 | 38 | template inline constexpr int_c ch; 39 | 40 | int f(); 41 | 42 | struct A 43 | { 44 | int g(); 45 | 46 | int data = 99; 47 | }; 48 | 49 | int h(A); 50 | -------------------------------------------------------------------------------- /tests/move_only_function/main.cpp: -------------------------------------------------------------------------------- 1 | int main() 2 | {} -------------------------------------------------------------------------------- /tests/move_only_function/test_basics.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | suite basics = [] 4 | { 5 | using namespace bdd; 6 | 7 | "basics"_test = [] 8 | { 9 | given("a default constructed move_only_function") = [] 10 | { 11 | move_only_function fn; 12 | 13 | then("it is empty") = [&] 14 | { 15 | expect(!fn); 16 | expect(fn == nullptr); 17 | expect(nullptr == fn); 18 | }; 19 | }; 20 | 21 | given("a move_only_function initialized from function") = [] 22 | { 23 | move_only_function fn = f; 24 | 25 | then("it is not empty") = [&] 26 | { 27 | expect(bool(fn)); 28 | expect(fn != nullptr); 29 | expect(nullptr != fn); 30 | }; 31 | 32 | when("called") = [&] { expect(fn() == 0_i); }; 33 | }; 34 | 35 | given("a move_only_function initialized from closure") = [] 36 | { 37 | move_only_function fn = [] { return 42; }; 38 | 39 | then("it is not empty") = [&] 40 | { 41 | expect(bool(fn)); 42 | expect(fn != nullptr); 43 | expect(nullptr != fn); 44 | }; 45 | 46 | when("called") = [&] { expect(fn() == 42_i); }; 47 | }; 48 | }; 49 | }; 50 | -------------------------------------------------------------------------------- /tests/move_only_function/test_cvref.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | #include 4 | 5 | using T = std::unique_ptr; 6 | 7 | struct UnspecificValueCategory : T 8 | { 9 | int operator()(T) { return BODYN(empty); } 10 | }; 11 | 12 | struct LvalueOnly : T 13 | { 14 | int operator()(T) & { return BODYN(lref); } 15 | }; 16 | 17 | struct RvalueOnly : T 18 | { 19 | int operator()(T) && { return BODYN(rref); } 20 | }; 21 | 22 | struct EitherValueCategory : LvalueOnly, RvalueOnly 23 | { 24 | using LvalueOnly::operator(); 25 | using RvalueOnly::operator(); 26 | }; 27 | 28 | template struct ImmutableCall; 29 | 30 | template<> 31 | struct ImmutableCall : UnspecificValueCategory 32 | { 33 | using UnspecificValueCategory::operator(); 34 | int operator()(T) const { return BODYN(const_); } 35 | }; 36 | 37 | template<> struct ImmutableCall : LvalueOnly 38 | { 39 | using LvalueOnly::operator(); 40 | int operator()(T) const & { return BODYN(const_lref); } 41 | }; 42 | 43 | template<> struct ImmutableCall : RvalueOnly 44 | { 45 | using RvalueOnly::operator(); 46 | int operator()(T) const && { return BODYN(const_rref); } 47 | }; 48 | 49 | template<> 50 | struct ImmutableCall : ImmutableCall, 51 | ImmutableCall 52 | { 53 | using ImmutableCall::operator(); 54 | using ImmutableCall::operator(); 55 | }; 56 | 57 | struct NoCall 58 | { 59 | int unspecific_value_category(T) { return BODYN('k'); } 60 | int lvalue_only(T) & { return BODYN('k'); } 61 | int rvalue_only(T) && { return BODYN('k'); } 62 | int immutable(T) const { return BODYN('k'); } 63 | int immutable_lvalue_only(T) const & { return BODYN('k'); } 64 | int immutable_rvalue_only(T) const && { return BODYN('k'); } 65 | }; 66 | 67 | suite cvref = [] 68 | { 69 | using namespace bdd; 70 | using type_traits::is_valid; 71 | 72 | feature("empty cv-ref qualifier") = 73 | [call = [](move_only_function f) { return f(nullptr); }] 74 | { 75 | given("a callable object with unqual call operator") = [=] 76 | { 77 | expect(call(UnspecificValueCategory{}) == empty); 78 | 79 | then("reference_wrapper can call it without moving") = [=] 80 | { 81 | UnspecificValueCategory fn; 82 | expect(call(std::reference_wrapper(fn)) == empty); 83 | }; 84 | }; 85 | 86 | given("a callable object with value-category-aware call operators") = 87 | [=] 88 | { 89 | then("the object is called only as an lvalue") = [=] 90 | { expect(call(EitherValueCategory{}) == lref); }; 91 | }; 92 | 93 | static_assert(not std::is_invocable_v, 94 | "See also: https://cplusplus.github.io/LWG/issue3680"); 95 | 96 | given("an immutable callable object") = [=] 97 | { 98 | then("the object is called only as a copy") = [=] { 99 | expect(call(ImmutableCall{}) == empty); 100 | }; 101 | 102 | then("reference_wrapper can call either const or non-const") = [=] 103 | { 104 | ImmutableCall fn; 105 | expect(call(std::ref(fn)) == empty); 106 | expect(call(std::cref(fn)) == const_); 107 | }; 108 | }; 109 | 110 | given("an object without operator()") = [=] 111 | { 112 | NoCall a; 113 | 114 | then("a member function may be used in place of operator()") = 115 | [&](auto t) 116 | { 117 | expect(call({t, a}) == ch<'k'>) << "by name"; 118 | expect(call({t, &a}) == ch<'k'>) << "by pointer"; 119 | expect(call({t, std::ref(a)}) == ch<'k'>) << "by refwrap"; 120 | } | std::tuple(nontype<&NoCall::unspecific_value_category>, 121 | nontype<&NoCall::immutable>, 122 | nontype<&NoCall::lvalue_only>, 123 | nontype<&NoCall::immutable_lvalue_only>); 124 | }; 125 | }; 126 | 127 | feature("const-qualified") = 128 | [call = [](move_only_function const f) 129 | { return f(nullptr); }] 130 | { 131 | given("a callable object with const call operator") = [=] 132 | { 133 | expect(call(ImmutableCall{}) == const_); 134 | 135 | then("reference_wrapper is unaffected by the qualifier") = [=] 136 | { 137 | ImmutableCall fn; 138 | expect(call(std::ref(fn)) == empty); 139 | expect(call(std::cref(fn)) == const_); 140 | }; 141 | }; 142 | 143 | given("a callable object with value-category-aware call operators") = 144 | [=] 145 | { 146 | then("the object is called only as an lvalue") = [=] { 147 | expect(call(ImmutableCall{}) == 148 | const_lref); 149 | }; 150 | }; 151 | 152 | static_assert( 153 | not std::is_invocable_v, 154 | "unqual signature is non-const-only"); 155 | 156 | given("a callable object with unqual call operator") = [=] 157 | { 158 | then("reference_wrapper can make it const-invocable by lying") = [=] 159 | { 160 | UnspecificValueCategory fn; 161 | expect(call(std::reference_wrapper(fn)) == empty); 162 | }; 163 | }; 164 | 165 | given("an object without operator()") = [=] 166 | { 167 | NoCall a; 168 | 169 | then("a member function may be used in place of operator()") = 170 | [=](auto t) 171 | { 172 | expect(call({t, a}) == ch<'k'>) << "by name"; 173 | expect(call({t, &a}) == ch<'k'>) << "by pointer"; 174 | expect(call({t, std::ref(a)}) == ch<'k'>) << "by refwrap"; 175 | } | std::tuple(nontype<&NoCall::immutable>, 176 | nontype<&NoCall::immutable_lvalue_only>); 177 | }; 178 | }; 179 | 180 | feature("&-qualified") = 181 | [call = [](move_only_function f) { return f(nullptr); }] 182 | { 183 | given("a callable object with lvalue-ref call operator") = [=] 184 | { expect(call(LvalueOnly{}) == lref); }; 185 | 186 | given("a callable object with unqual call operator") = [=] 187 | { expect(call(UnspecificValueCategory{}) == empty); }; 188 | 189 | given("a callable object with value-category-aware call operators") = 190 | [=] 191 | { 192 | then("the object is called only as an lvalue") = [=] 193 | { expect(call(EitherValueCategory{}) == lref); }; 194 | }; 195 | 196 | static_assert(not std::is_invocable_v, 197 | "&&-qualified cannot be called as an lvalue"); 198 | 199 | given("an immutable callable object") = [=] 200 | { 201 | then("the object is called only as a copy") = [=] { 202 | expect(call(ImmutableCall{}) == empty); 203 | }; 204 | }; 205 | }; 206 | 207 | feature("&&-qualified") = [call = [](move_only_function f) 208 | { return std::move(f)(nullptr); }] 209 | { 210 | given("a callable object with rvalue-ref call operator") = [=] 211 | { expect(call(RvalueOnly{}) == rref); }; 212 | 213 | given("a callable object with unqual call operator") = [=] 214 | { expect(call(UnspecificValueCategory{}) == empty); }; 215 | 216 | given("a callable object with value-category-aware call operators") = 217 | [=] 218 | { 219 | then("the object is called only as an rvalue") = [=] 220 | { expect(call(EitherValueCategory{}) == rref); }; 221 | 222 | then("reference_wrapper calls it only as an lvalue") = [=] 223 | { 224 | EitherValueCategory fn; 225 | expect(call(std::reference_wrapper(fn)) == lref); 226 | }; 227 | }; 228 | 229 | static_assert(not std::is_invocable_v, 230 | "&-qualified cannot be called as an rvalue"); 231 | 232 | given("an immutable callable object") = [=] 233 | { 234 | then("the object is called only as a copy") = [=] { 235 | expect(call(ImmutableCall{}) == empty); 236 | }; 237 | }; 238 | 239 | given("an object without operator()") = [=] 240 | { 241 | NoCall a; 242 | 243 | then("a member function may be used in place of operator()") = 244 | [&](auto t) 245 | { 246 | expect(call({t, a}) == ch<'k'>) << "by name"; 247 | 248 | static_assert( 249 | not is_valid([&](auto t) -> decltype(call({t, &a})) {}), 250 | "calling pointer-to-object works as if dereferenced"); 251 | } | std::tuple(nontype<&NoCall::rvalue_only>, 252 | nontype<&NoCall::immutable_rvalue_only>); 253 | }; 254 | }; 255 | 256 | feature("const &-qualified") = 257 | [call = [](move_only_function const f) 258 | { return f(nullptr); }] 259 | { 260 | given("a callable object with const lvalue-ref call operator") = [=] 261 | { expect(call(ImmutableCall{}) == const_lref); }; 262 | 263 | given("a callable object with const call operator") = [=] 264 | { expect(call(ImmutableCall{}) == const_); }; 265 | 266 | given("a callable object with value-category-aware call operators") = 267 | [=] 268 | { 269 | then("the object is called only as an lvalue") = [=] { 270 | expect(call(ImmutableCall{}) == 271 | const_lref); 272 | }; 273 | }; 274 | 275 | static_assert( 276 | not std::is_invocable_v>, 277 | "&&-qualified cannot be called as an lvalue"); 278 | 279 | static_assert( 280 | not std::is_invocable_v, 281 | "unqual signature is non-const-only"); 282 | }; 283 | 284 | feature("const &&-qualified") = 285 | [call = [](move_only_function const f) 286 | { return static_cast(f)(nullptr); }] 287 | { 288 | given("a callable object with const rvalue-ref call operator") = [=] 289 | { expect(call(ImmutableCall{}) == const_rref); }; 290 | 291 | given("a callable object with const call operator") = [=] 292 | { expect(call(ImmutableCall{}) == const_); }; 293 | 294 | given("a callable object with value-category-aware call operators") = 295 | [=] 296 | { 297 | then("the object is called only as an rvalue") = [=] { 298 | expect(call(ImmutableCall{}) == 299 | const_rref); 300 | }; 301 | 302 | then("reference_wrapper calls it only as an lvalue") = [=] 303 | { 304 | ImmutableCall fn; 305 | expect(call(std::ref(fn)) == lref); 306 | expect(call(std::cref(fn)) == const_lref); 307 | }; 308 | }; 309 | 310 | given("a callable object with const lvalue-ref call operators") = [=] 311 | { 312 | then("the object can be called as an rvalue") = [=] 313 | { expect(call(ImmutableCall{}) == const_lref); }; 314 | }; 315 | 316 | static_assert( 317 | not std::is_invocable_v, 318 | "unqual signature is non-const-only"); 319 | }; 320 | }; 321 | 322 | static_assert(std::is_invocable_v>); 323 | static_assert(std::is_invocable_v>); 324 | static_assert(not std::is_invocable_v const>); 325 | static_assert(std::is_invocable_v const>); 326 | 327 | static_assert(not std::is_invocable_v>); 328 | static_assert(std::is_invocable_v>, 329 | "const & can bind rvalue"); 330 | static_assert(not std::is_invocable_v const>); 331 | static_assert(std::is_invocable_v const>); 332 | 333 | static_assert(not std::is_invocable_v &>); 334 | static_assert(not std::is_invocable_v &>); 335 | static_assert(not std::is_invocable_v const &>); 336 | static_assert( 337 | not std::is_invocable_v const &>); 338 | -------------------------------------------------------------------------------- /tests/move_only_function/test_inplace.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace std::string_view_literals; 9 | 10 | class NotMovable : std::vector 11 | { 12 | public: 13 | NotMovable(NotMovable &&) = delete; 14 | NotMovable &operator=(NotMovable &&) = delete; 15 | 16 | using std::vector::vector; 17 | 18 | int operator()(int initial, std::string &out) 19 | { 20 | auto r = std::accumulate(begin(), end(), initial); 21 | out = std::to_string(r); 22 | return r; 23 | } 24 | }; 25 | 26 | static_assert(not std::is_move_constructible_v); 27 | static_assert(not std::is_move_assignable_v); 28 | 29 | class NotDefaultConstructible : public NotMovable 30 | { 31 | using NotMovable::NotMovable; 32 | 33 | NotDefaultConstructible() = delete; 34 | }; 35 | 36 | using T = move_only_function; 37 | 38 | struct copy_init 39 | { 40 | void operator()(T); 41 | }; 42 | 43 | suite inplace = [] 44 | { 45 | using std23::in_place_type; 46 | using namespace bdd; 47 | using type_traits::is_valid; 48 | 49 | feature("move_only_function does not require callable to be movable") = [] 50 | { 51 | given("a default constructible target type") = [] 52 | { 53 | T fn(in_place_type); 54 | 55 | then("move_only_function behaves as if it is the target") = [&] 56 | { 57 | std::string s; 58 | fn(42, s); 59 | 60 | expect(s == "42"sv); 61 | }; 62 | 63 | when("the move_only_function is moved into a new object") = [&] 64 | { 65 | auto fn2 = std::move(fn); 66 | 67 | then("the new object behaves as if it is the target") = [&] 68 | { 69 | std::string s; 70 | fn2(-1, s); 71 | 72 | expect(s == "-1"sv); 73 | }; 74 | }; 75 | 76 | then("you may not in-place construct a function parameter") = [] 77 | { 78 | static_assert( 79 | not is_valid( 80 | [](auto f) -> decltype(f(in_place_type)) { 81 | }), 82 | "not a converting constructor"); 83 | }; 84 | }; 85 | }; 86 | 87 | feature("move_only_function can in-place construct using any ctors") = [] 88 | { 89 | given("a target type without a default ctor") = [] 90 | { 91 | T fn(in_place_type, 3u, 2); // [2, 2, 2] 92 | 93 | then("direct-non-list-initialization may be requested") = [&] 94 | { 95 | std::string s; 96 | fn(0, s); 97 | 98 | expect(s == "6"sv); 99 | }; 100 | 101 | then("you may not in-place construct a function parameter") = [] 102 | { 103 | static_assert( 104 | not is_valid( 105 | [](auto f) -> decltype(f( 106 | {in_place_type, 107 | 3, 2})) {}), 108 | "does not convert from braced-init-list"); 109 | }; 110 | }; 111 | 112 | given("a target type with a initializer_list ctor") = [] 113 | { 114 | std::allocator a; 115 | T fn(in_place_type, {3, 1, 5, 4}, a); 116 | 117 | then("the ctor may be requested by passing braced-init-list") = [&] 118 | { 119 | std::string s; 120 | fn(0, s); 121 | 122 | expect(s == "13"sv); 123 | }; 124 | 125 | then("you may not in-place construct a function parameter") = [&] 126 | { 127 | static_assert( 128 | not is_valid( 129 | [](auto f) -> decltype(f( 130 | {in_place_type, 131 | {3, 1, 5, 4}})) {}), 132 | "does not convert from braced-init-list"); 133 | }; 134 | }; 135 | 136 | feature("in-place construct types without operator()") = [] 137 | { 138 | given("a type with user-defined ctors") = [] 139 | { 140 | move_only_function fn( 141 | nontype<[](std::vector &v) 142 | { return std::to_string(v.size()); }>, 143 | in_place_type>, 4u); 144 | 145 | then("in-place construct") = [&] { expect(fn() == "4"sv); }; 146 | }; 147 | 148 | given("a type with a initializer_list ctor") = [] 149 | { 150 | move_only_function fn( 151 | nontype<[](std::string const &s) { return std::stoi(s); }>, 152 | in_place_type, {'5', '9', '2', '\0'}); 153 | 154 | then("passing braced-init-list") = [&] 155 | { expect(fn() == 592_i); }; 156 | }; 157 | }; 158 | }; 159 | }; 160 | 161 | static_assert(std::is_constructible_v>, 162 | "std23::in_place_type should be as same as std::in_place_type"); 163 | static_assert(not std::is_constructible_v< 164 | T, std::in_place_type_t>); 165 | static_assert(std::is_constructible_v< 166 | T, std::in_place_type_t, std::size_t>); 167 | 168 | using W = move_only_function; 169 | using Vec = std::vector; 170 | 171 | // extension 172 | static_assert( 173 | std::is_nothrow_constructible_v, 174 | std23::in_place_type_t>); 175 | static_assert(std::is_nothrow_constructible_v< 176 | W, nontype_t<[](Vec &) { return 0; }>, 177 | std23::in_place_type_t>, Vec &>); 178 | -------------------------------------------------------------------------------- /tests/move_only_function/test_noexcept.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | constexpr auto throwable_call = [](move_only_function f) { return f(); }; 4 | 5 | constexpr auto nothrow_call = [](move_only_function f) 6 | { return f(); }; 7 | 8 | template struct overload : T... 9 | { 10 | using T::operator()...; 11 | }; 12 | 13 | template overload(T...) -> overload; 14 | 15 | constexpr auto call = overload{throwable_call, nothrow_call}; 16 | 17 | int f_good() noexcept 18 | { 19 | return BODYN(free_function); 20 | } 21 | 22 | struct A_good 23 | { 24 | int g() noexcept { return BODYN('g'); } 25 | }; 26 | 27 | int h_good(A) noexcept 28 | { 29 | return BODYN('h'); 30 | } 31 | 32 | suite noexcept_qualified = [] 33 | { 34 | using namespace bdd; 35 | 36 | feature("noexcept signature accepts nothrow callable objects") = [] 37 | { 38 | given("a noexcept function") = [] 39 | { expect(nothrow_call(f_good) == free_function); }; 40 | 41 | given("a noexcept closure") = [] 42 | { 43 | expect(nothrow_call([]() noexcept { return BODYN(noexcept_); }) == 44 | noexcept_); 45 | }; 46 | 47 | given("a noexcept structual callable") = [] 48 | { expect(nothrow_call(nontype) == free_function); }; 49 | }; 50 | 51 | feature("noexcept signature allows binding nothrow nttp") = [] 52 | { 53 | given("an object without operator()") = [] 54 | { 55 | A_good x; 56 | 57 | when("binding by name") = [&] { 58 | expect(nothrow_call({nontype<&A_good::g>, x}) == ch<'g'>); 59 | }; 60 | 61 | when("binding by pointer") = [&] { 62 | expect(nothrow_call({nontype<&A_good::g>, &x}) == ch<'g'>); 63 | }; 64 | 65 | when("binding by reference_wrapper") = [&] { 66 | expect(nothrow_call({nontype<&A_good::g>, std::ref(x)}) == 67 | ch<'g'>); 68 | }; 69 | }; 70 | 71 | given("an object without a noexcept member function") = [] 72 | { 73 | A a; 74 | 75 | then("you can treat member access as a nothrow call") = [&] { 76 | expect(nothrow_call({nontype<&A::data>, a}) == 99_i); 77 | }; 78 | 79 | then("you can treat a noexcept free function as its memfn") = [&] { 80 | expect(nothrow_call({nontype, a}) == ch<'h'>); 81 | }; 82 | }; 83 | }; 84 | 85 | feature("noexcept signature and unqual signature can overload") = [] 86 | { 87 | static_assert(not std::is_invocable_v, 88 | "a noexcept function is ambiguous to this overload set"); 89 | 90 | given("a function that potentially throws") = [] 91 | { 92 | then("it binds only to the throwable signature") = [] 93 | { expect(call(f) == free_function); }; 94 | }; 95 | }; 96 | }; 97 | 98 | static_assert(std::is_invocable_v); 99 | static_assert(std::is_invocable_v); 100 | static_assert(not std::is_invocable_v); 101 | static_assert(std::is_invocable_v); 102 | 103 | using X = move_only_function; 104 | using Y = move_only_function; 105 | using Xp = move_only_function; 106 | using Yp = move_only_function; 107 | 108 | static_assert(std::is_invocable_v); 109 | static_assert(not std::is_nothrow_invocable_v); 110 | static_assert(std::is_nothrow_invocable_v); 111 | 112 | static_assert(std::is_constructible_v); 113 | static_assert(std::is_constructible_v); 114 | static_assert(std::is_constructible_v); 115 | static_assert(not std::is_constructible_v); 116 | static_assert(std::is_constructible_v); 117 | static_assert(std::is_constructible_v, 118 | "pointer-to-data-member is deemed noexcept"); 119 | 120 | using T = move_only_function; 121 | using U = move_only_function; 122 | 123 | static_assert(std::is_constructible_v, A>); 124 | static_assert(not std::is_constructible_v, A>, 125 | "member function may throw"); 126 | static_assert(std::is_constructible_v, A_good>); 127 | static_assert(std::is_constructible_v, A_good>); 128 | static_assert(std::is_constructible_v, A>); 129 | static_assert(not std::is_constructible_v, A>, 130 | "explicit member function may throw"); 131 | static_assert(std::is_constructible_v, A>); 132 | static_assert(std::is_constructible_v, A>); 133 | -------------------------------------------------------------------------------- /tests/move_only_function/test_nontype.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | #include 4 | 5 | using T = move_only_function; 6 | 7 | suite nttp_callable = [] 8 | { 9 | using namespace bdd; 10 | 11 | feature("type-erase a bound instance method") = [] 12 | { 13 | given("an object without call operator") = [] 14 | { 15 | auto obj = std::make_optional(); 16 | 17 | when("binding a pointer-to-member to a pointer to the object") = [&] 18 | { 19 | T fn = {nontype<&A::data>, &obj.value()}; 20 | 21 | then("you can observe reference semantics") = [&] 22 | { 23 | obj->data = 27; 24 | expect(fn() == 27_i); 25 | }; 26 | 27 | when("rebinding a copy of the object") = [&] 28 | { 29 | fn = {nontype<&A::data>, obj}; 30 | 31 | then("you can observe value semantics instead") = [&] 32 | { 33 | obj->data = 84; 34 | expect(fn() == 27_i); 35 | }; 36 | 37 | static_assert(std::is_constructible_v, 38 | decltype(*obj)>); 39 | static_assert( 40 | not std::is_constructible_v, 41 | decltype(obj)>, 42 | "non-member does not dereference pointer-like objects"); 43 | using U = decltype(obj); 44 | static_assert( 45 | std::is_constructible_v< 46 | T, nontype_t<[](U const &x) { return h(*x); }>, U>, 47 | "...but you can DIY"); 48 | }; 49 | }; 50 | }; 51 | }; 52 | 53 | feature("type-erase an unbound instance method") = [] 54 | { 55 | given("a call wrapper and its argument") = [&] 56 | { 57 | move_only_function fn; 58 | A obj; 59 | 60 | when("move_only_function is assigned a pointer-to-member") = [&] 61 | { 62 | fn = nontype<&A::data>; 63 | 64 | then("it behaves as if it is memfn") = [&] 65 | { 66 | obj.data = 33; 67 | expect(fn(obj) == 33); 68 | }; 69 | }; 70 | 71 | when("move_only_function is assigned a function") = [&] 72 | { 73 | fn = nontype; 74 | 75 | then("it behaves as if it is the function") = [&] 76 | { expect(fn(obj) == free_function); }; 77 | }; 78 | }; 79 | }; 80 | }; 81 | 82 | using U = move_only_function; 83 | 84 | static_assert(std::is_nothrow_constructible_v>); 85 | static_assert(std::is_nothrow_constructible_v>, 86 | "unbounded cases are always noexcept"); 87 | static_assert(std::is_nothrow_assignable_v>); 88 | static_assert(std::is_nothrow_assignable_v>); 89 | 90 | static_assert(std::is_constructible_v, A>); 91 | static_assert(std::is_constructible_v, A>, 92 | "initializing bounded objects potentially throws"); 93 | 94 | // extension 95 | static_assert(std::is_nothrow_constructible_v, A *>, 96 | "...unless we stored a pointer"); 97 | static_assert(std::is_nothrow_constructible_v, 98 | std::reference_wrapper>, 99 | "...or reference_wrapper"); 100 | -------------------------------------------------------------------------------- /tests/move_only_function/test_nullable.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | suite nullable = [] 4 | { 5 | using namespace bdd; 6 | 7 | feature("move_only_function recognizes certain nullable callable objects") = 8 | [] 9 | { 10 | given("a non-empty move_only_function") = [] 11 | { 12 | move_only_function fn = f; 13 | expect(fn != nullptr); 14 | 15 | when("it is assigned from a null function pointer") = [&] 16 | { 17 | decltype(f) *p = {}; 18 | fn = p; 19 | 20 | then("it becomes empty") = [&] { expect(!fn); }; 21 | }; 22 | }; 23 | 24 | given("a move_only_function initialized from a null PMF") = [] 25 | { 26 | struct A 27 | { 28 | int data = 99; 29 | } a; 30 | int A::*pm = {}; 31 | move_only_function fn = pm; 32 | 33 | then("it is empty") = [&] { expect(!fn); }; 34 | 35 | when("it is assigned from a PM constant") = [&] 36 | { 37 | fn = &A::data; 38 | 39 | then("it is not empty") = [&] 40 | { 41 | expect(bool(fn)); 42 | expect(fn(a) == 99_i); 43 | }; 44 | }; 45 | }; 46 | 47 | given("an empty move_only_function") = [] 48 | { 49 | move_only_function fn; 50 | expect(!fn); 51 | 52 | when("it initialises a different specialization") = [&] 53 | { 54 | move_only_function fn2 = std::move(fn); 55 | 56 | then("that object becomes empty") = [&] { expect(!fn2); }; 57 | }; 58 | }; 59 | }; 60 | }; 61 | -------------------------------------------------------------------------------- /tests/move_only_function/test_reference_semantics.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | struct counter 4 | { 5 | int n = 0; 6 | void operator()() & { ++n; } 7 | }; 8 | 9 | suite reference_semantics = [] 10 | { 11 | using namespace bdd; 12 | 13 | feature("move_only_function supports reference_wrapper") = [] 14 | { 15 | given("a stateful callable object") = [] 16 | { 17 | counter obj; 18 | 19 | when("initializes move_only_function via refwrap") = [=]() mutable 20 | { 21 | move_only_function fn = std::ref(obj); 22 | 23 | then("calling the it affects the original object") = [&] 24 | { 25 | fn(); 26 | fn(); 27 | 28 | expect(obj.n == 2_i); 29 | }; 30 | 31 | given("a different stateful object") = [&] 32 | { 33 | counter rhs; 34 | 35 | when("swapping with a different move_only_function that " 36 | "refers to it") = [&] 37 | { 38 | move_only_function fn2( 39 | std23::in_place_type< 40 | std::reference_wrapper>, 41 | rhs); 42 | swap(fn, fn2); 43 | 44 | then("their references are swapped") = [&] 45 | { 46 | expect(obj.n == 2_i); 47 | expect(rhs.n == 0_i); 48 | 49 | fn(); 50 | expect(rhs.n == 1_i); 51 | 52 | fn2(); 53 | expect(obj.n == 3_i); 54 | }; 55 | }; 56 | }; 57 | }; 58 | 59 | when("initializes const-correct move_only_function") = [=]() mutable 60 | { 61 | obj.n = 3; 62 | move_only_function fn = std::ref(obj); 63 | 64 | then("const-correctness is broke by refwrap") = [&] 65 | { 66 | fn(); 67 | 68 | expect(obj.n == 4_i); 69 | }; 70 | 71 | using T = decltype(fn); 72 | static_assert(not std::is_constructible_v, 73 | "counter's call op is not const"); 74 | static_assert( 75 | std::is_constructible_v>, 76 | "refwrap's call op is unconditionally const"); 77 | static_assert(not std::is_constructible_v< 78 | T, std::reference_wrapper>, 79 | "refwrap's call op is constrained"); 80 | }; 81 | }; 82 | }; 83 | }; 84 | 85 | using T = move_only_function; 86 | using X = decltype([](int) {}); 87 | 88 | static_assert(std::is_constructible_v); 89 | static_assert(std::is_constructible_v>); 90 | static_assert(std::is_assignable_v); 91 | static_assert(std::is_constructible_v>); 92 | 93 | static_assert(std::is_constructible_v>); 94 | static_assert(std::is_constructible_v< 95 | T, std::in_place_type_t>, X &>); 96 | 97 | // extension 98 | static_assert(std::is_nothrow_constructible_v>); 99 | static_assert(std::is_nothrow_assignable_v>); 100 | static_assert(std::is_nothrow_constructible_v); 101 | static_assert(std::is_nothrow_constructible_v); 102 | static_assert(std::is_nothrow_assignable_v); 103 | static_assert(std::is_nothrow_assignable_v); 104 | 105 | static_assert(std::is_nothrow_constructible_v< 106 | T, std::in_place_type_t>, X &>); 107 | static_assert(std::is_nothrow_constructible_v< 108 | T, std::in_place_type_t, void(int)>); 109 | static_assert( 110 | std::is_nothrow_constructible_v, X>); 111 | -------------------------------------------------------------------------------- /tests/move_only_function/test_return_reference.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | template T &&identity_fn(T &&x) 4 | { 5 | return x; 6 | } 7 | 8 | template class refwrap 9 | { 10 | T &x; 11 | 12 | public: 13 | refwrap(T &obj) : x(obj) {} 14 | 15 | T &operator()() const { return x; } 16 | T &get() const { return x; } 17 | }; 18 | 19 | suite return_reference = [] 20 | { 21 | using namespace bdd; 22 | 23 | feature("reference return value") = [] 24 | { 25 | given("a variable") = [] 26 | { 27 | int x; 28 | move_only_function fn = identity_fn; 29 | expect(std::addressof(fn(x)) == &x); 30 | }; 31 | 32 | given("a data member") = [] 33 | { 34 | int x; 35 | refwrap obj = x; 36 | 37 | when("bind to a non-const wrapper") = [&] 38 | { 39 | move_only_function fn = obj; 40 | expect(std::addressof(fn()) == &x); 41 | }; 42 | 43 | when("bind to a const wrapper") = [&] 44 | { 45 | move_only_function fn = obj; 46 | expect(std::addressof(fn()) == &x); 47 | }; 48 | }; 49 | 50 | given("a pointer to member object") = [] 51 | { 52 | int x; 53 | refwrap obj = x; 54 | 55 | when("used as an unbound method") = [&] 56 | { 57 | move_only_function const &)> fn = 58 | nontype<&refwrap::get>; 59 | expect(std::addressof(fn(obj)) == &x); 60 | }; 61 | 62 | when("used as a non-const bound method") = [&] 63 | { 64 | move_only_function fn = {nontype<&refwrap::get>, 65 | obj}; 66 | expect(std::addressof(fn()) == &x); 67 | }; 68 | 69 | when("used as a const bound method") = [&] 70 | { 71 | move_only_function fn = { 72 | nontype<&refwrap::get>, obj}; 73 | expect(std::addressof(fn()) == &x); 74 | }; 75 | }; 76 | }; 77 | }; 78 | -------------------------------------------------------------------------------- /tests/move_only_function/test_unique.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class inlined_fixed_string 9 | { 10 | public: 11 | inlined_fixed_string() = delete; 12 | 13 | auto size() const { return size_; } 14 | auto data() const 15 | { 16 | return reinterpret_cast(std::next(this)); 17 | } 18 | 19 | auto slice(size_t beg, size_t ed) const & 20 | { 21 | return std::string_view(data(), size()).substr(beg, ed - beg); 22 | } 23 | 24 | static auto make(std::string_view s) 25 | { 26 | size_t full_size = sizeof(inlined_fixed_string) + s.size(); 27 | return new (::operator new(full_size)) 28 | inlined_fixed_string(s.data(), s.size()); 29 | } 30 | 31 | static auto make_unique(std::string_view s) 32 | { 33 | return std::unique_ptr(make(s)); 34 | } 35 | 36 | private: 37 | inlined_fixed_string(char const *p, size_t sz) : size_(sz) 38 | { 39 | std::memcpy(std::next(this), p, sz); 40 | } 41 | 42 | ~inlined_fixed_string() = default; 43 | 44 | void operator delete(inlined_fixed_string *s, std::destroying_delete_t) 45 | { 46 | size_t full_size = sizeof(inlined_fixed_string) + s->size(); 47 | s->~inlined_fixed_string(); 48 | ::operator delete(s, full_size); 49 | } 50 | 51 | friend std::default_delete; 52 | 53 | size_t size_; 54 | }; 55 | 56 | static_assert(not std::is_default_constructible_v); 57 | static_assert(not std::is_move_constructible_v); 58 | static_assert(not std::is_destructible_v); 59 | 60 | using T = move_only_function; 61 | 62 | suite unique_callable = [] 63 | { 64 | using namespace bdd; 65 | using namespace std::literals; 66 | 67 | feature("type-erase a boxed bound instance method") = [] 68 | { 69 | given("a move_only_function storing a unique_ptr") = [] 70 | { 71 | T fn(nontype<&inlined_fixed_string::slice>, 72 | inlined_fixed_string::make_unique("coffee engineering"sv)); 73 | 74 | when("calling the wrapper") = [&] 75 | { 76 | then("the object works as an lvalue") = [&] 77 | { expect(fn(0, 6) == "coffee"sv); }; 78 | }; 79 | 80 | when("moving the wrapper") = [fn2 = std::move(fn)] 81 | { 82 | then("the object behaves as if it is boxed") = [&] 83 | { expect(fn2(7, 10) == "eng"sv); }; 84 | }; 85 | }; 86 | 87 | given("a move_only_function in-place constructed a unique_ptr") = [] 88 | { 89 | T fn(nontype<&inlined_fixed_string::slice>, 90 | std::in_place_type>, 91 | inlined_fixed_string::make("destroying delete"sv)); 92 | 93 | when("moving the wrapper") = [&] 94 | { 95 | T fn2 = std::move(fn); 96 | 97 | then("the object behaves as if it is boxed") = [&] 98 | { expect(fn2(11, 17) == "delete"sv); }; 99 | }; 100 | }; 101 | }; 102 | }; 103 | 104 | static_assert( 105 | std::is_constructible_v, 106 | std::unique_ptr>); 107 | static_assert( 108 | not std::is_constructible_v, 109 | std::unique_ptr &>, 110 | "users cannot construct the same wrapper from an lvalue"); 111 | 112 | static_assert(std::is_constructible_v< 113 | T, nontype_t<&inlined_fixed_string::slice>, 114 | std::in_place_type_t>, 115 | inlined_fixed_string *>); 116 | static_assert(not std::is_constructible_v< 117 | T, nontype_t<&inlined_fixed_string::slice>, 118 | std::in_place_type_t>, 119 | std::unique_ptr &>, 120 | "move-only type cannot be in-place constructed from lvalue"); 121 | -------------------------------------------------------------------------------- /tests/move_only_function/test_value_semantics.cpp: -------------------------------------------------------------------------------- 1 | #include "common_callables.h" 2 | 3 | #include 4 | 5 | class move_counter 6 | { 7 | std::unique_ptr n_ = std::make_unique(0); 8 | 9 | public: 10 | int operator()() & { return (*n_)++; } 11 | }; 12 | 13 | suite value_semantics = [] 14 | { 15 | using namespace bdd; 16 | 17 | given("a stateful move_only_function") = [] 18 | { 19 | move_only_function fn = move_counter(); 20 | 21 | when("it holds some state") = [&] 22 | { 23 | fn(); 24 | fn(); 25 | expect(fn() == 2_i); 26 | 27 | then("the object can be self-swapped") = [&] 28 | { 29 | swap(fn, fn); 30 | 31 | expect(fn != nullptr); 32 | expect(fn() == 3_i); 33 | }; 34 | 35 | when("moving from the object") = [&] 36 | { 37 | auto fn2 = std::move(fn); 38 | 39 | then("the new object inherits the state") = [&] 40 | { expect(fn2() == 4_i); }; 41 | 42 | then("self-move does not leak") = [&] 43 | { 44 | fn2 = std::move(fn2); 45 | 46 | expect(fn2 != nullptr); // extension 47 | }; 48 | }; 49 | }; 50 | }; 51 | 52 | given("two stateful move_only_function objects") = [] 53 | { 54 | move_only_function fn1 = move_counter(); 55 | move_only_function fn2 = move_counter(); 56 | 57 | when("each holds different state") = [&] 58 | { 59 | fn1(); 60 | expect(fn1() == 1_i); 61 | 62 | fn2(); 63 | fn2(); 64 | fn2(); 65 | expect(fn2() == 3_i); 66 | 67 | then("swapping them exchanges their states") = [&] 68 | { 69 | swap(fn1, fn2); 70 | 71 | expect(fn1() == 4_i); 72 | expect(fn2() == 2_i); 73 | }; 74 | }; 75 | }; 76 | }; 77 | 78 | using T = move_only_function; 79 | using R = T::result_type; 80 | 81 | static_assert(std::is_nothrow_default_constructible_v); 82 | static_assert(std::is_nothrow_constructible_v); 83 | static_assert(not std::is_copy_constructible_v); 84 | static_assert(not std::is_copy_assignable_v); 85 | static_assert(std::is_nothrow_assignable_v); 86 | static_assert(std::is_nothrow_move_constructible_v); 87 | static_assert(std::is_nothrow_move_assignable_v); 88 | static_assert(std::is_nothrow_swappable_v); 89 | 90 | static_assert(std::is_same_v, R>); 91 | 92 | struct reject_rvalue 93 | { 94 | reject_rvalue(reject_rvalue &) = default; 95 | 96 | void operator()(int) {} 97 | void mf(int) {} 98 | }; 99 | 100 | static_assert(not std::is_move_constructible_v); 101 | static_assert(std::is_invocable_r_v); 102 | 103 | static_assert(std::is_constructible_v); 104 | static_assert(not std::is_constructible_v, 105 | "target object must be initialized"); 106 | 107 | static_assert( 108 | std::is_constructible_v, reject_rvalue &>); 109 | static_assert(not std::is_constructible_v, 110 | reject_rvalue>, 111 | "bounded target object must be initialized"); 112 | --------------------------------------------------------------------------------