├── .gitattributes ├── tests ├── NatvisTests.h ├── ExerciseOptionalAIP.h ├── ExerciseOptionalInplace.h ├── ExerciseStdOptional.h ├── ExerciseOptionalEmptyViaType.h ├── ComparisonTests.h ├── IntermediateTests.h ├── CompilationErrorTests.h ├── make_win_clang.ps1 ├── SpecialMonadicTests.h ├── ConstructionTests.h ├── ExerciseTinyOptionalPayload.h ├── GccLikeCompilation.h ├── MsvcCompilation.h ├── CompilationBase.h ├── ExerciseOptionalWithCustomFlagManipulator.h ├── ExerciseOptionalAIP.cpp ├── ExerciseStdOptional.cpp ├── GccLikeCompilation.cpp ├── ExerciseOptionalInplace.cpp ├── MsvcCompilation.cpp ├── ExerciseOptionalEmptyViaType.cpp ├── CompilationBase.cpp ├── make_win_gcclike.ps1 ├── Tests.cpp ├── SpecialMonadicTests.cpp ├── tiny-optional.sln ├── Tests.vcxproj.filters ├── IntermediateTests.cpp ├── makefile ├── TestTypes.h ├── CompilationErrorTests.cpp ├── NatvisTests.cpp ├── ExerciseTinyOptionalPayload1.cpp ├── ComparisonTests.cpp ├── TestUtilities.cpp ├── TestUtilities.h ├── ExerciseTinyOptionalPayload2.cpp └── ExerciseOptionalWithCustomFlagManipulator.cpp ├── performance ├── results_absolute.png ├── results_relative.png ├── makefile ├── performance.vcxproj.filters ├── performance.sln ├── plot.plt ├── results_gcc.dat ├── results_clang.dat ├── results_msvc.dat ├── performance.vcxproj └── main.cpp ├── cmake └── tiny-optionalConfig.cmake.in ├── .github └── workflows │ ├── test_gcc_win.yml │ ├── test_clang_win.yml │ ├── test_msvc_win.yml │ ├── test_gcc_linux.yml │ ├── test_clang_mac.yml │ └── test_clang_linux.yml ├── LICENSE ├── include ├── tiny │ └── optional_flag_manipulator_fwd.h └── tiny_optional.natvis ├── CMakeLists.txt ├── .clang-format └── .gitignore /.gitattributes: -------------------------------------------------------------------------------- 1 | * -text 2 | -------------------------------------------------------------------------------- /tests/NatvisTests.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void test_Natvis(); 4 | -------------------------------------------------------------------------------- /tests/ExerciseOptionalAIP.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void test_OptionalAIP(); 4 | -------------------------------------------------------------------------------- /tests/ExerciseOptionalInplace.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void test_OptionalInplace(); 4 | -------------------------------------------------------------------------------- /tests/ExerciseStdOptional.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void test_CrosscheckStdOptional(); 4 | -------------------------------------------------------------------------------- /tests/ExerciseOptionalEmptyViaType.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void test_OptionalEmptyViaType(); 4 | -------------------------------------------------------------------------------- /tests/ComparisonTests.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Tests operator==, <=, etc. 4 | void test_Comparisons(); 5 | -------------------------------------------------------------------------------- /performance/results_absolute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sedeniono/tiny-optional/HEAD/performance/results_absolute.png -------------------------------------------------------------------------------- /performance/results_relative.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sedeniono/tiny-optional/HEAD/performance/results_relative.png -------------------------------------------------------------------------------- /tests/IntermediateTests.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void test_IsIntegralInRange(); 4 | 5 | void test_NanExploit(); 6 | 7 | void test_SelectDecomposition(); 8 | -------------------------------------------------------------------------------- /cmake/tiny-optionalConfig.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") 4 | check_required_components("@PROJECT_NAME@") 5 | -------------------------------------------------------------------------------- /tests/CompilationErrorTests.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Tests various expressions that should NOT compile. 4 | // This is done by actually fireing up a compiler and checking its output. 5 | void test_ExpressionsThatShouldNotCompile(); 6 | -------------------------------------------------------------------------------- /tests/make_win_clang.ps1: -------------------------------------------------------------------------------- 1 | # Helper script to run the clang windows build locally. 2 | 3 | $CXX = "clang++" 4 | $ADDITIONAL_FLAGS = "-m64", "-std=c++20" 5 | $OUT_DIR_NAME = "win_clang_x64_cpp20_debug" 6 | 7 | $script = $PSScriptRoot + "\make_win_gcclike.ps1" 8 | & $script 9 | -------------------------------------------------------------------------------- /performance/makefile: -------------------------------------------------------------------------------- 1 | clang: main.cpp 2 | clang++ -Wall -Wextra -pedantic -std=c++17 -O3 -DNDEBUG -mavx -I../include -o clangPerf $? 3 | ./clangPerf 4 | 5 | gcc: main.cpp 6 | g++ -Wall -Wextra -pedantic -std=c++17 -O3 -DNDEBUG -mavx -I../include -o gccPerf $? 7 | ./gccPerf 8 | -------------------------------------------------------------------------------- /tests/SpecialMonadicTests.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // General tests of the monadic operations are in the "exercises" (EXERCISE_OPTIONAL etc). 4 | // But we need to tests a few additional things that do not fit there. 5 | void test_SpecialTestsFor_and_then(); 6 | void test_SpecialTestsFor_transform(); 7 | void test_SpecialTestsFor_or_else(); 8 | -------------------------------------------------------------------------------- /tests/ConstructionTests.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void test_TinyOptionalMemoryManagement(); 4 | 5 | void test_Exceptions(); 6 | 7 | void test_TinyOptionalCopyConstruction(); 8 | void test_TinyOptionalMoveConstruction(); 9 | void test_TinyOptionalCopyAssignment(); 10 | void test_TinyOptionalMoveAssignment(); 11 | 12 | void test_TinyOptionalDestruction(); 13 | 14 | void test_TinyOptionalConversions(); 15 | 16 | void test_MakeOptional(); 17 | -------------------------------------------------------------------------------- /tests/ExerciseTinyOptionalPayload.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Tests for various payload types. 4 | // Note: Implementations split into separate .cpp files because the object file became larger than 5 | // what mingw could handle. 6 | void test_TinyOptionalPayload_Bool(); 7 | void test_TinyOptionalPayload_FloatingPoint(); 8 | void test_TinyOptionalPayload_IntegersAndEnums(); 9 | void test_TinyOptionalPayload_IsEmptyFlagInMember(); 10 | void test_TinyOptionalPayload_Pointers(); 11 | void test_TinyOptionalPayload_StdTypes(); 12 | void test_TinyOptionalPayload_NestedOptionals(); 13 | void test_TinyOptionalPayload_ConstAndVolatile(); 14 | void test_TinyOptionalPayload_Cpp20NTTP(); 15 | void test_TinyOptionalPayload_WindowsHandles(); 16 | void test_TinyOptionalPayload_OtherTypes(); 17 | -------------------------------------------------------------------------------- /tests/GccLikeCompilation.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CompilationBase.h" 4 | 5 | #include 6 | 7 | // Compilers that follow gcc's syntax (gcc, clang, ...) 8 | class GccLikeCompilationChecks final : public CompilationChecksBase 9 | { 10 | public: 11 | explicit GccLikeCompilationChecks( 12 | std::filesystem::path const & executable, 13 | std::filesystem::path const & tinyOptionalIncludeDir, 14 | std::string const & compilationFlags); 15 | 16 | virtual void PrintInputOptions(std::ostream & out) const override; 17 | 18 | protected: 19 | virtual ExecutionResult PerformCompilation(std::string const & code) const override; 20 | 21 | private: 22 | std::filesystem::path mExecutable; 23 | std::filesystem::path mTinyOptionalIncludeDir; 24 | std::filesystem::path mTinyOptionalIncludeDirCanon; 25 | std::string const mCompilationFlags; 26 | }; 27 | -------------------------------------------------------------------------------- /tests/MsvcCompilation.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "TestUtilities.h" 4 | 5 | #ifdef TINY_OPTIONAL_WINDOWS_BUILD 6 | #include "CompilationBase.h" 7 | 8 | #include 9 | #endif 10 | 11 | // Microsoft MSVC is only available on Windows, and we need to use Windows specific functions to run it. So compile it 12 | // only on Windows. 13 | #ifdef TINY_OPTIONAL_WINDOWS_BUILD 14 | class MsvcCompilationChecks final : public CompilationChecksBase 15 | { 16 | public: 17 | explicit MsvcCompilationChecks( 18 | std::filesystem::path const & vcvarsBatFile, 19 | std::filesystem::path const & tinyOptionalIncludeDir, 20 | std::string const & compilationFlags); 21 | 22 | virtual void PrintInputOptions(std::ostream & out) const override; 23 | 24 | protected: 25 | virtual ExecutionResult PerformCompilation(std::string const & code) const override; 26 | 27 | private: 28 | std::filesystem::path mVcvarsBatFile; 29 | std::filesystem::path mTinyOptionalIncludeDir; 30 | std::string const mCompilationFlags; 31 | }; 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /performance/performance.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/CompilationBase.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | struct ExecutionResult; 8 | 9 | class CompilationChecksBase 10 | { 11 | public: 12 | virtual ~CompilationChecksBase() = default; 13 | 14 | // Test that the code snippet does not compile and that the error message contains the expected regex expression. 15 | // Returns an empty string in case this test succeeds (i.e. in case it did not compile as expected). 16 | // Otherwise, returns a string containing what was done (so that one can figure out why the test failed). 17 | std::string CheckDoesNotCompile(std::string const & codeSnippet, std::string const & expectedRegex) const; 18 | 19 | virtual void PrintInputOptions(std::ostream & out) const = 0; 20 | 21 | protected: 22 | virtual ExecutionResult PerformCompilation(std::string const & code) const = 0; 23 | 24 | private: 25 | static std::string FormatInfo(std::string const & code, ExecutionResult const & result); 26 | }; 27 | 28 | 29 | std::unique_ptr CreateCompilationChecker(); 30 | -------------------------------------------------------------------------------- /performance/performance.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.32602.291 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "performance", "performance.vcxproj", "{92817F05-479C-4D77-A84C-0485E3AC135F}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Release|x64 = Release|x64 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {92817F05-479C-4D77-A84C-0485E3AC135F}.Debug|x64.ActiveCfg = Debug|x64 15 | {92817F05-479C-4D77-A84C-0485E3AC135F}.Debug|x64.Build.0 = Debug|x64 16 | {92817F05-479C-4D77-A84C-0485E3AC135F}.Release|x64.ActiveCfg = Release|x64 17 | {92817F05-479C-4D77-A84C-0485E3AC135F}.Release|x64.Build.0 = Release|x64 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {22150103-6774-463D-AD65-87227056B801} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /.github/workflows/test_gcc_win.yml: -------------------------------------------------------------------------------- 1 | name: gcc windows 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | schedule: 8 | - cron: '20 3 * * *' 9 | 10 | 11 | env: 12 | TEST_DIR: tests 13 | 14 | 15 | jobs: 16 | all: 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | cpp_version: [c++17, c++20] 21 | # Note: We are never building without optimizations because mingw complains that the object 22 | # files become too big. 23 | buildmode: [-O2, -O3 -DNDEBUG, -O3 -DNDEBUG -ffast-math] 24 | 25 | runs-on: windows-2022 26 | timeout-minutes: 15 27 | 28 | defaults: 29 | run: 30 | shell: msys2 {0} 31 | 32 | steps: 33 | - uses: actions/checkout@v4 34 | - uses: msys2/setup-msys2@v2.22.0 35 | with: 36 | msystem: mingw64 37 | install: mingw-w64-x86_64-toolchain make 38 | - name: Print compiler version 39 | run: g++ --version 40 | - name: Build and run tests 41 | working-directory: ${{env.TEST_DIR}} 42 | env: 43 | CXX: g++ 44 | ADDITIONAL_FLAGS: ${{ format('-Wa,-mbig-obj -std={0} {1}', matrix.cpp_version, matrix.buildmode) }} 45 | IS_MSYS: 1 46 | run: make generic 2>&1 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /.github/workflows/test_clang_win.yml: -------------------------------------------------------------------------------- 1 | name: clang windows 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | schedule: 8 | - cron: '20 3 * * *' 9 | 10 | 11 | env: 12 | TEST_DIR: tests 13 | 14 | 15 | jobs: 16 | all: 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | # Earlier versions are not supported by the installed Microsoft STL that is used by clang. 21 | clang_version: [19, 20, 21] 22 | cpp_version: [c++17, c++20] 23 | buildmode: [~ , -O3 -DNDEBUG, -O3 -DNDEBUG -ffast-math] 24 | 25 | # Also include a few variations with disabled UB tricks (not for all to limit resource consumption). 26 | include: 27 | - clang_version: 20 28 | cpp_version: c++20 29 | buildmode: -DTINY_OPTIONAL_USE_SEPARATE_BOOL_INSTEAD_OF_UB_TRICKS 30 | 31 | runs-on: windows-2022 32 | timeout-minutes: 10 33 | 34 | steps: 35 | - uses: actions/checkout@v4 36 | - name: Install clang 37 | uses: KyleMayes/install-llvm-action@v2.0.8 38 | with: 39 | version: ${{ matrix.clang_version }} 40 | - name: Build and run tests 41 | working-directory: ${{env.TEST_DIR}} 42 | env: 43 | CXX: clang++ 44 | ADDITIONAL_FLAGS: ${{ format('-std={0} {1}', matrix.cpp_version, matrix.buildmode) }} 45 | OUT_DIR_NAME: win_clang_generic 46 | run: | 47 | & ".\make_win_gcclike.ps1" 2>&1 48 | -------------------------------------------------------------------------------- /tests/ExerciseOptionalWithCustomFlagManipulator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "tiny/optional_flag_manipulator_fwd.h" 4 | 5 | #include 6 | 7 | 8 | void test_TinyOptionalWithRegisteredCustomFlagManipulator(); 9 | 10 | 11 | //============================================================== 12 | // ClassInHeader: Type used in tests. Especially: The full "tiny/optional.h" header is not included. 13 | // Additionally, the class is templated, so optional_flag_manipulator needs to have a template argument, too. 14 | //============================================================== 15 | 16 | template 17 | struct ClassInHeader 18 | { 19 | bool isEmpty = false; 20 | T * ptr = nullptr; 21 | 22 | friend bool operator==(ClassInHeader const & lhs, ClassInHeader const & rhs) 23 | { 24 | return lhs.isEmpty == rhs.isEmpty && lhs.ptr == rhs.ptr; 25 | } 26 | }; 27 | 28 | 29 | template 30 | struct tiny::optional_flag_manipulator> 31 | { 32 | static bool is_empty(ClassInHeader const & payload) noexcept 33 | { 34 | return payload.isEmpty; 35 | } 36 | 37 | static void init_empty_flag(ClassInHeader & uninitializedPayloadMemory) noexcept 38 | { 39 | ::new (&uninitializedPayloadMemory) ClassInHeader(); 40 | uninitializedPayloadMemory.isEmpty = true; 41 | } 42 | 43 | static void invalidate_empty_flag(ClassInHeader & emptyPayload) noexcept 44 | { 45 | emptyPayload.~ClassInHeader(); 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /performance/plot.plt: -------------------------------------------------------------------------------- 1 | set terminal pngcairo size 1024,500 2 | 3 | set key top left 4 | 5 | set output "results_relative.png" 6 | set logscale x 7 | set xlabel "Number of values" 8 | set yrange [0:] 9 | set ylabel "Ratio of execution time: std/tiny\n(>1 means tiny is faster)" 10 | 11 | set arrow from 1024, graph 0 to 1024, graph 1 nohead 12 | set label "L1 " at 1024, graph 0.95 right 13 | 14 | set arrow from 4096, graph 0 to 4096, graph 1 nohead 15 | set label "L2 " at 4096, graph 0.95 right 16 | 17 | set arrow from 131072, graph 0 to 131072, graph 1 nohead 18 | set label "L3 " at 131072, graph 0.95 right 19 | 20 | plot \ 21 | "results_msvc.dat" u 2:6 w lp lw 3 lt 7 lc "red" title "MSVC", \ 22 | "results_clang.dat" u 2:6 w lp lw 3 lt 7 lc "black" title "clang", \ 23 | "results_gcc.dat" u 2:6 w lp lw 3 lt 7 lc "blue" title "gcc", \ 24 | 1 lw 1 lc "black" notitle 25 | set output 26 | 27 | 28 | 29 | set output "results_absolute.png" 30 | set logscale x 31 | set xlabel "numVals" 32 | set yrange [*:] 33 | set ylabel "Duration [s]" 34 | 35 | plot \ 36 | "results_msvc.dat" u 2:4 w lp lw 3 lt 7 lc "red" title "msvc std", \ 37 | "results_msvc.dat" u 2:5 w lp lw 3 lt 7 lc "orange" dt "-" title "msvc tiny", \ 38 | "results_clang.dat" u 2:4 w lp lw 3 lt 7 lc "black" title "clang std", \ 39 | "results_clang.dat" u 2:5 w lp lw 3 lt 7 lc "gray30" dt "-" title "clang tiny", \ 40 | "results_gcc.dat" u 2:4 w lp lw 3 lt 7 lc "blue" title "gcc std", \ 41 | "results_gcc.dat" u 2:5 w lp lw 3 lt 7 lc "royalblue" dt "-" title "gcc tiny" 42 | set output 43 | -------------------------------------------------------------------------------- /.github/workflows/test_msvc_win.yml: -------------------------------------------------------------------------------- 1 | name: msvc windows 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | schedule: 8 | - cron: '20 3 * * *' 9 | 10 | 11 | env: 12 | TEST_DIR: tests 13 | 14 | 15 | jobs: 16 | all: 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | platform: [x64, Win32] 21 | 22 | # The values of 'cpp_version' and 'buildmode' are used to build the name of the configuration 23 | # in the solution file. In other words, the values do not correspond directly to compiler 24 | # switches but instead to parts of the configuration names from the .sln file. 25 | cpp_version: [C++17, C++20] 26 | buildmode: [Debug, Release] 27 | 28 | include: 29 | - cpp_version: "C++17 permissive" 30 | platform: x64 31 | buildmode: Debug 32 | - cpp_version: "C++20 permissive" 33 | platform: x64 34 | buildmode: Debug 35 | # Also include a variation with disabled UB tricks (not for all to limit resource consumption). 36 | - cpp_version: "C++20 No UB" 37 | platform: x64 38 | buildmode: Debug 39 | 40 | runs-on: windows-2022 41 | timeout-minutes: 10 42 | 43 | steps: 44 | - uses: actions/checkout@v4 45 | - name: Add MSBuild to PATH 46 | uses: microsoft/setup-msbuild@v2 47 | - name: Build 48 | working-directory: ${{env.TEST_DIR}} 49 | run: msbuild /m /p:Configuration="Msvc ${{ matrix.buildmode }} ${{ matrix.cpp_version }}" /p:Platform=${{matrix.platform}} tiny-optional.sln 50 | - name: Execute 51 | working-directory: ${{env.TEST_DIR}} 52 | run: | 53 | & "../bin/Msvc ${{ matrix.buildmode }} ${{ matrix.cpp_version }} ${{matrix.platform}}/Tests.exe" 2>&1 54 | -------------------------------------------------------------------------------- /performance/results_gcc.dat: -------------------------------------------------------------------------------- 1 | Modulo numVals Nullopts std[s] tiny[s] std/tiny 2 | 8192 1 0 28.558 29.119 0.9807 3 | 8192 2 0 17.726 17.148 1.034 4 | 8192 3 0 19.725 18.998 1.038 5 | 8192 4 0 16.634 13.922 1.195 6 | 8192 8 0 19.674 18.025 1.091 7 | 8192 32 0 14.75 13.45 1.097 8 | 8192 128 0 15.907 16.202 0.9818 9 | 8192 512 0 11.887 12.215 0.9731 10 | 8192 1024 0 16.442 11.44 1.437 11 | 8192 2048 0 16.915 11.386 1.486 12 | 8192 4096 0 21.966 15.429 1.424 13 | 8192 8192 0 23.03 15.59 1.477 14 | 8192 16384 8.14e-05 31.182 17.913 1.741 15 | 8192 32768 0.000132 32.354 17.974 1.8 16 | 8192 65536 0.000127 30.496 16.172 1.886 17 | 8192 80000 0.000146 32.565 18.273 1.782 18 | 8192 98304 0.000119 40.278 19.418 2.074 19 | 8192 110000 0.000118 53.023 17.301 3.065 20 | 8192 131072 0.000122 43.541 17.405 2.502 21 | 8192 196608 0.000132 73.419 26.167 2.806 22 | 8192 262144 0.000123 57.691 22.658 2.546 23 | 8192 393216 0.000131 71.078 31.703 2.242 24 | 8192 524288 0.000135 61.95 29.645 2.09 25 | 8192 786432 0.000126 49.395 24.382 2.026 26 | 8192 1048576 0.000131 26.738 13.337 2.005 27 | 8192 2097152 0.000133 25.599 13.279 1.928 28 | 8192 5000000 0.000127 30.954 16.618 1.863 29 | 8192 10000000 0.000126 24.696 13.334 1.852 30 | -------------------------------------------------------------------------------- /performance/results_clang.dat: -------------------------------------------------------------------------------- 1 | Modulo numVals Nullopts std[s] tiny[s] std/tiny 2 | 8192 1 0 31.884 35.62 0.8951 3 | 8192 2 0 21.708 20.647 1.051 4 | 8192 3 0 23.561 22.49 1.048 5 | 8192 4 0 20.31 21.566 0.9418 6 | 8192 8 0 22.755 23.495 0.9685 7 | 8192 32 0 15.418 16.265 0.9479 8 | 8192 128 0 18.884 17.697 1.067 9 | 8192 512 0 15.171 15.192 0.9986 10 | 8192 1024 0 20.486 13.913 1.472 11 | 8192 2048 0 17.989 14.523 1.239 12 | 8192 4096 0 24.994 17.48 1.43 13 | 8192 8192 0 23.467 19.142 1.226 14 | 8192 16384 8.14e-05 30.883 24.867 1.242 15 | 8192 32768 0.000132 31.623 20.124 1.571 16 | 8192 65536 0.000127 30.429 16.3 1.867 17 | 8192 80000 0.000146 29.946 18.34 1.633 18 | 8192 98304 0.000119 36.461 20.155 1.809 19 | 8192 110000 0.000118 38.26 20.275 1.887 20 | 8192 131072 0.000122 43.186 20.362 2.121 21 | 8192 196608 0.000132 62.219 25.273 2.462 22 | 8192 262144 0.000123 55.715 23.886 2.333 23 | 8192 393216 0.000131 78.083 31.986 2.441 24 | 8192 524288 0.000135 62.341 34.52 1.806 25 | 8192 786432 0.000126 55.636 27.179 2.047 26 | 8192 1048576 0.000131 28.454 14.959 1.902 27 | 8192 2097152 0.000133 27.638 15.302 1.806 28 | 8192 5000000 0.000127 31.774 15.839 2.006 29 | 8192 10000000 0.000126 24.68 13.352 1.848 30 | -------------------------------------------------------------------------------- /performance/results_msvc.dat: -------------------------------------------------------------------------------- 1 | Modulo numVals Nullopts std[s] tiny[s] std/tiny 2 | 8192 1 0 26.501 22.201 1.194 3 | 8192 2 0 16.63 13.28 1.252 4 | 8192 3 0 20.343 16.231 1.253 5 | 8192 4 0 17.334 13.173 1.316 6 | 8192 8 0 21.608 16.489 1.31 7 | 8192 32 0 12.928 12.163 1.063 8 | 8192 128 0 12.677 12.579 1.008 9 | 8192 512 0 12.324 10.934 1.127 10 | 8192 1024 0 14.057 10.959 1.283 11 | 8192 2048 0 12.824 10.648 1.204 12 | 8192 4096 0 15.428 11.862 1.301 13 | 8192 8192 0 19.511 11.943 1.634 14 | 8192 16384 8.14e-05 25.633 17.279 1.483 15 | 8192 32768 0.000132 25.66 16.823 1.525 16 | 8192 65536 0.000127 20.405 13.354 1.528 17 | 8192 80000 0.000146 22.113 14.325 1.544 18 | 8192 98304 0.000119 25.381 14.937 1.699 19 | 8192 110000 0.000118 26.462 14.834 1.784 20 | 8192 131072 0.000122 31.844 13.934 2.285 21 | 8192 196608 0.000132 50.365 17.422 2.891 22 | 8192 262144 0.000123 51.178 18.918 2.705 23 | 8192 393216 0.000131 61.371 26.752 2.294 24 | 8192 524288 0.000135 55.247 26.814 2.06 25 | 8192 786432 0.000126 41.798 21.32 1.961 26 | 8192 1048576 0.000131 22.467 11.673 1.925 27 | 8192 2097152 0.000133 22.718 11.796 1.926 28 | 8192 5000000 0.000127 27.275 14.261 1.913 29 | 8192 10000000 0.000126 21.831 11.302 1.931 30 | -------------------------------------------------------------------------------- /tests/ExerciseOptionalAIP.cpp: -------------------------------------------------------------------------------- 1 | #include "ExerciseOptionalAIP.h" 2 | 3 | #include "Exercises.h" 4 | #include "TestTypes.h" 5 | #include "tiny/optional.h" 6 | 7 | void test_OptionalAIP() 8 | { 9 | //---------------------- 10 | // Tests with automatic selection of the sentinel 11 | 12 | #ifndef TINY_OPTIONAL_USE_SEPARATE_BOOL_INSTEAD_OF_UNUSED_BITS 13 | EXERCISE_OPTIONAL((tiny::optional_aip{}), EXPECT_INPLACE, 43.0, 44.0); 14 | #endif 15 | 16 | EXERCISE_OPTIONAL((tiny::optional_aip{}), EXPECT_INPLACE, static_cast(-10), static_cast(42)); 17 | EXERCISE_OPTIONAL( 18 | (tiny::optional_aip{}), 19 | EXPECT_INPLACE, 20 | static_cast(10), 21 | static_cast(42)); 22 | 23 | EXERCISE_OPTIONAL((tiny::optional_aip{}), EXPECT_INPLACE, -10, 42); 24 | EXERCISE_OPTIONAL((tiny::optional_aip{}), EXPECT_INPLACE, 10u, 42u); 25 | 26 | EXERCISE_OPTIONAL((tiny::optional_aip{}), EXPECT_INPLACE, -10l, 42l); 27 | EXERCISE_OPTIONAL((tiny::optional_aip{}), EXPECT_INPLACE, 10ul, 42ul); 28 | 29 | EXERCISE_OPTIONAL((tiny::optional_aip{}), EXPECT_INPLACE, -10ll, 42ll); 30 | EXERCISE_OPTIONAL((tiny::optional_aip{}), EXPECT_INPLACE, 10ull, 42ull); 31 | 32 | #ifndef TINY_OPTIONAL_USE_SEPARATE_BOOL_INSTEAD_OF_UNUSED_BITS 33 | TestClass c1, c2; 34 | EXERCISE_OPTIONAL((tiny::optional_aip{}), EXPECT_INPLACE, &c1, &c2); 35 | #endif 36 | 37 | //---------------------- 38 | // Tests with manual specification of sentinel 39 | 40 | EXERCISE_OPTIONAL((tiny::optional_aip{}), EXPECT_INPLACE, INT_MIN, INT_MAX); 41 | 42 | EXERCISE_OPTIONAL((tiny::optional_aip{}), EXPECT_INPLACE, 'b', 'c'); 43 | EXERCISE_OPTIONAL( 44 | (tiny::optional_aip{}), 45 | EXPECT_INPLACE, 46 | ScopedEnum::v2, 47 | ScopedEnum::v1); 48 | } 49 | -------------------------------------------------------------------------------- /include/tiny/optional_flag_manipulator_fwd.h: -------------------------------------------------------------------------------- 1 | #ifndef TINY_OPTIONAL_FLAG_MANIPULATOR_ALREADY_DECLARED 2 | #define TINY_OPTIONAL_FLAG_MANIPULATOR_ALREADY_DECLARED 3 | 4 | /* 5 | Boost Software License - Version 1.0 - August 17th, 2003 6 | 7 | Permission is hereby granted, free of charge, to any person or organization 8 | obtaining a copy of the software and accompanying documentation covered by 9 | this license (the "Software") to use, reproduce, display, distribute, 10 | execute, and transmit the Software, and to prepare derivative works of the 11 | Software, and to permit third-parties to whom the Software is furnished to 12 | do so, all subject to the following: 13 | 14 | The copyright notices in the Software and this entire statement, including 15 | the above license grant, this restriction and the following disclaimer, 16 | must be included in all copies of the Software, in whole or in part, and 17 | all derivative works of the Software, unless such copies or derivative 18 | works are solely in the form of machine-executable object code generated by 19 | a source language processor. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 24 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 25 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 26 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 27 | DEALINGS IN THE SOFTWARE. 28 | 29 | ------ 30 | 31 | Original repository: https://github.com/Sedeniono/tiny-optional 32 | */ 33 | 34 | namespace tiny 35 | { 36 | // Forward declaration of optional_flag_manipulator, which is a user customization point. 37 | // See README.md and corresponding definition in optional.h. 38 | template 39 | struct optional_flag_manipulator; 40 | } // namespace tiny 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /.github/workflows/test_gcc_linux.yml: -------------------------------------------------------------------------------- 1 | name: gcc linux 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | schedule: 8 | - cron: '20 3 * * *' 9 | 10 | 11 | env: 12 | TEST_DIR: ./tests 13 | 14 | 15 | jobs: 16 | all: 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | gcc_version: [9, 11, 13, 14] 21 | cpp_version: [c++17, c++20] 22 | arch: [m64, m32] # 64 and 32 bit 23 | buildmode: [~ , -O3 -DNDEBUG, -O3 -DNDEBUG -ffast-math] 24 | 25 | # gcc 9 does not support C++20 26 | exclude: 27 | - gcc_version: 9 28 | cpp_version: c++20 29 | 30 | # Also include a few variations with disabled UB tricks (not for all to limit resource consumption). 31 | include: 32 | - gcc_version: 13 33 | cpp_version: c++20 34 | arch: m64 35 | buildmode: -DTINY_OPTIONAL_USE_SEPARATE_BOOL_INSTEAD_OF_UB_TRICKS 36 | 37 | runs-on: ubuntu-24.04 38 | timeout-minutes: 20 39 | 40 | steps: 41 | - uses: actions/checkout@v4 42 | 43 | - name: Install gcc 44 | run: | 45 | sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y 46 | sudo apt update -y 47 | sudo apt install ${{ format('gcc-{0} g++-{0}', matrix.gcc_version) }} -y 48 | 49 | - name: Install gcc-multilib for x86 if required 50 | # Creating a 32 bit executable on a 64 bit OS requires the gcc multilib. 51 | # It is not installed by default on the github runner. 52 | if: ${{ matrix.arch == 'm32' }} 53 | run: | 54 | sudo apt update -y 55 | sudo apt install ${{ format('gcc-{0}-multilib g++-{0}-multilib', matrix.gcc_version) }} -y 56 | 57 | - name: Build and run tests 58 | working-directory: ${{env.TEST_DIR}} 59 | env: 60 | CXX: ${{ format('g++-{0}', matrix.gcc_version) }} 61 | ADDITIONAL_FLAGS: ${{ format('-{0} -std={1} {2}', matrix.arch, matrix.cpp_version, matrix.buildmode) }} 62 | run: make generic 63 | -------------------------------------------------------------------------------- /tests/ExerciseStdOptional.cpp: -------------------------------------------------------------------------------- 1 | #include "ExerciseStdOptional.h" 2 | 3 | #include "Exercises.h" 4 | #include "TestTypes.h" 5 | #include "tiny/optional.h" 6 | 7 | #include 8 | 9 | void test_CrosscheckStdOptional() 10 | { 11 | // We also run our generic tests with std::optional to cross-check that the tests themselves 12 | // do not test non-standard behavior. 13 | 14 | { 15 | std::vector testValue1{1, 2, 3, 4}; 16 | std::vector testValue2{5, 6, 7}; 17 | EXERCISE_OPTIONAL((std::optional>{}), EXPECT_SEPARATE, testValue1, testValue2); 18 | } 19 | 20 | { 21 | std::initializer_list const initList = {3, 4, 5}; 22 | EXERCISE_OPTIONAL_WITH_CONSTRUCTOR_ARGS( 23 | (std::optional{}), 24 | EXPECT_SEPARATE, 25 | TestClassWithInitializerList({1}), 26 | TestClassWithInitializerList({2}), 27 | initList, 28 | ScopedEnum::v2); 29 | } 30 | 31 | // The libstdc++ in version 11 contains a bug that prevents the volatile and const tests from running. 32 | // See https://github.com/gcc-mirror/gcc/commit/fc6f1128ae603164aea6303ce2b3ed0b57e6a378 33 | #if !defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE != 11 34 | EXERCISE_OPTIONAL((std::optional{}), EXPECT_SEPARATE, 43.0, 44.0); 35 | static_assert(std::is_same_v::value_type, double volatile>); 36 | EXERCISE_OPTIONAL((std::optional{}), EXPECT_SEPARATE, 43.0, 44.0); 37 | static_assert(std::is_same_v::value_type, double const>); 38 | EXERCISE_OPTIONAL((std::optional{}), EXPECT_SEPARATE, 43.0, 44.0); 39 | static_assert(std::is_same_v::value_type, double volatile const>); 40 | #endif 41 | 42 | { 43 | int i = 0; 44 | int j = 42; 45 | EXERCISE_OPTIONAL((std::optional{}), EXPECT_SEPARATE, static_cast(&i), &j); 46 | EXERCISE_OPTIONAL((std::optional{}), EXPECT_SEPARATE, nullptr, static_cast(&i)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | project( 4 | tiny-optional 5 | VERSION 1.5.2 6 | DESCRIPTION "Replacement for std::optional that does not waste memory unnecessarily." 7 | HOMEPAGE_URL "https://github.com/Sedeniono/tiny-optional" 8 | LANGUAGES CXX 9 | ) 10 | 11 | include(GNUInstallDirs) 12 | include(CMakePackageConfigHelpers) 13 | 14 | add_library(tiny-optional INTERFACE) 15 | add_library(tiny-optional::tiny-optional ALIAS tiny-optional) 16 | 17 | if (NOT PROJECT_IS_TOP_LEVEL) 18 | set(warning_guard SYSTEM) 19 | endif() 20 | 21 | target_include_directories( 22 | tiny-optional ${warning_guard} 23 | INTERFACE "$" 24 | "$") 25 | 26 | target_compile_features(tiny-optional INTERFACE cxx_std_17) 27 | 28 | install( 29 | DIRECTORY include/ 30 | DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") 31 | 32 | install( 33 | TARGETS tiny-optional 34 | EXPORT tiny-optionalTargets 35 | INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" 36 | ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}") 37 | 38 | write_basic_package_version_file( 39 | "tiny-optionalConfigVersion.cmake" 40 | VERSION ${PROJECT_VERSION} 41 | # Different major and minor versions of tiny::optional cannot be mixed. 42 | # It is actively prevented by the use of inline namespaces. 43 | COMPATIBILITY SameMinorVersion) 44 | 45 | configure_package_config_file( 46 | "${PROJECT_SOURCE_DIR}/cmake/tiny-optionalConfig.cmake.in" 47 | "${PROJECT_BINARY_DIR}/tiny-optionalConfig.cmake" 48 | INSTALL_DESTINATION "${CMAKE_INSTALL_DATADIR}/tiny-optional") 49 | 50 | install( 51 | EXPORT tiny-optionalTargets 52 | FILE tiny-optionalTargets.cmake 53 | NAMESPACE tiny-optional:: 54 | DESTINATION "${CMAKE_INSTALL_DATADIR}/tiny-optional") 55 | 56 | install( 57 | FILES "${PROJECT_BINARY_DIR}/tiny-optionalConfig.cmake" 58 | "${PROJECT_BINARY_DIR}/tiny-optionalConfigVersion.cmake" 59 | DESTINATION "${CMAKE_INSTALL_DATADIR}/tiny-optional") 60 | -------------------------------------------------------------------------------- /tests/GccLikeCompilation.cpp: -------------------------------------------------------------------------------- 1 | #include "GccLikeCompilation.h" 2 | 3 | #include "TestUtilities.h" 4 | 5 | #include 6 | 7 | GccLikeCompilationChecks::GccLikeCompilationChecks( 8 | std::filesystem::path const & executable, 9 | std::filesystem::path const & tinyOptionalIncludeDir, 10 | std::string const & compilationFlags) 11 | : mExecutable(executable) 12 | , mTinyOptionalIncludeDir(tinyOptionalIncludeDir) 13 | , mTinyOptionalIncludeDirCanon(weakly_canonical(tinyOptionalIncludeDir)) 14 | , mCompilationFlags(compilationFlags) 15 | { 16 | mExecutable.make_preferred(); 17 | } 18 | 19 | 20 | void GccLikeCompilationChecks::PrintInputOptions(std::ostream & out) const 21 | { 22 | out << "\t==> Compiler: " << mExecutable << std::endl; 23 | out << "\t==> TinyOptional include directory (input): " << mTinyOptionalIncludeDir << std::endl; 24 | out << "\t==> TinyOptional include directory (canonical): " << mTinyOptionalIncludeDirCanon << std::endl; 25 | out << "\t==> Flags: " << mCompilationFlags << std::endl; 26 | } 27 | 28 | 29 | auto GccLikeCompilationChecks::PerformCompilation(std::string const & code) const -> ExecutionResult 30 | { 31 | #ifdef TINY_OPTIONAL_WINDOWS_BUILD 32 | // Piping strings with new lines and containing quotation marks into clang on Windows is cumbersome. 33 | // So instead just use a temporary file. Especially since we have the required mechanism anyway for the 34 | // MSVC version. 35 | WindowsTemporaryCodeFileScope tempCodeFileScope(code); 36 | 37 | // Output to NUL corresponds kind-of to /dev/null on linux. It prevents the creation of a file. 38 | std::string command = mExecutable.string() + " " + mCompilationFlags + " -I\"" + mTinyOptionalIncludeDirCanon.string() 39 | + "\" -c -x c++ -o NUL \"" + tempCodeFileScope.GetFullFilename().string() + "\""; 40 | #else 41 | std::string command = "printf \"%s\" '" + code + "' | " + mExecutable.string() + " " + mCompilationFlags + " -I\"" 42 | + mTinyOptionalIncludeDirCanon.string() + "\" -c -x c++ -o /dev/null -"; 43 | #endif 44 | 45 | return ExecuteProgramSync(command); 46 | } 47 | -------------------------------------------------------------------------------- /.github/workflows/test_clang_mac.yml: -------------------------------------------------------------------------------- 1 | name: clang mac 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | schedule: 8 | - cron: '20 3 * * *' 9 | 10 | 11 | env: 12 | TEST_DIR: ./tests 13 | 14 | 15 | jobs: 16 | all: 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | os: [macos-15-intel] # x64 21 | 22 | # Note: No clang version variation since the macOS runners do not have versioned executables (e.g. clang++-17). 23 | cpp_version: [c++17, c++20] 24 | buildmode: [~ , -O3 -DNDEBUG, -O3 -DNDEBUG -ffast-math] 25 | 26 | include: 27 | # Also include a few variations with disabled UB tricks (not for all to limit resource consumption). 28 | # Especially include some on arm64 where the UB tricks are not supported by tiny::optional. 29 | - os: macos-15-intel # x64 30 | cpp_version: c++20 31 | buildmode: -DTINY_OPTIONAL_USE_SEPARATE_BOOL_INSTEAD_OF_UB_TRICKS 32 | - os: macos-14 # arm64 33 | cpp_version: c++17 34 | buildmode: -DTINY_OPTIONAL_USE_SEPARATE_BOOL_INSTEAD_OF_UB_TRICKS 35 | - os: macos-14 # arm64 36 | cpp_version: c++20 37 | buildmode: -DTINY_OPTIONAL_USE_SEPARATE_BOOL_INSTEAD_OF_UB_TRICKS 38 | - os: macos-14 # arm64 39 | cpp_version: c++20 40 | buildmode: -DTINY_OPTIONAL_USE_SEPARATE_BOOL_INSTEAD_OF_UB_TRICKS -O3 -DNDEBUG 41 | - os: macos-15 # arm64 42 | cpp_version: c++20 43 | buildmode: -DTINY_OPTIONAL_USE_SEPARATE_BOOL_INSTEAD_OF_UB_TRICKS 44 | - os: macos-15 # arm64 45 | cpp_version: c++20 46 | buildmode: -DTINY_OPTIONAL_USE_SEPARATE_BOOL_INSTEAD_OF_UB_TRICKS -O3 -DNDEBUG 47 | 48 | runs-on: ${{ matrix.os }} 49 | timeout-minutes: 10 50 | 51 | steps: 52 | - uses: actions/checkout@v4 53 | 54 | - name: Build and run tests 55 | working-directory: ${{env.TEST_DIR}} 56 | env: 57 | CXX: clang++ 58 | ADDITIONAL_FLAGS: ${{ format('-std={0} {1}', matrix.cpp_version, matrix.buildmode) }} 59 | run: make generic 60 | 61 | -------------------------------------------------------------------------------- /.github/workflows/test_clang_linux.yml: -------------------------------------------------------------------------------- 1 | name: clang linux 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | schedule: 8 | - cron: '20 3 * * *' 9 | 10 | 11 | env: 12 | TEST_DIR: ./tests 13 | 14 | 15 | jobs: 16 | all: 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | clang_version: [13, 15] 21 | cpp_version: [c++17, c++20] 22 | stdlib: [libc++, libstdc++] # llvm library and gcc library 23 | arch: [m64, m32] # 64 and 32 bit 24 | buildmode: [~ , -O3 -DNDEBUG, -O3 -DNDEBUG -ffast-math] 25 | 26 | # Also include a few variations with disabled UB tricks (not for all to limit resource consumption). 27 | include: 28 | - clang_version: 15 29 | cpp_version: c++20 30 | stdlib: libc++ 31 | arch: m64 32 | buildmode: -DTINY_OPTIONAL_USE_SEPARATE_BOOL_INSTEAD_OF_UB_TRICKS 33 | - clang_version: 15 34 | cpp_version: c++20 35 | stdlib: libstdc++ 36 | arch: m64 37 | buildmode: -DTINY_OPTIONAL_USE_SEPARATE_BOOL_INSTEAD_OF_UB_TRICKS 38 | 39 | runs-on: ubuntu-22.04 40 | timeout-minutes: 10 41 | 42 | steps: 43 | - uses: actions/checkout@v4 44 | 45 | - name: Install libc++ if required 46 | # libc++ is not installed for all clang versions by default. 47 | if: ${{ matrix.stdlib == 'libc++' && matrix.arch == 'm64' }} 48 | run: | 49 | sudo apt update 50 | sudo apt install ${{ format('libc++-{0}-dev libc++abi-{0}-dev', matrix.clang_version) }} -y 51 | 52 | - name: Install gcc-multilib and libc++ for x86 if required 53 | # For x86: We require both gcc multilib and the 32-bit variant (i386) of libc++ for the 54 | # used clang version. For libc++ we need to manually add the i386 architecture. 55 | if: ${{ matrix.arch == 'm32' }} 56 | run: | 57 | sudo dpkg --add-architecture i386 58 | sudo apt update 59 | sudo apt install gcc-multilib g++-multilib ${{ format('libc++-{0}-dev:i386 libc++abi-{0}-dev:i386', matrix.clang_version) }} -y 60 | 61 | - name: Build and run tests 62 | working-directory: ${{env.TEST_DIR}} 63 | env: 64 | CXX: ${{ format('clang++-{0}', matrix.clang_version) }} 65 | ADDITIONAL_FLAGS: ${{ format('-{0} -std={1} -stdlib={2} {3}', matrix.arch, matrix.cpp_version, matrix.stdlib, matrix.buildmode) }} 66 | run: make generic 67 | 68 | -------------------------------------------------------------------------------- /tests/ExerciseOptionalInplace.cpp: -------------------------------------------------------------------------------- 1 | #include "ExerciseOptionalInplace.h" 2 | 3 | #include "Exercises.h" 4 | #include "TestTypes.h" 5 | #include "tiny/optional.h" 6 | 7 | #include 8 | 9 | void test_OptionalInplace() 10 | { 11 | EXERCISE_OPTIONAL_WITH_CONSTRUCTOR_ARGS( 12 | (tiny::optional_inplace{}), 13 | EXPECT_INPLACE, 14 | TestClass(true, 4.0), 15 | TestClass(true, 6.0), 16 | true, 17 | 5.0); 18 | 19 | { 20 | // Test for the std::hash specialization 21 | static_assert(!IsDisabledHash>); 22 | std::unordered_set> const set 23 | = {TestClass(true, 42.0), std::nullopt, TestClass(true, 43.0)}; 24 | ASSERT_TRUE(set.count(TestClass(true, 42.0)) > 0); 25 | ASSERT_TRUE(set.count(std::nullopt) > 0); 26 | ASSERT_TRUE(set.count(TestClass(true, 43.0)) > 0); 27 | } 28 | 29 | { 30 | { 31 | struct UniqueObjectRepr 32 | { 33 | int i1 = 0; 34 | int i2 = 0; 35 | 36 | bool operator==(UniqueObjectRepr const & rhs) const 37 | { 38 | return i1 == rhs.i1 && i2 == rhs.i2; 39 | } 40 | }; 41 | 42 | struct UniqueObjectReprFlagManipulator 43 | { 44 | static bool is_empty(UniqueObjectRepr const & flag) noexcept 45 | { 46 | return flag.i1 == -1; 47 | } 48 | 49 | static void init_empty_flag(UniqueObjectRepr & uninitializedFlagMemory) noexcept 50 | { 51 | ::new (&uninitializedFlagMemory) UniqueObjectRepr{-1, 0}; 52 | } 53 | 54 | static void invalidate_empty_flag(UniqueObjectRepr & emptyFlag) noexcept 55 | { 56 | emptyFlag.~UniqueObjectRepr(); 57 | } 58 | }; 59 | 60 | static_assert(std::has_unique_object_representations_v); 61 | EXERCISE_OPTIONAL( 62 | (tiny::optional_inplace{}), 63 | EXPECT_INPLACE, 64 | UniqueObjectRepr{1}, 65 | UniqueObjectRepr{2}); 66 | #ifdef TINY_OPTIONAL_TRIVIAL_SPECIAL_MEMBER_FUNCTIONS 67 | static_assert(std::is_trivially_copy_constructible_v>); 68 | static_assert(std::is_trivially_move_constructible_v>); 69 | static_assert(std::is_trivially_move_assignable_v>); 70 | static_assert(std::is_trivially_move_assignable_v>); 71 | #endif 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tests/MsvcCompilation.cpp: -------------------------------------------------------------------------------- 1 | #include "MsvcCompilation.h" 2 | 3 | #include "TestUtilities.h" 4 | 5 | #ifdef TINY_OPTIONAL_WINDOWS_BUILD 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #endif 12 | 13 | 14 | #ifdef TINY_OPTIONAL_WINDOWS_BUILD 15 | MsvcCompilationChecks::MsvcCompilationChecks( 16 | std::filesystem::path const & vcvarsBatFile, 17 | std::filesystem::path const & tinyOptionalIncludeDir, 18 | std::string const & compilationFlags) 19 | : mVcvarsBatFile(vcvarsBatFile) 20 | , mTinyOptionalIncludeDir(weakly_canonical(tinyOptionalIncludeDir)) 21 | , mCompilationFlags(compilationFlags) 22 | { 23 | mVcvarsBatFile.make_preferred(); 24 | } 25 | 26 | 27 | void MsvcCompilationChecks::PrintInputOptions(std::ostream & out) const 28 | { 29 | out << "\t==> vcvars: " << mVcvarsBatFile << std::endl; 30 | out << "\t==> TinyOptional include directory: " << mTinyOptionalIncludeDir << std::endl; 31 | out << "\t==> Flags: " << mCompilationFlags << std::endl; 32 | } 33 | 34 | 35 | auto MsvcCompilationChecks::PerformCompilation(std::string const & code) const -> ExecutionResult 36 | { 37 | // cl.exe cannot read from stdin, so we need to put the code into a temporary file. 38 | WindowsTemporaryCodeFileScope tempFileScope(code); 39 | auto const tempCodeFilename = tempFileScope.GetFilename(); 40 | auto const tempDirectory = tempFileScope.GetDirectory(); 41 | 42 | // clang-format off 43 | // 44 | // We run cmd.exe because we need to setup an environment via the vcvars*.bat file. Running cl.exe directly will not work since it 45 | // will fail to find even the STL headers. 46 | std::string batchCmd = 47 | // /S/C are the options to cmd.exe. Everything afterwards is surrounded by "". 48 | "cmd.exe /S/C " 49 | // Surround everything after the /C with quotes. 50 | "\"" 51 | // First command: Execute the batch that imports all the necessary environment variables required by cl.exe to actually work. 52 | "\"" + mVcvarsBatFile.string() + "\"" + " " 53 | // Second command: Change working directory to where the temporary file is located. 54 | + "&& cd /D \"" + tempDirectory.string() + "\" " 55 | // Third command: Compile. 56 | // /c causes the link step to be skipped. 57 | + "&& cl.exe " 58 | + mCompilationFlags 59 | + " /c " 60 | + "/I\"" + mTinyOptionalIncludeDir.string() + "\" " 61 | + "\"" + tempCodeFilename.string() + "\"" 62 | // Closing quotation mark for /C of cmd.exe. 63 | + "\"" 64 | ; 65 | // clang-format on 66 | 67 | return ExecuteProgramSync(batchCmd); 68 | } 69 | 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /tests/ExerciseOptionalEmptyViaType.cpp: -------------------------------------------------------------------------------- 1 | #include "ExerciseOptionalEmptyViaType.h" 2 | 3 | #include "Exercises.h" 4 | #include "TestTypes.h" 5 | #include "tiny/optional.h" 6 | 7 | #include 8 | 9 | void test_OptionalEmptyViaType() 10 | { 11 | EXERCISE_OPTIONAL( 12 | (tiny::optional_sentinel_via_type{}), 13 | cInPlaceExpectationForUnusedBits, 14 | false, 15 | true); 16 | EXERCISE_OPTIONAL( 17 | (tiny::optional_sentinel_via_type{}), 18 | cInPlaceExpectationForUnusedBits, 19 | true, 20 | false); 21 | 22 | EXERCISE_OPTIONAL( 23 | (tiny::optional_sentinel_via_type{}), 24 | cInPlaceExpectationForUnusedBits, 25 | 43.0, 26 | 44.0); 27 | EXERCISE_OPTIONAL( 28 | (tiny::optional_sentinel_via_type{}), 29 | cInPlaceExpectationForUnusedBits, 30 | 43.0f, 31 | 44.0f); 32 | 33 | EXERCISE_OPTIONAL((tiny::optional_sentinel_via_type{}), EXPECT_SEPARATE, 43, 44); 34 | EXERCISE_OPTIONAL((tiny::optional_sentinel_via_type>{}), EXPECT_INPLACE, 43, 44); 35 | 36 | EXERCISE_OPTIONAL( 37 | (tiny::optional_sentinel_via_type< 38 | TestClassForInplace, 39 | std::integral_constant, 40 | &TestClassForInplace::someInt>{}), 41 | cInPlaceExpectationForMemPtr, 42 | TestClassForInplace{}, 43 | TestClassForInplace(43, 44.0, 45, nullptr)); 44 | 45 | EXERCISE_OPTIONAL( 46 | (tiny::optional_sentinel_via_type{}), 47 | EXPECT_SEPARATE, 48 | TestClass(true, 4.0), 49 | TestClass(true, 5.0)); 50 | 51 | EXERCISE_OPTIONAL((tiny::optional_sentinel_via_type{}), EXPECT_INPLACE, 43.0, 44.0); 52 | 53 | EXERCISE_OPTIONAL( 54 | (tiny::optional_sentinel_via_type{}), 55 | cInPlaceExpectationForMemPtr, 56 | TestClassForInplace{}, 57 | TestClassForInplace(43, 44.0, 45, nullptr)); 58 | 59 | { 60 | // Test for the std::hash specialization 61 | static_assert(!IsDisabledHash>); 62 | std::unordered_set> const set 63 | = {43.0, std::nullopt, 44.0}; 64 | ASSERT_TRUE(set.count(43.0) > 0); 65 | ASSERT_TRUE(set.count(std::nullopt) > 0); 66 | ASSERT_TRUE(set.count(44.0) > 0); 67 | } 68 | { 69 | // We did not specialize std::hash for TestClassWithInitializerList, so neither should tiny optional. 70 | static_assert(IsDisabledHash); 71 | static_assert(IsDisabledHash>); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/CompilationBase.cpp: -------------------------------------------------------------------------------- 1 | #include "CompilationBase.h" 2 | 3 | #include "GccLikeCompilation.h" 4 | #include "MsvcCompilation.h" 5 | #include "TestUtilities.h" 6 | 7 | #include 8 | #include 9 | 10 | 11 | std::string CompilationChecksBase::CheckDoesNotCompile( 12 | std::string const & codeSnippet, 13 | std::string const & expectedRegex) const 14 | { 15 | std::string const code = CompleteCppCodeSnippet(codeSnippet); 16 | ExecutionResult const result = PerformCompilation(code); 17 | 18 | // 0 means the compilation succeeded, but we expect a failure. 19 | if (result.exitCode == 0) { 20 | return "ERROR: The compilation succeeded unexpectedly (exit code == " + std::to_string(result.exitCode) 21 | + "). Expected that it fails and includes the regex \"" + expectedRegex + "\" in the compiler's output.\n" 22 | + FormatInfo(code, result); 23 | } 24 | else { 25 | std::regex r(expectedRegex); 26 | bool const foundExpected = std::regex_search(result.output, r); 27 | if (!foundExpected) { 28 | return "ERROR: Failed to find the expected regex \"" + expectedRegex + "\" in the compiler's output.\n" 29 | + FormatInfo(code, result); 30 | } 31 | } 32 | 33 | return {}; 34 | } 35 | 36 | 37 | namespace 38 | { 39 | std::string 40 | PrependToEveryLine(std::string const & original, std::string const & toPrepend, bool includeLineNumbers = false) 41 | { 42 | std::stringstream s(original); 43 | std::string line; 44 | std::stringstream modified; 45 | int lineNumber = 1; 46 | while (std::getline(s, line)) { 47 | modified << toPrepend; 48 | if (includeLineNumbers) { 49 | modified << std::setw(2) << lineNumber << " "; 50 | } 51 | modified << line << "\n"; 52 | ++lineNumber; 53 | } 54 | return modified.str(); 55 | } 56 | } // namespace 57 | 58 | 59 | std::string CompilationChecksBase::FormatInfo(std::string const & code, ExecutionResult const & result) 60 | { 61 | std::stringstream s; 62 | 63 | s << "Compiled the following code:\n"; 64 | s << PrependToEveryLine(code, "\t", true) << "\n"; 65 | s << "using the following command:\n"; 66 | s << PrependToEveryLine(result.command, "\t") << "\n"; 67 | s << "It returned exit code " << result.exitCode << "\n"; 68 | s << "and the following output:\n"; 69 | s << PrependToEveryLine(result.output, "\t") << "\n"; 70 | 71 | return s.str(); 72 | } 73 | 74 | 75 | std::unique_ptr CreateCompilationChecker() 76 | { 77 | #if defined(TINY_OPTIONAL_MSVC_BUILD) 78 | // E.g.: "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\" 79 | std::filesystem::path const vcInstallDir = TINY_OPTIONAL_TESTS_VC_INSTALL_DIR; 80 | // E.g.: vcvars64.bat 81 | std::string const vcvarsFilename = TINY_OPTIONAL_TESTS_VCVARS_NAME; 82 | std::filesystem::path fullVcvarsPath = vcInstallDir / "Auxiliary" / "Build" / vcvarsFilename; 83 | fullVcvarsPath = canonical(fullVcvarsPath); 84 | 85 | std::filesystem::path const tinyOptionalIncludeDir = TINY_OPTIONAL_TESTS_HEADER_INCLUDE_DIR; 86 | std::string const compilationFlags = TINY_OPTIONAL_TESTS_COMPILATION_FLAGS; 87 | 88 | return std::make_unique(fullVcvarsPath, tinyOptionalIncludeDir, compilationFlags); 89 | 90 | #else 91 | std::filesystem::path const executable = TINY_OPTIONAL_TESTS_COMPILER_EXECUTABLE; 92 | std::filesystem::path const tinyOptionalIncludeDir = TINY_OPTIONAL_TESTS_HEADER_INCLUDE_DIR; 93 | std::string const compilationFlags = TINY_OPTIONAL_TESTS_COMPILATION_FLAGS; 94 | return std::make_unique(executable, tinyOptionalIncludeDir, compilationFlags); 95 | #endif 96 | } 97 | -------------------------------------------------------------------------------- /tests/make_win_gcclike.ps1: -------------------------------------------------------------------------------- 1 | # Generic "makefile" like powershell script to compile and run the tests 2 | # using clang or gcc on windows. 3 | 4 | $ErrorActionPreference = "Stop" 5 | 6 | # Get input variables from environment if they are defined there. 7 | if ($null -ne $env:CXX) { $CXX = $env:CXX } 8 | if ($null -ne $env:ADDITIONAL_FLAGS) { $ADDITIONAL_FLAGS = -split $env:ADDITIONAL_FLAGS } 9 | if ($null -ne $env:OUT_DIR_NAME) { $OUT_DIR_NAME = $env:OUT_DIR_NAME } 10 | 11 | $PS_VERSION_MAJOR = (get-host).Version.Major 12 | $PS_VERSION_MINOR = (get-host).Version.Minor 13 | $PS_VERSION_COMBINED = $PS_VERSION_MAJOR * 1000 + $PS_VERSION_MINOR 14 | echo "Powershell version: $PS_VERSION_MAJOR.$PS_VERSION_MINOR (combined: $PS_VERSION_COMBINED)" 15 | 16 | $WARNING_FLAGS = "-Wall", "-Wextra", "-pedantic", "-Wconversion", "-Werror" 17 | $CPP_FILES = "ComparisonTests.cpp", "CompilationBase.cpp", "CompilationErrorTests.cpp", "ConstructionTests.cpp", "ExerciseOptionalAIP.cpp", "ExerciseOptionalEmptyViaType.cpp", "ExerciseOptionalInplace.cpp", "ExerciseOptionalWithCustomFlagManipulator.cpp", "ExerciseStdOptional.cpp", "ExerciseTinyOptionalPayload1.cpp", "ExerciseTinyOptionalPayload2.cpp", "GccLikeCompilation.cpp", "IntermediateTests.cpp", "MsvcCompilation.cpp", "NatvisTests.cpp", "SpecialMonadicTests.cpp", "Tests.cpp", "TestUtilities.cpp" 18 | $TINY_OPTIONAL_INCLUDE_DIR = "../include" 19 | $EXE_NAME = "TinyOptionalTests.exe" 20 | 21 | # The quotation escape rules changed in powershell 7.3 (probably: https://github.com/PowerShell/PowerShell/issues/1995) 22 | if ($PS_VERSION_COMBINED -le 7002) { 23 | echo "Using old escape rules" 24 | $TINY_OPTIONAL_TESTS_COMPILER_EXECUTABLE = '-DTINY_OPTIONAL_TESTS_COMPILER_EXECUTABLE=' + '\"' + $CXX + '\"' 25 | $TINY_OPTIONAL_TESTS_HEADER_INCLUDE_DIR = '-DTINY_OPTIONAL_TESTS_HEADER_INCLUDE_DIR=' + '\"' + $TINY_OPTIONAL_INCLUDE_DIR +'\"' 26 | $TINY_OPTIONAL_TESTS_COMPILATION_FLAGS = '-DTINY_OPTIONAL_TESTS_COMPILATION_FLAGS=' + '""' + $WARNING_FLAGS + ' ' + $ADDITIONAL_FLAGS + '""' 27 | } 28 | else { 29 | echo "Using new escape rules" 30 | $TINY_OPTIONAL_TESTS_COMPILER_EXECUTABLE = '-DTINY_OPTIONAL_TESTS_COMPILER_EXECUTABLE=' + '"' + $CXX + '"' 31 | $TINY_OPTIONAL_TESTS_HEADER_INCLUDE_DIR = '-DTINY_OPTIONAL_TESTS_HEADER_INCLUDE_DIR=' + '"' + $TINY_OPTIONAL_INCLUDE_DIR +'"' 32 | $TINY_OPTIONAL_TESTS_COMPILATION_FLAGS = '-DTINY_OPTIONAL_TESTS_COMPILATION_FLAGS=' + '"' + $WARNING_FLAGS + ' ' + $ADDITIONAL_FLAGS + '"' 33 | } 34 | 35 | $OUT_DIR = "../bin/" + $OUT_DIR_NAME 36 | $FULL_EXE_PATH = $OUT_DIR + "/" + $EXE_NAME 37 | 38 | 39 | # Cross check input 40 | if ([string]::IsNullOrEmpty($CXX)) { 41 | echo "CXX is null" 42 | exit 50 43 | } 44 | if ([string]::IsNullOrEmpty($ADDITIONAL_FLAGS)) { 45 | echo "ADDITIONAL_FLAGS is null" 46 | exit 51 47 | } 48 | if ([string]::IsNullOrEmpty($OUT_DIR_NAME)) { 49 | echo "OUT_DIR_NAME is null" 50 | exit 52 51 | } 52 | 53 | 54 | 55 | echo "Creating directory if it does not exist: $OUT_DIR" 56 | New-Item -ItemType Directory -Force -Path "$OUT_DIR" | Out-Null 57 | 58 | echo "Removing existing executable if it exists: $FULL_EXE_PATH" 59 | rm "$FULL_EXE_PATH" -ErrorAction Ignore 60 | 61 | echo "TINY_OPTIONAL_TESTS_COMPILER_EXECUTABLE: $TINY_OPTIONAL_TESTS_COMPILER_EXECUTABLE" 62 | echo "TINY_OPTIONAL_TESTS_HEADER_INCLUDE_DIR: $TINY_OPTIONAL_TESTS_HEADER_INCLUDE_DIR" 63 | echo "TINY_OPTIONAL_TESTS_COMPILATION_FLAGS: $TINY_OPTIONAL_TESTS_COMPILATION_FLAGS" 64 | 65 | echo "Compiling using CXX='$CXX' with ADDITIONAL_FLAGS='$ADDITIONAL_FLAGS' and OUT_DIR_NAME='$OUT_DIR_NAME'. FULL_EXE_PATH='$FULL_EXE_PATH'" 66 | & "$CXX" -o "$FULL_EXE_PATH" @WARNING_FLAGS @ADDITIONAL_FLAGS -I $TINY_OPTIONAL_INCLUDE_DIR ` 67 | $TINY_OPTIONAL_TESTS_COMPILER_EXECUTABLE $TINY_OPTIONAL_TESTS_HEADER_INCLUDE_DIR $TINY_OPTIONAL_TESTS_COMPILATION_FLAGS ` 68 | @CPP_FILES 69 | $CompilationResult = $LASTEXITCODE 70 | if ($CompilationResult -ne 0) { 71 | echo "Compilation failed" 72 | exit $CompilationResult 73 | } 74 | 75 | echo "Executing tests: $FULL_EXE_PATH" 76 | & "$FULL_EXE_PATH" 77 | exit $LASTEXITCODE 78 | 79 | -------------------------------------------------------------------------------- /tests/Tests.cpp: -------------------------------------------------------------------------------- 1 | #include "ComparisonTests.h" 2 | #include "CompilationErrorTests.h" 3 | #include "ConstructionTests.h" 4 | #include "ExerciseOptionalAIP.h" 5 | #include "ExerciseOptionalEmptyViaType.h" 6 | #include "ExerciseOptionalInplace.h" 7 | #include "ExerciseOptionalWithCustomFlagManipulator.h" 8 | #include "ExerciseStdOptional.h" 9 | #include "ExerciseTinyOptionalPayload.h" 10 | #include "IntermediateTests.h" 11 | #include "NatvisTests.h" 12 | #include "SpecialMonadicTests.h" 13 | #include "TestUtilities.h" 14 | #include "tiny/optional.h" 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #ifdef TINY_OPTIONAL_WINDOWS_BUILD 22 | #include 23 | #endif 24 | 25 | void RunTests() 26 | { 27 | // clang-format off 28 | #define ADD_TEST(x) {x, #x} 29 | // clang-format on 30 | 31 | std::vector> const tests 32 | = {ADD_TEST(test_Natvis), 33 | ADD_TEST(test_IsIntegralInRange), 34 | ADD_TEST(test_NanExploit), 35 | ADD_TEST(test_SelectDecomposition), 36 | ADD_TEST(test_TinyOptionalPayload_Bool), 37 | ADD_TEST(test_TinyOptionalPayload_FloatingPoint), 38 | ADD_TEST(test_TinyOptionalPayload_IntegersAndEnums), 39 | ADD_TEST(test_TinyOptionalPayload_IsEmptyFlagInMember), 40 | ADD_TEST(test_TinyOptionalPayload_Pointers), 41 | ADD_TEST(test_TinyOptionalPayload_StdTypes), 42 | ADD_TEST(test_TinyOptionalPayload_NestedOptionals), 43 | ADD_TEST(test_TinyOptionalPayload_ConstAndVolatile), 44 | ADD_TEST(test_TinyOptionalPayload_Cpp20NTTP), 45 | ADD_TEST(test_TinyOptionalPayload_WindowsHandles), 46 | ADD_TEST(test_TinyOptionalPayload_OtherTypes), 47 | ADD_TEST(test_TinyOptionalMemoryManagement), 48 | ADD_TEST(test_TinyOptionalCopyConstruction), 49 | ADD_TEST(test_TinyOptionalMoveConstruction), 50 | ADD_TEST(test_TinyOptionalCopyAssignment), 51 | ADD_TEST(test_TinyOptionalMoveAssignment), 52 | ADD_TEST(test_TinyOptionalDestruction), 53 | ADD_TEST(test_TinyOptionalConversions), 54 | ADD_TEST(test_TinyOptionalWithRegisteredCustomFlagManipulator), 55 | ADD_TEST(test_OptionalEmptyViaType), 56 | ADD_TEST(test_OptionalInplace), 57 | ADD_TEST(test_OptionalAIP), 58 | ADD_TEST(test_CrosscheckStdOptional), 59 | ADD_TEST(test_Exceptions), 60 | ADD_TEST(test_MakeOptional), 61 | ADD_TEST(test_Comparisons), 62 | ADD_TEST(test_SpecialTestsFor_and_then), 63 | ADD_TEST(test_SpecialTestsFor_transform), 64 | ADD_TEST(test_SpecialTestsFor_or_else), 65 | ADD_TEST(test_ExpressionsThatShouldNotCompile)}; 66 | 67 | for (size_t testIdx = 0; testIdx < tests.size(); ++testIdx) { 68 | auto const [testFunc, testName] = tests.at(testIdx); 69 | std::cout << "Running: " << testName << std::endl; 70 | testFunc(); 71 | } 72 | } 73 | 74 | 75 | int main() 76 | { 77 | #ifdef TINY_OPTIONAL_WINDOWS_BUILD 78 | // Explicitly disable the creation of windows due to assert(). I suspect this might cause problems in the github 79 | // action runners (a window might pop up and prevent the termination of the runners because the window waits for user 80 | // input which will never come). 81 | _set_error_mode(_OUT_TO_STDERR); 82 | _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG); 83 | #endif 84 | 85 | #ifdef TINY_OPTIONAL_MSVC_BUILD 86 | std::cout << "Microsoft compiler version (_MSC_FULL_VER): " << _MSC_FULL_VER << std::endl; 87 | std::cout << "Microsoft STL version (_MSVC_STL_UPDATE): " << _MSVC_STL_UPDATE << std::endl; 88 | #endif 89 | 90 | try { 91 | std::cout << "Running tests..." << std::endl; 92 | RunTests(); 93 | std::cout << "All tests PASSED" << std::endl; 94 | return 0; 95 | } 96 | catch (std::exception const & ex) { 97 | std::cerr << "ERROR: Caught exception: " << ex.what() << " (type: " << typeid(ex).name() << ")" << std::endl; 98 | assert(false); 99 | return 44; 100 | } 101 | catch (...) { 102 | std::cerr << "ERROR: Caught unknown exception." << std::endl; 103 | assert(false); 104 | return 45; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /tests/SpecialMonadicTests.cpp: -------------------------------------------------------------------------------- 1 | #include "SpecialMonadicTests.h" 2 | 3 | #include "TestUtilities.h" 4 | #include "tiny/optional.h" 5 | 6 | #include 7 | 8 | 9 | void test_SpecialTestsFor_and_then() 10 | { 11 | // Check that modification of the payload works from within the function called by and_then. 12 | { 13 | tiny::optional> o{{40}}; 14 | ASSERT_TRUE(o.value().size() == 1); 15 | 16 | o.and_then([](auto & vec) { 17 | vec.push_back(42); 18 | return tiny::optional{}; 19 | }); 20 | ASSERT_TRUE(o.value().size() == 2); 21 | ASSERT_TRUE(o->at(1) == 42); 22 | } 23 | 24 | // Check that moving out the payload works from within the function called by and_then. 25 | { 26 | tiny::optional> o{{40}}; 27 | ASSERT_TRUE(o.value().size() == 1); 28 | 29 | std::vector movedTo; 30 | std::move(o).and_then([&](auto && vec) { 31 | movedTo = std::move(vec); 32 | return tiny::optional{}; 33 | }); 34 | ASSERT_TRUE(o.value().empty()); 35 | ASSERT_TRUE(movedTo.at(0) == 40); 36 | } 37 | } 38 | 39 | 40 | void test_SpecialTestsFor_transform() 41 | { 42 | // Check that modification of the payload works from within the function called by transform. 43 | { 44 | tiny::optional> o{{40}}; 45 | ASSERT_TRUE(o.value().size() == 1); 46 | 47 | o.transform([](auto & vec) { 48 | vec.push_back(42); 49 | return 1; 50 | }); 51 | ASSERT_TRUE(o.value().size() == 2); 52 | ASSERT_TRUE(o->at(1) == 42); 53 | } 54 | 55 | // Check that moving out the payload works from within the function called by transform. 56 | { 57 | tiny::optional> o{{40}}; 58 | ASSERT_TRUE(o.value().size() == 1); 59 | 60 | std::vector movedTo; 61 | std::move(o).transform([&](auto && vec) { 62 | movedTo = std::move(vec); 63 | return 1; 64 | }); 65 | ASSERT_TRUE(o.value().empty()); 66 | ASSERT_TRUE(movedTo.at(0) == 40); 67 | } 68 | 69 | // transform() is supposed to work even if the type returned by the called function is neither copyable nor moveable. 70 | // This is the stuff with DirectInitializationFromFunctionTag in the implementation. Check that this is true. 71 | { 72 | struct NonCopyableAndNonMovable 73 | { 74 | int value; 75 | 76 | explicit NonCopyableAndNonMovable(int value) 77 | : value(value) 78 | { 79 | } 80 | 81 | NonCopyableAndNonMovable(NonCopyableAndNonMovable const &) = delete; 82 | NonCopyableAndNonMovable(NonCopyableAndNonMovable &&) = delete; 83 | }; 84 | 85 | tiny::optional original = 42; 86 | tiny::optional transformed 87 | = original.transform([](int v) { return NonCopyableAndNonMovable{v}; }); 88 | ASSERT_TRUE(transformed.value().value == 42); 89 | } 90 | } 91 | 92 | 93 | void test_SpecialTestsFor_or_else() 94 | { 95 | #ifdef TINY_OPTIONAL_ENABLE_ORELSE 96 | // Check that or_else moves out the payload when called on a rvalue. 97 | { 98 | tiny::optional> origOpt{{40}}; 99 | ASSERT_TRUE(origOpt.value().size() == 1); 100 | 101 | tiny::optional> newOpt = std::move(origOpt).or_else([]() -> decltype(origOpt) { FAIL(); }); 102 | ASSERT_TRUE(newOpt.value().size() == 1); 103 | ASSERT_TRUE(newOpt->at(0) == 40); 104 | 105 | // Core check: The original optional should contain an empty vector because it was moved out. 106 | ASSERT_TRUE(origOpt.has_value()); // NOLINT(clang-analyzer-cplusplus.Move) 107 | ASSERT_TRUE(origOpt->empty()); 108 | } 109 | // Similar test, but with a move-only type. 110 | { 111 | tiny::optional> origOpt{std::make_unique(42)}; 112 | ASSERT_TRUE(origOpt.value() != nullptr); 113 | 114 | tiny::optional> newOpt = std::move(origOpt).or_else([]() -> decltype(origOpt) { FAIL(); }); 115 | ASSERT_TRUE(newOpt.value() != nullptr); 116 | ASSERT_TRUE(*newOpt.value() == 42); 117 | 118 | // Core check: The original optional should contain an pointer because it was moved out. 119 | ASSERT_TRUE(origOpt.has_value()); 120 | ASSERT_TRUE(origOpt.value() == nullptr); 121 | } 122 | #endif 123 | } 124 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: Mozilla 4 | DisableFormat: false 5 | AccessModifierOffset: -2 6 | AlignAfterOpenBracket: AlwaysBreak 7 | AlignConsecutiveAssignments: false 8 | AlignConsecutiveDeclarations: false 9 | AlignConsecutiveMacros: false 10 | AlignEscapedNewlinesLeft: false 11 | AlignOperands: true 12 | AlignTrailingComments: false 13 | AllowAllArgumentsOnNextLine: false 14 | AllowAllConstructorInitializersOnNextLine: false 15 | AllowAllParametersOfDeclarationOnNextLine: false 16 | AllowShortBlocksOnASingleLine: Never 17 | AllowShortCaseLabelsOnASingleLine: false 18 | AllowShortFunctionsOnASingleLine: Empty 19 | AllowShortLambdasOnASingleLine: Inline 20 | AllowShortIfStatementsOnASingleLine: false 21 | AllowShortLoopsOnASingleLine: false 22 | AlwaysBreakAfterReturnType: None 23 | AlwaysBreakBeforeMultilineStrings: false 24 | AlwaysBreakTemplateDeclarations: true 25 | BinPackArguments: false 26 | BinPackParameters: false 27 | BraceWrapping: 28 | AfterCaseLabel: true 29 | AfterClass: true 30 | AfterControlStatement: false 31 | AfterEnum: true 32 | AfterFunction: true 33 | AfterNamespace: true 34 | AfterObjCDeclaration: false 35 | AfterStruct: true 36 | AfterUnion: true 37 | BeforeCatch: true 38 | BeforeLambdaBody: false 39 | BeforeElse: true 40 | BeforeWhile: true 41 | IndentBraces: false 42 | SplitEmptyFunction: true 43 | SplitEmptyRecord: true 44 | SplitEmptyNamespace: true 45 | BreakBeforeBinaryOperators: All 46 | BreakBeforeBraces: Custom 47 | BreakBeforeTernaryOperators: true 48 | BreakConstructorInitializers: BeforeComma 49 | BreakInheritanceList: BeforeComma 50 | BreakStringLiterals: false 51 | ColumnLimit: 120 52 | CommentPragmas: '^ IWYU pragma:' 53 | CompactNamespaces: false 54 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 55 | ConstructorInitializerIndentWidth: 2 56 | ContinuationIndentWidth: 4 57 | Cpp11BracedListStyle: true 58 | DerivePointerAlignment: false 59 | ExperimentalAutoDetectBinPacking: false 60 | FixNamespaceComments: true 61 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 62 | IncludeBlocks: Regroup 63 | IncludeCategories: 64 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 65 | Priority: 2 66 | SortPriority: 2 67 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 68 | Priority: 3 69 | - Regex: '<[[:alnum:].]+>' 70 | Priority: 4 71 | - Regex: '.*precompiled.*' 72 | Priority: -1 73 | - Regex: '.*' 74 | Priority: 1 75 | SortPriority: 0 76 | IndentCaseLabels: true 77 | IndentGotoLabels: false 78 | IndentPPDirectives: BeforeHash 79 | IndentRequiresClause: true 80 | IndentWidth: 2 81 | IndentWrappedFunctionNames: true 82 | InsertBraces: true 83 | KeepEmptyLinesAtTheStartOfBlocks: true 84 | MacroBlockBegin: '' 85 | MacroBlockEnd: '' 86 | MaxEmptyLinesToKeep: 2 87 | NamespaceIndentation: Inner 88 | ObjCBlockIndentWidth: 2 89 | ObjCSpaceAfterProperty: true 90 | ObjCSpaceBeforeProtocolList: false 91 | PenaltyBreakBeforeFirstCallParameter: 19 92 | PenaltyBreakComment: 300 93 | PenaltyBreakFirstLessLess: 120 94 | PenaltyBreakString: 1000 95 | PenaltyExcessCharacter: 1000000 96 | PenaltyReturnTypeOnItsOwnLine: 200 97 | PointerAlignment: Middle 98 | #QualifierAlignment: Custom 99 | #QualifierOrder: ['inline', 'static', 'constexpr', 'restrict', 'type', 'const', 'volatile' ] 100 | QualifierAlignment: Custom 101 | QualifierOrder: ['type', 'const' ] 102 | ReflowComments: true 103 | ReferenceAlignment: Middle 104 | RequiresClausePosition: OwnLine 105 | #SeparateDefinitionBlocks: Always # Problem: Does not take into account MaxEmptyLinesToKeep 106 | SortIncludes: true 107 | SortUsingDeclarations: true 108 | SpaceAfterCStyleCast: false 109 | SpaceAfterLogicalNot: false 110 | SpaceAfterTemplateKeyword: true 111 | SpaceBeforeAssignmentOperators: true 112 | SpaceBeforeCpp11BracedList: false 113 | SpaceBeforeCtorInitializerColon: true 114 | SpaceBeforeInheritanceColon: true 115 | SpaceBeforeParens: ControlStatements 116 | SpaceBeforeRangeBasedForLoopColon: true 117 | SpaceBeforeSquareBrackets: false 118 | SpaceInEmptyBlock: true 119 | SpaceInEmptyParentheses: false 120 | SpacesBeforeTrailingComments: 1 121 | SpacesInAngles: false 122 | SpacesInCStyleCastParentheses: false 123 | SpacesInContainerLiterals: true 124 | SpacesInParentheses: false 125 | SpacesInSquareBrackets: false 126 | Standard: Latest 127 | TabWidth: 2 128 | UseCRLF: true 129 | UseTab: Never 130 | ... 131 | 132 | -------------------------------------------------------------------------------- /tests/tiny-optional.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33103.184 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Tests", "Tests.vcxproj", "{67F2DCFE-B0F2-429F-864E-AA7962BC981F}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Msvc Debug C++17 permissive|Win32 = Msvc Debug C++17 permissive|Win32 11 | Msvc Debug C++17 permissive|x64 = Msvc Debug C++17 permissive|x64 12 | Msvc Debug C++17|Win32 = Msvc Debug C++17|Win32 13 | Msvc Debug C++17|x64 = Msvc Debug C++17|x64 14 | Msvc Debug C++20 No UB|Win32 = Msvc Debug C++20 No UB|Win32 15 | Msvc Debug C++20 No UB|x64 = Msvc Debug C++20 No UB|x64 16 | Msvc Debug C++20 permissive|Win32 = Msvc Debug C++20 permissive|Win32 17 | Msvc Debug C++20 permissive|x64 = Msvc Debug C++20 permissive|x64 18 | Msvc Debug C++20|Win32 = Msvc Debug C++20|Win32 19 | Msvc Debug C++20|x64 = Msvc Debug C++20|x64 20 | Msvc Release C++17|Win32 = Msvc Release C++17|Win32 21 | Msvc Release C++17|x64 = Msvc Release C++17|x64 22 | Msvc Release C++20|Win32 = Msvc Release C++20|Win32 23 | Msvc Release C++20|x64 = Msvc Release C++20|x64 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {67F2DCFE-B0F2-429F-864E-AA7962BC981F}.Msvc Debug C++17 permissive|Win32.ActiveCfg = Msvc Debug C++17 permissive|Win32 27 | {67F2DCFE-B0F2-429F-864E-AA7962BC981F}.Msvc Debug C++17 permissive|Win32.Build.0 = Msvc Debug C++17 permissive|Win32 28 | {67F2DCFE-B0F2-429F-864E-AA7962BC981F}.Msvc Debug C++17 permissive|x64.ActiveCfg = Msvc Debug C++17 permissive|x64 29 | {67F2DCFE-B0F2-429F-864E-AA7962BC981F}.Msvc Debug C++17 permissive|x64.Build.0 = Msvc Debug C++17 permissive|x64 30 | {67F2DCFE-B0F2-429F-864E-AA7962BC981F}.Msvc Debug C++17|Win32.ActiveCfg = Msvc Debug C++17|Win32 31 | {67F2DCFE-B0F2-429F-864E-AA7962BC981F}.Msvc Debug C++17|Win32.Build.0 = Msvc Debug C++17|Win32 32 | {67F2DCFE-B0F2-429F-864E-AA7962BC981F}.Msvc Debug C++17|x64.ActiveCfg = Msvc Debug C++17|x64 33 | {67F2DCFE-B0F2-429F-864E-AA7962BC981F}.Msvc Debug C++17|x64.Build.0 = Msvc Debug C++17|x64 34 | {67F2DCFE-B0F2-429F-864E-AA7962BC981F}.Msvc Debug C++20 No UB|Win32.ActiveCfg = Msvc Debug C++20 No UB|Win32 35 | {67F2DCFE-B0F2-429F-864E-AA7962BC981F}.Msvc Debug C++20 No UB|Win32.Build.0 = Msvc Debug C++20 No UB|Win32 36 | {67F2DCFE-B0F2-429F-864E-AA7962BC981F}.Msvc Debug C++20 No UB|x64.ActiveCfg = Msvc Debug C++20 No UB|x64 37 | {67F2DCFE-B0F2-429F-864E-AA7962BC981F}.Msvc Debug C++20 No UB|x64.Build.0 = Msvc Debug C++20 No UB|x64 38 | {67F2DCFE-B0F2-429F-864E-AA7962BC981F}.Msvc Debug C++20 permissive|Win32.ActiveCfg = Msvc Debug C++20 permissive|Win32 39 | {67F2DCFE-B0F2-429F-864E-AA7962BC981F}.Msvc Debug C++20 permissive|Win32.Build.0 = Msvc Debug C++20 permissive|Win32 40 | {67F2DCFE-B0F2-429F-864E-AA7962BC981F}.Msvc Debug C++20 permissive|x64.ActiveCfg = Msvc Debug C++20 permissive|x64 41 | {67F2DCFE-B0F2-429F-864E-AA7962BC981F}.Msvc Debug C++20 permissive|x64.Build.0 = Msvc Debug C++20 permissive|x64 42 | {67F2DCFE-B0F2-429F-864E-AA7962BC981F}.Msvc Debug C++20|Win32.ActiveCfg = Msvc Debug C++20|Win32 43 | {67F2DCFE-B0F2-429F-864E-AA7962BC981F}.Msvc Debug C++20|Win32.Build.0 = Msvc Debug C++20|Win32 44 | {67F2DCFE-B0F2-429F-864E-AA7962BC981F}.Msvc Debug C++20|x64.ActiveCfg = Msvc Debug C++20|x64 45 | {67F2DCFE-B0F2-429F-864E-AA7962BC981F}.Msvc Debug C++20|x64.Build.0 = Msvc Debug C++20|x64 46 | {67F2DCFE-B0F2-429F-864E-AA7962BC981F}.Msvc Release C++17|Win32.ActiveCfg = Msvc Release C++17|Win32 47 | {67F2DCFE-B0F2-429F-864E-AA7962BC981F}.Msvc Release C++17|Win32.Build.0 = Msvc Release C++17|Win32 48 | {67F2DCFE-B0F2-429F-864E-AA7962BC981F}.Msvc Release C++17|x64.ActiveCfg = Msvc Release C++17|x64 49 | {67F2DCFE-B0F2-429F-864E-AA7962BC981F}.Msvc Release C++17|x64.Build.0 = Msvc Release C++17|x64 50 | {67F2DCFE-B0F2-429F-864E-AA7962BC981F}.Msvc Release C++20|Win32.ActiveCfg = Msvc Release C++20|Win32 51 | {67F2DCFE-B0F2-429F-864E-AA7962BC981F}.Msvc Release C++20|Win32.Build.0 = Msvc Release C++20|Win32 52 | {67F2DCFE-B0F2-429F-864E-AA7962BC981F}.Msvc Release C++20|x64.ActiveCfg = Msvc Release C++20|x64 53 | {67F2DCFE-B0F2-429F-864E-AA7962BC981F}.Msvc Release C++20|x64.Build.0 = Msvc Release C++20|x64 54 | EndGlobalSection 55 | GlobalSection(SolutionProperties) = preSolution 56 | HideSolutionNode = FALSE 57 | EndGlobalSection 58 | GlobalSection(ExtensibilityGlobals) = postSolution 59 | SolutionGuid = {BC62C5E4-DE67-4515-8770-551180193E52} 60 | EndGlobalSection 61 | EndGlobal 62 | -------------------------------------------------------------------------------- /performance/performance.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | Release 10 | x64 11 | 12 | 13 | 14 | 16.0 15 | Win32Proj 16 | {92817f05-479c-4d77-a84c-0485e3ac135f} 17 | performance 18 | 10.0 19 | 20 | 21 | 22 | Application 23 | true 24 | v142 25 | Unicode 26 | 27 | 28 | Application 29 | false 30 | v142 31 | true 32 | Unicode 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | true 48 | 49 | 50 | false 51 | 52 | 53 | 54 | Level4 55 | true 56 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 57 | true 58 | $(SolutionDir)..\include;%(AdditionalIncludeDirectories) 59 | stdcpp17 60 | AdvancedVectorExtensions 61 | false 62 | 63 | 64 | Console 65 | true 66 | 67 | 68 | 69 | 70 | Level4 71 | true 72 | true 73 | false 74 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 75 | true 76 | $(SolutionDir)..\include;%(AdditionalIncludeDirectories) 77 | stdcpp17 78 | AdvancedVectorExtensions 79 | false 80 | 81 | 82 | Console 83 | true 84 | true 85 | true 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /tests/Tests.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | Source Files 29 | 30 | 31 | Source Files 32 | 33 | 34 | Source Files 35 | 36 | 37 | Source Files 38 | 39 | 40 | Source Files 41 | 42 | 43 | Source Files 44 | 45 | 46 | Source Files 47 | 48 | 49 | Source Files 50 | 51 | 52 | Source Files 53 | 54 | 55 | Source Files 56 | 57 | 58 | Source Files 59 | 60 | 61 | Source Files 62 | 63 | 64 | Source Files 65 | 66 | 67 | Source Files 68 | 69 | 70 | Source Files 71 | 72 | 73 | 74 | 75 | Header Files 76 | 77 | 78 | Header Files 79 | 80 | 81 | Header Files 82 | 83 | 84 | Header Files 85 | 86 | 87 | Header Files 88 | 89 | 90 | Header Files 91 | 92 | 93 | Header Files 94 | 95 | 96 | Header Files 97 | 98 | 99 | Header Files 100 | 101 | 102 | Header Files 103 | 104 | 105 | Header Files 106 | 107 | 108 | Header Files 109 | 110 | 111 | Header Files 112 | 113 | 114 | Header Files 115 | 116 | 117 | Header Files 118 | 119 | 120 | Header Files 121 | 122 | 123 | Header Files 124 | 125 | 126 | Header Files 127 | 128 | 129 | Header Files 130 | 131 | 132 | Header Files 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /tests/IntermediateTests.cpp: -------------------------------------------------------------------------------- 1 | #include "IntermediateTests.h" 2 | 3 | #include "TestTypes.h" 4 | #include "TestUtilities.h" 5 | #include "tiny/optional.h" 6 | 7 | void test_IsIntegralInRange() 8 | { 9 | using namespace tiny::impl; 10 | 11 | static_assert(IsIntegralInRange(-1)); 12 | static_assert(IsIntegralInRange(1)); 13 | 14 | static_assert(IsIntegralInRange(1)); 15 | static_assert(!IsIntegralInRange(-1)); 16 | 17 | static_assert(IsIntegralInRange(INT8_MIN)); 18 | static_assert(IsIntegralInRange(INT8_MAX)); 19 | static_assert(!IsIntegralInRange(UINT8_MAX)); 20 | static_assert(IsIntegralInRange(std::intmax_t{INT8_MIN})); 21 | static_assert(IsIntegralInRange(std::intmax_t{INT8_MAX})); 22 | static_assert(!IsIntegralInRange(std::intmax_t{INT8_MIN} - 1)); 23 | static_assert(!IsIntegralInRange(std::intmax_t{INT8_MAX} + 1)); 24 | 25 | static_assert(IsIntegralInRange(0)); 26 | static_assert(IsIntegralInRange(UINT8_MAX)); 27 | static_assert(!IsIntegralInRange(INT8_MIN)); 28 | static_assert(IsIntegralInRange(std::intmax_t{0})); 29 | static_assert(IsIntegralInRange(std::intmax_t{INT8_MAX})); 30 | static_assert(!IsIntegralInRange(std::intmax_t{-1})); 31 | static_assert(!IsIntegralInRange(std::intmax_t{UINT8_MAX} + 1)); 32 | 33 | static_assert(IsIntegralInRange(INTMAX_MIN)); 34 | static_assert(IsIntegralInRange(INTMAX_MAX)); 35 | static_assert(!IsIntegralInRange(UINTMAX_MAX)); 36 | 37 | static_assert(IsIntegralInRange(0)); 38 | static_assert(IsIntegralInRange(UINTMAX_MAX)); 39 | static_assert(!IsIntegralInRange(INTMAX_MIN)); 40 | } 41 | 42 | 43 | void test_NanExploit() 44 | { 45 | using namespace tiny::impl; 46 | 47 | #ifndef TINY_OPTIONAL_USE_SEPARATE_BOOL_INSTEAD_OF_UNUSED_BITS 48 | { 49 | #ifndef __FAST_MATH__ // std::isnan is broken with -ffast-math 50 | float testValue; 51 | std::memcpy(&testValue, &SentinelForExploitingUnusedBits::value, sizeof(float)); 52 | ASSERT_TRUE(std::isnan(testValue)); 53 | #endif 54 | 55 | constexpr float sNaN = std::numeric_limits::signaling_NaN(); 56 | ASSERT_TRUE(std::memcmp(&SentinelForExploitingUnusedBits::value, &sNaN, sizeof(float)) != 0); 57 | constexpr float qNaN = std::numeric_limits::quiet_NaN(); 58 | ASSERT_TRUE(std::memcmp(&SentinelForExploitingUnusedBits::value, &qNaN, sizeof(float)) != 0); 59 | } 60 | { 61 | #ifndef __FAST_MATH__ // std::isnan is broken with -ffast-math 62 | double testValue; 63 | std::memcpy(&testValue, &SentinelForExploitingUnusedBits::value, sizeof(double)); 64 | ASSERT_TRUE(std::isnan(testValue)); 65 | #endif 66 | 67 | constexpr double sNaN = std::numeric_limits::signaling_NaN(); 68 | ASSERT_TRUE(std::memcmp(&SentinelForExploitingUnusedBits::value, &sNaN, sizeof(double)) != 0); 69 | constexpr double qNaN = std::numeric_limits::quiet_NaN(); 70 | ASSERT_TRUE(std::memcmp(&SentinelForExploitingUnusedBits::value, &qNaN, sizeof(double)) != 0); 71 | } 72 | #endif 73 | } 74 | 75 | 76 | void test_SelectDecomposition() 77 | { 78 | using namespace tiny; 79 | using namespace tiny::impl; 80 | 81 | // clang-format off 82 | static_assert(NoArgsAndBehavesAsStdOptional == SelectDecomposition::test); 83 | static_assert(NoArgsAndBehavesAsStdOptional == SelectDecomposition::test); 84 | static_assert(SentinelValueSpecifiedForInplaceSwallowing == SelectDecomposition, UseDefaultValue>::test); 85 | static_assert(SentinelValueAndMemPtrSpecifiedForInplaceSwallowing == SelectDecomposition, &TestClassForInplace::someInt>::test); 86 | 87 | #ifndef TINY_OPTIONAL_USE_SEPARATE_BOOL_INSTEAD_OF_UNUSED_BITS 88 | static_assert(NoArgsAndHasCustomFlagManipulator == SelectDecomposition::test); 89 | static_assert(SentinelValueSpecifiedForInplaceSwallowingForTypeWithCustomFlagManipulator == SelectDecomposition::test); 90 | static_assert(SentinelValueAndMemPtrSpecifiedForInplaceSwallowingForTypeWithCustomFlagManipulator == SelectDecomposition::test); 91 | static_assert(MemPtrSpecifiedToVariableWithCustomFlagManipulator == SelectDecomposition::test); 92 | #else 93 | static_assert(NoArgsAndBehavesAsStdOptional == SelectDecomposition::test); 94 | static_assert(SentinelValueSpecifiedForInplaceSwallowing == SelectDecomposition::test); 95 | static_assert(SentinelValueAndMemPtrSpecifiedForInplaceSwallowing == SelectDecomposition::test); 96 | #ifndef TINY_OPTIONAL_USE_SEPARATE_BOOL_INSTEAD_OF_MEMBER 97 | static_assert(MemPtrSpecifiedToVariableWithoutCustomFlagManipulator == SelectDecomposition::test); 98 | #endif 99 | #endif 100 | 101 | // Should not compile: 102 | //static_assert(3 == SelectDecomposition, UseDefaultValue>::test); 103 | //std::cout << "F = " << SelectDecomposition::test << std::endl; // someValue1 is int, no default value possible 104 | //std::cout << "G = " << SelectDecomposition::test << std::endl; // Mismatching type and memptr 105 | //std::cout << "H = " << SelectDecomposition, &TestClassForInplace::someValue1>::test << std::endl; // Mismatching type and memptr 106 | 107 | // clang-format on 108 | } 109 | -------------------------------------------------------------------------------- /tests/makefile: -------------------------------------------------------------------------------- 1 | PWD = $(shell pwd) 2 | CXX_CLANG ?= clang++ 3 | CXX_GCC ?= g++ 4 | 5 | BIN_DIR = ../bin 6 | EXE_NAME = TinyOptionalTests 7 | #SANITIZE_FLAGS = -fsanitize=undefined -fsanitize=address -fsanitize-address-use-after-scope 8 | SANITIZE_FLAGS = 9 | WARNING_FLAGS = -Wall -Wextra -pedantic -Wconversion -Werror 10 | TINY_OPTIONAL_INCLUDE_DIR_CMD = $(PWD)/../include 11 | TINY_OPTIONAL_INCLUDE_DIR_IN_EXE = $(TINY_OPTIONAL_INCLUDE_DIR_CMD) 12 | 13 | # The github workflow for gcc on windows sets the IS_MSYS variable. In msys, we need to convert the include path 14 | # that we build above (which is unix style) to windows style for use in our C++ program. See https://www.msys2.org/docs/filesystem-paths/ 15 | ifdef IS_MSYS 16 | TINY_OPTIONAL_INCLUDE_DIR_IN_EXE = $(shell cygpath -m $(TINY_OPTIONAL_INCLUDE_DIR_CMD)) 17 | endif 18 | 19 | 20 | CPP_FILES = ComparisonTests.cpp CompilationBase.cpp CompilationErrorTests.cpp ConstructionTests.cpp ExerciseOptionalAIP.cpp ExerciseOptionalEmptyViaType.cpp ExerciseOptionalInplace.cpp ExerciseOptionalWithCustomFlagManipulator.cpp ExerciseStdOptional.cpp ExerciseTinyOptionalPayload1.cpp ExerciseTinyOptionalPayload2.cpp GccLikeCompilation.cpp IntermediateTests.cpp MsvcCompilation.cpp NatvisTests.cpp SpecialMonadicTests.cpp Tests.cpp TestUtilities.cpp 21 | 22 | 23 | CXX_AND_RUN_COMMAND = \ 24 | $(eval OUT_DIR = $(BIN_DIR)/$@) \ 25 | mkdir -p $(OUT_DIR) \ 26 | && $(CXX) -o $(OUT_DIR)/$(EXE_NAME) -I"$(TINY_OPTIONAL_INCLUDE_DIR_CMD)" $(ADDITIONAL_FLAGS) $(WARNING_FLAGS) $(SANITIZE_FLAGS) -pthread -DTINY_OPTIONAL_TESTS_COMPILER_EXECUTABLE='"$(CXX)"' -DTINY_OPTIONAL_TESTS_HEADER_INCLUDE_DIR='"$(TINY_OPTIONAL_INCLUDE_DIR_IN_EXE)"' -DTINY_OPTIONAL_TESTS_COMPILATION_FLAGS='"$(ADDITIONAL_FLAGS) $(WARNING_FLAGS)"' $(CPP_FILES) \ 27 | && $(OUT_DIR)/$(EXE_NAME) 28 | 29 | 30 | #--------------------------------------------------------------------- 31 | # clang 32 | #--------------------------------------------------------------------- 33 | 34 | clang_tidy: $(CPP_FILES) 35 | $(eval ADDITIONAL_FLAGS = -m64 -std=c++20 -stdlib=libc++ -O3) 36 | clang-tidy $(CPP_FILES) -checks=clang-analyzer-* -- -I"$(TINY_OPTIONAL_INCLUDE_DIR_CMD)" $(ADDITIONAL_FLAGS) $(WARNING_FLAGS) $(SANITIZE_FLAGS) -pthread -DTINY_OPTIONAL_TESTS_COMPILER_EXECUTABLE='"$(CXX)"' -DTINY_OPTIONAL_TESTS_HEADER_INCLUDE_DIR='"$(TINY_OPTIONAL_INCLUDE_DIR_IN_EXE)"' -DTINY_OPTIONAL_TESTS_COMPILATION_FLAGS='"$(ADDITIONAL_FLAGS) $(WARNING_FLAGS)"' 37 | 38 | 39 | clang_x64_cpp17_libcpp_debug: $(CPP_FILES) 40 | $(eval CXX = $(CXX_CLANG)) 41 | $(eval ADDITIONAL_FLAGS = -m64 -std=c++17 -stdlib=libc++) 42 | $(CXX_AND_RUN_COMMAND) 43 | 44 | clang_x64_cpp17_libcpp_release: $(CPP_FILES) 45 | $(eval CXX = $(CXX_CLANG)) 46 | $(eval ADDITIONAL_FLAGS = -m64 -std=c++17 -O3 -DNDEBUG -stdlib=libc++) 47 | $(CXX_AND_RUN_COMMAND) 48 | 49 | clang_x64_cpp20_libcpp_debug: $(CPP_FILES) 50 | $(eval CXX = $(CXX_CLANG)) 51 | $(eval ADDITIONAL_FLAGS = -m64 -std=c++20 -stdlib=libc++) 52 | $(CXX_AND_RUN_COMMAND) 53 | 54 | clang_x64_cpp20_libcpp_release: $(CPP_FILES) 55 | $(eval CXX = $(CXX_CLANG)) 56 | $(eval ADDITIONAL_FLAGS = -m64 -std=c++20 -O3 -DNDEBUG -stdlib=libc++) 57 | $(CXX_AND_RUN_COMMAND) 58 | 59 | clang_x64_cpp23_libcpp_debug: $(CPP_FILES) 60 | $(eval CXX = $(CXX_CLANG)) 61 | $(eval ADDITIONAL_FLAGS = -m64 -std=c++2b -stdlib=libc++) 62 | $(CXX_AND_RUN_COMMAND) 63 | 64 | 65 | clang_x86_cpp20_libcpp_debug: $(CPP_FILES) 66 | $(eval CXX = $(CXX_CLANG)) 67 | $(eval ADDITIONAL_FLAGS = -m32 -std=c++20 -stdlib=libc++) 68 | $(CXX_AND_RUN_COMMAND) 69 | 70 | clang_x86_cpp17_libcpp_debug: $(CPP_FILES) 71 | $(eval CXX = $(CXX_CLANG)) 72 | $(eval ADDITIONAL_FLAGS = -m32 -std=c++17 -stdlib=libc++) 73 | $(CXX_AND_RUN_COMMAND) 74 | 75 | 76 | clang_x64_cpp20_gcclib_debug: $(CPP_FILES) 77 | $(eval CXX = $(CXX_CLANG)) 78 | $(eval ADDITIONAL_FLAGS = -m64 -std=c++20 -stdlib=libstdc++) 79 | $(CXX_AND_RUN_COMMAND) 80 | 81 | clang_x64_cpp17_gcclib_debug: $(CPP_FILES) 82 | $(eval CXX = $(CXX_CLANG)) 83 | $(eval ADDITIONAL_FLAGS = -m64 -std=c++17 -stdlib=libstdc++) 84 | $(CXX_AND_RUN_COMMAND) 85 | 86 | 87 | clang_x64_cpp20_libcpp_debug_noUB: $(CPP_FILES) 88 | $(eval CXX = $(CXX_CLANG)) 89 | $(eval ADDITIONAL_FLAGS = -m64 -std=c++20 -stdlib=libc++ -DTINY_OPTIONAL_USE_SEPARATE_BOOL_INSTEAD_OF_UB_TRICKS) 90 | $(CXX_AND_RUN_COMMAND) 91 | 92 | 93 | #--------------------------------------------------------------------- 94 | # gcc 95 | #--------------------------------------------------------------------- 96 | 97 | gcc_x64_cpp20_debug: $(CPP_FILES) 98 | $(eval CXX = $(CXX_GCC)) 99 | $(eval ADDITIONAL_FLAGS = -m64 -std=c++20) 100 | $(CXX_AND_RUN_COMMAND) 101 | 102 | gcc_x64_cpp17_debug: $(CPP_FILES) 103 | $(eval CXX = $(CXX_GCC)) 104 | $(eval ADDITIONAL_FLAGS = -m64 -std=c++17) 105 | $(CXX_AND_RUN_COMMAND) 106 | 107 | gcc_x86_cpp20_debug: $(CPP_FILES) 108 | $(eval CXX = $(CXX_GCC)) 109 | $(eval ADDITIONAL_FLAGS = -m32 -std=c++20) 110 | $(CXX_AND_RUN_COMMAND) 111 | 112 | gcc_x86_cpp17_debug: $(CPP_FILES) 113 | $(eval CXX = $(CXX_GCC)) 114 | $(eval ADDITIONAL_FLAGS = -m32 -std=c++17) 115 | $(CXX_AND_RUN_COMMAND) 116 | 117 | gcc_x86_cpp17_release: $(CPP_FILES) 118 | $(eval CXX = $(CXX_GCC)) 119 | $(eval ADDITIONAL_FLAGS = -m32 -std=c++17 -O3 -DNDEBUG) 120 | $(CXX_AND_RUN_COMMAND) 121 | 122 | gcc_x64_cpp20_release: $(CPP_FILES) 123 | $(eval CXX = $(CXX_GCC)) 124 | $(eval ADDITIONAL_FLAGS = -m64 -std=c++20 -O3 -DNDEBUG) 125 | $(CXX_AND_RUN_COMMAND) 126 | 127 | gcc_x64_cpp20_fastmath_release: $(CPP_FILES) 128 | $(eval CXX = $(CXX_GCC)) 129 | $(eval ADDITIONAL_FLAGS = -m64 -std=c++20 -O3 -DNDEBUG -ffast-math) 130 | $(CXX_AND_RUN_COMMAND) 131 | 132 | gcc_x64_cpp17_release: $(CPP_FILES) 133 | $(eval CXX = $(CXX_GCC)) 134 | $(eval ADDITIONAL_FLAGS = -m64 -std=c++17 -O3 -DNDEBUG) 135 | $(CXX_AND_RUN_COMMAND) 136 | 137 | gcc_x64_cpp20_release_noUB: $(CPP_FILES) 138 | $(eval CXX = $(CXX_GCC)) 139 | $(eval ADDITIONAL_FLAGS = -m64 -std=c++20 -O3 -DNDEBUG -DTINY_OPTIONAL_USE_SEPARATE_BOOL_INSTEAD_OF_UB_TRICKS) 140 | $(CXX_AND_RUN_COMMAND) 141 | 142 | 143 | #--------------------------------------------------------------------- 144 | # generic version, used by the github actions 145 | # Expects that CXX and ADDITIONAL_FLAGS are set from the environment. 146 | #--------------------------------------------------------------------- 147 | 148 | generic: $(CPP_FILES) 149 | $(CXX_AND_RUN_COMMAND) 150 | 151 | -------------------------------------------------------------------------------- /tests/TestTypes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "TestUtilities.h" 4 | 5 | #include 6 | #include 7 | 8 | #if defined(__GNUG__) && !defined(__clang__) 9 | // Disable incorrect warning for gcc. 10 | #pragma GCC diagnostic push 11 | #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" 12 | #endif 13 | 14 | enum class ScopedEnum 15 | { 16 | v1, 17 | v2, 18 | vend 19 | }; 20 | 21 | 22 | enum UnscopedEnum 23 | { 24 | UE_v1, 25 | UE_v2, 26 | UE_end 27 | }; 28 | 29 | 30 | struct TestClass 31 | { 32 | bool IsValid() const noexcept 33 | { 34 | return isValid; 35 | } 36 | 37 | bool isValid = false; 38 | double someValue = 0.0; 39 | 40 | friend bool operator==(TestClass const & lhs, TestClass const & rhs) 41 | { 42 | return lhs.isValid == rhs.isValid && MatchingFloat(lhs.someValue, rhs.someValue); 43 | } 44 | 45 | TestClass() = default; 46 | 47 | TestClass(bool isValid, double someValue) 48 | : isValid(isValid) 49 | , someValue(someValue) 50 | { 51 | } 52 | }; 53 | 54 | namespace std 55 | { 56 | template <> 57 | struct hash 58 | { 59 | size_t operator()(TestClass const & v) const 60 | { 61 | size_t s = 0; 62 | hash_combine(s, v.isValid); 63 | hash_combine(s, v.someValue); 64 | return s; 65 | } 66 | }; 67 | } // namespace std 68 | 69 | 70 | struct FlagManipulatorForTestClass 71 | { 72 | [[nodiscard]] static bool is_empty(TestClass const & flag) noexcept 73 | { 74 | return !flag.IsValid(); 75 | } 76 | 77 | static void init_empty_flag(TestClass & uninitializedFlagMemory) noexcept 78 | { 79 | // Placement new because memory is already allocated. 80 | ::new (&uninitializedFlagMemory) TestClass(); 81 | } 82 | 83 | static void invalidate_empty_flag(TestClass & emptyFlag) noexcept 84 | { 85 | // Freeing memory is handled by the TinyOptional implementation. 86 | emptyFlag.~TestClass(); 87 | } 88 | }; 89 | 90 | 91 | struct TestClassForInplace 92 | { 93 | int someInt = -1; 94 | unsigned someUnsigned = 3; 95 | double someDouble = 1.0; 96 | float someFloat = 2.0; 97 | bool someBool = false; 98 | TestClassForInplace * somePtr = nullptr; 99 | 100 | friend bool operator==(TestClassForInplace const & lhs, TestClassForInplace const & rhs) 101 | { 102 | return lhs.someInt == rhs.someInt && lhs.someUnsigned == rhs.someUnsigned 103 | && MatchingFloat(lhs.someDouble, rhs.someDouble) && MatchingFloat(lhs.someFloat, rhs.someFloat) 104 | && lhs.someBool == rhs.someBool && lhs.somePtr == rhs.somePtr; 105 | } 106 | 107 | TestClassForInplace() = default; 108 | 109 | TestClassForInplace(int someValue1, double someValue2, unsigned someValue3, TestClassForInplace * someValue4) 110 | : someInt(someValue1) 111 | , someUnsigned(someValue3) 112 | , someDouble(someValue2) 113 | , somePtr(someValue4) 114 | { 115 | } 116 | }; 117 | 118 | namespace std 119 | { 120 | template <> 121 | struct hash 122 | { 123 | size_t operator()(TestClassForInplace const & v) const 124 | { 125 | size_t s = 0; 126 | hash_combine(s, v.someInt); 127 | hash_combine(s, v.someUnsigned); 128 | hash_combine(s, v.someDouble); 129 | hash_combine(s, v.someFloat); 130 | hash_combine(s, v.someBool); 131 | hash_combine(s, v.somePtr); 132 | return s; 133 | } 134 | }; 135 | } // namespace std 136 | 137 | 138 | struct TestClassWithInitializerList 139 | { 140 | std::vector values; 141 | 142 | TestClassWithInitializerList(std::initializer_list v) 143 | : values(v) 144 | { 145 | } 146 | 147 | TestClassWithInitializerList(std::initializer_list v, ScopedEnum anotherValue) 148 | : values(v) 149 | { 150 | values.emplace_back(static_cast(anotherValue)); 151 | } 152 | 153 | friend bool operator==(TestClassWithInitializerList const & lhs, TestClassWithInitializerList const & rhs) 154 | { 155 | return lhs.values == rhs.values; 156 | } 157 | }; 158 | 159 | 160 | struct TestClassPrivate 161 | { 162 | friend bool operator==(TestClassPrivate const & lhs, TestClassPrivate const & rhs) 163 | { 164 | return MatchingFloat(lhs.someValue, rhs.someValue); 165 | } 166 | 167 | friend void test_TinyOptionalPayload_OtherTypes(); 168 | 169 | TestClassPrivate() 170 | : someValue(-1.0) 171 | { 172 | } 173 | 174 | explicit TestClassPrivate(double value) 175 | : someValue(value) 176 | { 177 | } 178 | 179 | private: 180 | double someValue; 181 | }; 182 | 183 | 184 | // Various stuff is noexcept(false) 185 | struct TestClassWithExcept 186 | { 187 | TestClassWithExcept() noexcept(false) { } 188 | 189 | TestClassWithExcept(int value) noexcept(false) 190 | : someValue(value) 191 | { 192 | } 193 | 194 | TestClassWithExcept(TestClassWithExcept const & rhs) noexcept(false) 195 | : someValue(rhs.someValue) 196 | { 197 | } 198 | 199 | TestClassWithExcept & operator=(TestClassWithExcept const & rhs) noexcept(false) 200 | { 201 | someValue = rhs.someValue; 202 | return *this; 203 | } 204 | 205 | friend bool operator==(TestClassWithExcept const & lhs, TestClassWithExcept const & rhs) 206 | { 207 | return lhs.someValue == rhs.someValue; 208 | } 209 | 210 | int someValue = 42; 211 | }; 212 | 213 | 214 | typedef int (*TestFuncPtr)(double, TestClass &); 215 | 216 | inline int TestFunc1(double, TestClass &) 217 | { 218 | return -1; 219 | } 220 | 221 | inline int TestFunc2(double, TestClass &) 222 | { 223 | return -1; 224 | } 225 | 226 | 227 | struct TestDoubleValue 228 | { 229 | static constexpr double value = 42.0; 230 | }; 231 | 232 | 233 | // Used for C++20 tests where we specify a sentinel value at compile to be an instance of this class. 234 | struct LiteralClass 235 | { 236 | constexpr LiteralClass(int v1, double v2) 237 | : v1(v1) 238 | , v2(v2) 239 | { 240 | } 241 | 242 | // Not default so that we have a non-trivial copy constructor. 243 | constexpr LiteralClass(LiteralClass const & rhs) 244 | : v1(rhs.v1) 245 | , v2(rhs.v2) 246 | { 247 | } 248 | 249 | // Not default so that we have a non-trivial copy assignment. 250 | constexpr LiteralClass & operator=(LiteralClass const & rhs) noexcept 251 | { 252 | v1 = rhs.v1; 253 | v2 = rhs.v2; 254 | return *this; 255 | } 256 | 257 | int v1; 258 | double v2; 259 | }; 260 | 261 | inline constexpr bool operator==(LiteralClass const & lhs, LiteralClass const & rhs) noexcept 262 | { 263 | return lhs.v1 == rhs.v1 && lhs.v2 == rhs.v2; 264 | } 265 | 266 | 267 | // Used for C++20 tests where we specify a sentinel value at compile to be an instance of this class, and stored in the 268 | // member lc. 269 | struct TestClassWithLiteralClass 270 | { 271 | TestClassWithLiteralClass() 272 | : TestClassWithLiteralClass(1, 2.0, 3) 273 | { 274 | } 275 | 276 | TestClassWithLiteralClass(int v1, double v2, int v3) 277 | : lc(v1, v2) 278 | , someInt(v3) 279 | { 280 | } 281 | 282 | LiteralClass lc; 283 | int someInt; 284 | }; 285 | 286 | inline bool operator==(TestClassWithLiteralClass const & lhs, TestClassWithLiteralClass const & rhs) 287 | { 288 | return lhs.lc == rhs.lc && lhs.someInt == rhs.someInt; 289 | } 290 | 291 | 292 | 293 | struct TrivialStruct 294 | { 295 | }; 296 | 297 | #if defined(__GNUG__) && !defined(__clang__) 298 | #pragma GCC diagnostic pop 299 | #endif 300 | -------------------------------------------------------------------------------- /.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 | 366 | 367 | 368 | TinyOptionalClang 369 | TinyOptionalGCC 370 | LinuxBin 371 | Debug C++17 372 | Debug C++20 373 | clangPerf 374 | gccPerf 375 | 376 | .vscode 377 | 378 | *.o 379 | a.out 380 | *.exe 381 | -------------------------------------------------------------------------------- /tests/CompilationErrorTests.cpp: -------------------------------------------------------------------------------- 1 | #include "CompilationErrorTests.h" 2 | 3 | #include "CompilationBase.h" 4 | #include "TestUtilities.h" 5 | #include "tiny/optional.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | 15 | void test_ExpressionsThatShouldNotCompile() 16 | { 17 | // clang-format off 18 | std::vector> const checks = { 19 | 20 | {/*code*/ "tiny::optional o;", 21 | /*expected regex*/ "The specified compile-time constant for the empty value is outside of the range supported"} 22 | 23 | , 24 | 25 | #ifndef TINY_OPTIONAL_USE_SEPARATE_BOOL_INSTEAD_OF_MEMBER 26 | {/*code*/ "struct TestClassForInplace { int someValue1 = -1; };\n" 27 | "tiny::optional o;", 28 | /*expected regex*/ "The type of the member variable given by the member-pointer cannot be used as flag if you do not specify a custom SentinelValue"} 29 | , 30 | #endif 31 | 32 | {/*code*/ "struct TestClassForInplace { double someValue1 = 0.0; };\n" 33 | "struct TestClass{};\n" 34 | "tiny::optional_sentinel_via_type o;", 35 | /*expected regex*/ "The flag given by the member-pointer is not a member of the payload type"} 36 | 37 | , 38 | 39 | {/*code*/ "struct TestClassForInplace { int someValue1 = -1; };\n" 40 | "struct TestClass{};\n" 41 | "tiny::optional_sentinel_via_type, &TestClassForInplace::someValue1> o;", 42 | /*expected regex*/ "The flag given by the member-pointer is not a member of the payload type"} 43 | 44 | , 45 | 46 | {/*code*/ "tiny::optional o;", 47 | /*expected regex*/ "is a bool, but not both. If one is a bool, both should be a bool"} 48 | 49 | , 50 | 51 | {/*code*/ "enum UnscopedEnum{val};\n" 52 | "tiny::optional o;", 53 | // Note: Compiler-dependent error message. First option for MSVC, second for clang, third for gcc. 54 | /*expected regex*/ "(return.*cannot.*convert)|(cannot.*return.*UnscopedEnum)|(invalid.*conversion.*UnscopedEnum)"} 55 | 56 | , 57 | 58 | // The following check roughly tests that the copy constructor of the optional is not available if the payload 59 | // does not have one. 60 | {/*code*/ R"( 61 | struct WithoutCopy 62 | { 63 | WithoutCopy() = default; 64 | WithoutCopy(WithoutCopy const &) = delete; 65 | WithoutCopy(WithoutCopy &&) = default; 66 | }; 67 | tiny::optional orig; 68 | [[maybe_unused]] tiny::optional copyTarget(orig); 69 | )", 70 | /*expected regex*/ "delete"} 71 | 72 | , 73 | 74 | // Should not compile because the std::in_place constructor is explicit. 75 | {/*code*/ "struct TestClass{ TestClass(int v1, int v2){} };\n" 76 | "tiny::optional o = {std::in_place, 42, 43};", 77 | // First regex is for MSVC and clang, second for gcc. 78 | /*expected regex*/ "(explicit)|(could not convert)"} 79 | 80 | , 81 | 82 | // Since the payload is not implicitly constructible from int, copy-initialization should not be possible. 83 | {/*code*/ R"( 84 | struct ExplicitConversion { 85 | explicit ExplicitConversion(int v) : value(v) { } 86 | int value; 87 | }; 88 | tiny::optional o = 42; 89 | )", 90 | // First for MSVC, second for clang, fourth for gcc. 91 | /*expected regex*/ "(cannot.*convert)|(no.*viable.*conversion)|(error.*conversion.*from)"} 92 | 93 | , 94 | 95 | // Since the payload is not implicitly constructible from int, copy-list-initialization should not be possible. 96 | {/*code*/ R"( 97 | struct ExplicitConversion { 98 | explicit ExplicitConversion(int v) : value(v) { } 99 | int value; 100 | }; 101 | tiny::optional o = {42}; 102 | )", 103 | // First for MSVC, second for clang, third for gcc < 13, fourth for gcc >= 13. 104 | /*expected regex*/ "(copy-list-initialization.*cannot.*use.*an.*explicit.*constructor)|(constructor.*is.*explicit.*in.*copy-initialization)|(error.*could.*not.*convert)|(error.*would.*use.*explicit.*constructor)"} 105 | 106 | , 107 | 108 | {/*code*/ R"( 109 | struct ExplicitConversion { 110 | explicit ExplicitConversion(int v) : value(v) { } 111 | int value; 112 | }; 113 | tiny::optional o; 114 | o = 42; 115 | )", 116 | // First for MSVC, second for clang, third for gcc. 117 | /*expected regex*/ "(no.*operator.*found)|(no.*viable.*overloaded)|(no.*match.*for.*operator)"} 118 | 119 | , 120 | 121 | // Neither references nor raw arrays are allowed by the C++ standard. 122 | {/*code*/ "tiny::optional o;", 123 | /*expected regex*/ "The payload type must meet the C\\+\\+ requirement 'Destructible'"} 124 | , 125 | {/*code*/ "tiny::optional o;", 126 | /*expected regex*/ "The payload type must meet the C\\+\\+ requirement 'Destructible'"} 127 | 128 | , 129 | 130 | // Monadic operations 131 | {/*code*/ R"( 132 | tiny::optional o = 42; 133 | o.and_then([](int) { return 43; }); 134 | )", 135 | /*expected regex*/ "The standard requires 'f' to return an optional"} 136 | , 137 | {/*code*/ R"( 138 | tiny::optional o = 42; 139 | o.transform([](int) { return std::nullopt; }); 140 | )", 141 | /*expected regex*/ "The standard requires 'f' to not return a std::nullopt_t"} 142 | #ifdef TINY_OPTIONAL_ENABLE_ORELSE 143 | , 144 | {/*code*/ R"( 145 | tiny::optional o = 42; 146 | o.or_else([]() -> std::optional { return std::nullopt; }); 147 | )", 148 | /*expected regex*/ "The function F passed to OPT::or_else\\(F&&\\) needs to return an optional of the same type OPT"} 149 | , 150 | {/*code*/ R"( 151 | tiny::optional_sentinel_via_type o = 42; 152 | o.or_else([]() -> tiny::optional { return std::nullopt; }); 153 | )", 154 | /*expected regex*/ "The function F passed to OPT::or_else\\(F&&\\) needs to return an optional of the same type OPT"} 155 | #endif 156 | 157 | , 158 | 159 | {/*code*/ R"( 160 | tiny::optional_aip o; 161 | )", 162 | /*expected regex*/ "optional_aip: No automatic sentinel for the PayloadType available"} 163 | , 164 | {/*code*/ R"( 165 | struct Foo{}; 166 | tiny::optional_aip o; 167 | )", 168 | /*expected regex*/ "optional_aip: No automatic sentinel for the PayloadType available"} 169 | }; 170 | // clang-format on 171 | 172 | 173 | std::unique_ptr checker = CreateCompilationChecker(); 174 | checker->PrintInputOptions(std::cout); 175 | 176 | #ifdef TINY_OPTIONAL_WINDOWS_BUILD 177 | // Although every thread uses its own temporary files, running the checks in parallel on Windows results 178 | // in flaky tests that fail for all sorts of reasons (such as 'access denied' errors). 179 | static constexpr bool cRunSerial = true; 180 | #else 181 | static constexpr bool cRunSerial = false; 182 | #endif 183 | 184 | // Since the compilation of the above code snippets takes a notable time, we run them in parallel if possible. 185 | std::vector> checkResults; 186 | for (auto check : checks) { 187 | auto threadFunc = [&checker, check]() -> std::string { 188 | auto const & [toCompile, expectedErrorRegex] = check; 189 | return checker->CheckDoesNotCompile(toCompile, expectedErrorRegex); 190 | }; 191 | 192 | checkResults.push_back(std::async(cRunSerial ? std::launch::deferred : std::launch::async, std::move(threadFunc))); 193 | } 194 | ASSERT_TRUE(checkResults.size() == checks.size()); 195 | 196 | bool foundFailure = false; 197 | for (size_t idx = 0; idx < checkResults.size(); ++idx) { 198 | std::cout << "\tCompilation check " << idx + 1 << "/" << checks.size() << std::endl; 199 | auto const result = checkResults.at(idx).get(); 200 | if (!result.empty()) { 201 | if (foundFailure) { 202 | std::cerr << "========================================\n"; 203 | } 204 | std::cerr << result; 205 | foundFailure = true; 206 | std::cerr << "========================================\n"; 207 | } 208 | } 209 | 210 | if (foundFailure) { 211 | exit(43); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /tests/NatvisTests.cpp: -------------------------------------------------------------------------------- 1 | #include "NatvisTests.h" 2 | 3 | #include "TestTypes.h" 4 | #include "tiny/optional.h" 5 | 6 | #include 7 | #include 8 | 9 | 10 | namespace 11 | { 12 | struct ClassWithCustomManip 13 | { 14 | std::string someString = "someString"; 15 | int someInt = 42; 16 | 17 | // Because we use a dereference trick in the Natvis file, added to check that an 18 | // operator* does not confuse Natvis. 19 | int const & operator*() const 20 | { 21 | return someInt; 22 | } 23 | }; 24 | 25 | inline std::string const CLASS_SENTINEL = "SENTINEL"; 26 | } // namespace 27 | 28 | 29 | template <> 30 | struct tiny::optional_flag_manipulator 31 | { 32 | static bool is_empty(ClassWithCustomManip const & payload) noexcept 33 | { 34 | return payload.someString == CLASS_SENTINEL; 35 | } 36 | 37 | static void init_empty_flag(ClassWithCustomManip & uninitializedPayloadMemory) noexcept 38 | { 39 | ::new (&uninitializedPayloadMemory) ClassWithCustomManip{CLASS_SENTINEL}; 40 | } 41 | 42 | static void invalidate_empty_flag(ClassWithCustomManip & emptyPayload) noexcept 43 | { 44 | emptyPayload.~ClassWithCustomManip(); 45 | } 46 | }; 47 | 48 | 49 | namespace 50 | { 51 | enum class TestEnum : unsigned long long 52 | { 53 | VALUE1, 54 | VALUE2, 55 | INVALID = 0x7fff'ffff'ffff'ffffull 56 | }; 57 | } // namespace 58 | 59 | 60 | template <> 61 | struct tiny::optional_flag_manipulator : tiny::sentinel_flag_manipulator 62 | { 63 | }; 64 | 65 | 66 | namespace 67 | { 68 | struct AdditionalInplaceTestClass 69 | { 70 | TestEnum someEnum = TestEnum::VALUE1; 71 | void (*someFuncPtr)() = nullptr; 72 | }; 73 | 74 | 75 | void someFunc() { } 76 | 77 | struct ClassWithFunc 78 | { 79 | void memberFunc() { } 80 | }; 81 | } // namespace 82 | 83 | 84 | // We cannot really run automated tests for Visual Studio natvis files. The code here serves as convenient 85 | // place to stop the Visual Studio debugger and step through the lines, manually observing the displayed 86 | // values to check that natvis works. 87 | void test_Natvis() 88 | { 89 | //-------------- Supported by natvis -------------- 90 | 91 | // Not in-place (i.e. not compressed) 92 | { 93 | [[maybe_unused]] tiny::optional empty; 94 | [[maybe_unused]] tiny::optional nonEmpty = 42u; 95 | [[maybe_unused]] int dummyToPlaceBreakpoint = 0; 96 | } 97 | { 98 | [[maybe_unused]] tiny::optional empty; 99 | [[maybe_unused]] tiny::optional nonEmpty = TestClassForInplace{}; 100 | [[maybe_unused]] int dummyToPlaceBreakpoint = 0; 101 | } 102 | { 103 | typedef void (ClassWithFunc::*MemFuncType)(); 104 | [[maybe_unused]] tiny::optional empty; 105 | [[maybe_unused]] tiny::optional nonEmpty = &ClassWithFunc::memberFunc; 106 | [[maybe_unused]] int dummyToPlaceBreakpoint = 0; 107 | } 108 | { 109 | [[maybe_unused]] tiny::optional> empty; 110 | [[maybe_unused]] tiny::optional> nonEmpty = nullptr; 111 | [[maybe_unused]] int dummyToPlaceBreakpoint = 0; 112 | } 113 | { 114 | [[maybe_unused]] tiny::optional> empty; 115 | [[maybe_unused]] tiny::optional> nonEmpty = std::make_shared(42u); 116 | [[maybe_unused]] int dummyToPlaceBreakpoint = 0; 117 | } 118 | 119 | // Special built-in sentinels. 120 | { 121 | [[maybe_unused]] tiny::optional empty; 122 | [[maybe_unused]] tiny::optional nonEmpty = true; 123 | [[maybe_unused]] int dummyToPlaceBreakpoint = 0; 124 | } 125 | { 126 | [[maybe_unused]] tiny::optional empty; 127 | [[maybe_unused]] tiny::optional nonEmpty = 42.0; 128 | [[maybe_unused]] int dummyToPlaceBreakpoint = 0; 129 | } 130 | { 131 | [[maybe_unused]] tiny::optional empty; 132 | [[maybe_unused]] tiny::optional nonEmpty = 42.0f; 133 | [[maybe_unused]] int dummyToPlaceBreakpoint = 0; 134 | } 135 | { 136 | [[maybe_unused]] tiny::optional empty; 137 | [[maybe_unused]] tiny::optional nonEmpty = nullptr; 138 | [[maybe_unused]] int dummyToPlaceBreakpoint = 0; 139 | } 140 | { 141 | TestClass c; 142 | [[maybe_unused]] tiny::optional empty; 143 | [[maybe_unused]] tiny::optional nonEmpty = &c; 144 | [[maybe_unused]] int dummyToPlaceBreakpoint = 0; 145 | } 146 | { 147 | [[maybe_unused]] tiny::optional empty; 148 | [[maybe_unused]] tiny::optional nonEmpty = &someFunc; 149 | [[maybe_unused]] int dummyToPlaceBreakpoint = 0; 150 | } 151 | 152 | // In-place in member variable, built-in sentinels 153 | { 154 | [[maybe_unused]] tiny::optional empty; 155 | [[maybe_unused]] tiny::optional nonEmpty 156 | = TestClassForInplace{}; 157 | [[maybe_unused]] int dummyToPlaceBreakpoint = 0; 158 | } 159 | { 160 | [[maybe_unused]] tiny::optional empty; 161 | [[maybe_unused]] tiny::optional nonEmpty 162 | = TestClassForInplace{}; 163 | [[maybe_unused]] int dummyToPlaceBreakpoint = 0; 164 | } 165 | { 166 | [[maybe_unused]] tiny::optional empty; 167 | [[maybe_unused]] tiny::optional nonEmpty 168 | = TestClassForInplace{}; 169 | [[maybe_unused]] int dummyToPlaceBreakpoint = 0; 170 | } 171 | { 172 | [[maybe_unused]] tiny::optional empty; 173 | [[maybe_unused]] tiny::optional nonEmpty 174 | = TestClassForInplace{}; 175 | [[maybe_unused]] int dummyToPlaceBreakpoint = 0; 176 | } 177 | { 178 | [[maybe_unused]] tiny::optional empty; 179 | [[maybe_unused]] tiny::optional nonEmpty 180 | = AdditionalInplaceTestClass{}; 181 | [[maybe_unused]] int dummyToPlaceBreakpoint = 0; 182 | } 183 | 184 | // In-place with custom sentinel 185 | { 186 | [[maybe_unused]] tiny::optional empty; 187 | [[maybe_unused]] tiny::optional nonEmpty = 42u; 188 | [[maybe_unused]] int dummyToPlaceBreakpoint = 0; 189 | } 190 | 191 | // In-place in member variable, custom sentinel 192 | { 193 | [[maybe_unused]] tiny::optional empty; 194 | [[maybe_unused]] tiny::optional nonEmpty 195 | = TestClassForInplace{}; 196 | [[maybe_unused]] int dummyToPlaceBreakpoint = 0; 197 | } 198 | 199 | // optional_sentinel_via_type 200 | { 201 | [[maybe_unused]] tiny::optional_sentinel_via_type> empty; 202 | [[maybe_unused]] tiny::optional_sentinel_via_type> nonEmpty = 42u; 203 | [[maybe_unused]] int dummyToPlaceBreakpoint = 0; 204 | } 205 | 206 | // optional_aip 207 | { 208 | [[maybe_unused]] tiny::optional_aip empty; 209 | [[maybe_unused]] tiny::optional_aip nonEmpty = 42u; 210 | [[maybe_unused]] int dummyToPlaceBreakpoint = 0; 211 | } 212 | 213 | 214 | //-------------- Not supported by our generic natvis file -------------- 215 | 216 | // These have specializations of tiny::optional_flag_manipulator. 217 | // Because Natvis does not allow to call functions, we cannot support them with a generic Natvis. 218 | { 219 | [[maybe_unused]] tiny::optional empty; 220 | [[maybe_unused]] tiny::optional nonEmpty = ClassWithCustomManip{}; 221 | [[maybe_unused]] int dummyToPlaceBreakpoint = 0; 222 | } 223 | { 224 | [[maybe_unused]] tiny::optional empty; 225 | [[maybe_unused]] tiny::optional nonEmpty = TestEnum::VALUE2; 226 | [[maybe_unused]] int dummyToPlaceBreakpoint = 0; 227 | } 228 | { 229 | [[maybe_unused]] tiny::optional empty; 230 | [[maybe_unused]] tiny::optional nonEmpty 231 | = AdditionalInplaceTestClass{}; 232 | [[maybe_unused]] int dummyToPlaceBreakpoint = 0; 233 | } 234 | 235 | // optional_inplace: Always uses a custom FlagManipulator, so also no chance to write a 236 | // generic Natvis visualizer. 237 | { 238 | [[maybe_unused]] tiny::optional_inplace> 239 | empty; 240 | [[maybe_unused]] tiny::optional_inplace> 241 | nonEmpty 242 | = ClassWithCustomManip{}; 243 | [[maybe_unused]] int dummyToPlaceBreakpoint = 0; 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /performance/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #if defined(__clang__) 13 | #define TINY_OPTIONAL_CLANG_BUILD 14 | #elif defined(__GNUC__) || defined(__GNUG__) 15 | #define TINY_OPTIONAL_GCC_BUILD 16 | #elif defined(_MSC_VER) 17 | #define TINY_OPTIONAL_MSVC_BUILD 18 | #else 19 | #error Unknown compiler 20 | #endif 21 | 22 | 23 | #if defined(TINY_OPTIONAL_CLANG_BUILD) || defined(TINY_OPTIONAL_GCC_BUILD) 24 | #define TINY_OPTIONAL_NO_INLINE __attribute__((noinline)) 25 | #elif defined(TINY_OPTIONAL_MSVC_BUILD) 26 | #define TINY_OPTIONAL_NO_INLINE __declspec(noinline) 27 | #else 28 | #error Unknown compiler 29 | #endif 30 | 31 | 32 | template 33 | constexpr T Sqr(T v) 34 | { 35 | return v*v; 36 | } 37 | 38 | 39 | template 40 | class Tester 41 | { 42 | public: 43 | explicit Tester(size_t modulo, size_t numValues, size_t numIterations) 44 | : modulo(modulo) 45 | , numIterations(numIterations) 46 | { 47 | size_t const totalSize = numValues * sizeof(WeirdVector); 48 | std::cout << "\tCreating vector with " << numValues << " elements (" << sizeof(WeirdVector) 49 | << " bytes per element, total " << totalSize << "B=" << totalSize / 1024 50 | << "kB=" << totalSize / (1024 * 1024) << "MB)" << std::endl; 51 | auto const start = std::chrono::steady_clock::now(); 52 | 53 | std::mt19937 rng(42); 54 | values.reserve(numValues); 55 | size_t numNullopts = 0; 56 | for (size_t i = 0; i < numValues; ++i) { 57 | auto const x = GenerateRandomOptional(rng); 58 | auto const y = GenerateRandomOptional(rng); 59 | auto const z = GenerateRandomOptional(rng); 60 | values.emplace_back(WeirdVector{x, y, z, std::nullopt}); 61 | 62 | numNullopts += !x; 63 | numNullopts += !y; 64 | numNullopts += !z; 65 | } 66 | 67 | size_t const numOptionals = 3 * numValues; 68 | nulloptRatio = static_cast(numNullopts) / numOptionals; 69 | 70 | double const duration = std::chrono::duration(std::chrono::steady_clock::now() - start).count(); 71 | std::cout << "\tCreated data in " << duration << "s, " << nulloptRatio * 100.0 << "% are nullopt" << std::endl; 72 | } 73 | 74 | double TestAndGetDuration() 75 | { 76 | auto const start = std::chrono::steady_clock::now(); 77 | 78 | typename Optional::value_type totalLength = 0; 79 | for (size_t i = 0; i < numIterations; ++i) { 80 | totalLength += RunRawTest(); 81 | } 82 | 83 | double const duration = std::chrono::duration(std::chrono::steady_clock::now() - start).count(); 84 | std::cout << "\tTesting finished. Took " << duration << "s. TotalLength = " << totalLength << std::endl; 85 | 86 | return duration; 87 | } 88 | 89 | double GetNulloptRatio() const 90 | { 91 | return nulloptRatio; 92 | } 93 | 94 | private: 95 | struct WeirdVector 96 | { 97 | Optional x, y, z; 98 | Optional length2; 99 | }; 100 | 101 | Optional GenerateRandomOptional(std::mt19937 & rng) 102 | { 103 | auto const value = rng(); 104 | if (value % modulo == 0) { 105 | return std::nullopt; 106 | } 107 | else { 108 | return value; 109 | } 110 | } 111 | 112 | TINY_OPTIONAL_NO_INLINE auto RunRawTest() 113 | { 114 | for (WeirdVector & vec : values) { 115 | if (vec.x || vec.y || vec.z) { 116 | vec.length2.emplace(Sqr(vec.x.value_or(0)) + Sqr(vec.y.value_or(0)) + Sqr(vec.z.value_or(0))); 117 | } 118 | } 119 | 120 | typename Optional::value_type totalLength = 0; 121 | for (WeirdVector & vec : values) { 122 | totalLength += vec.length2.value_or(0); 123 | } 124 | return totalLength; 125 | } 126 | 127 | size_t const modulo; 128 | size_t const numIterations; 129 | std::vector values; 130 | double nulloptRatio; 131 | }; 132 | 133 | 134 | struct Result 135 | { 136 | size_t modulo = 0; 137 | size_t numValues = 0; 138 | double nulloptRatio = std::numeric_limits::quiet_NaN(); 139 | double durationTiny = std::numeric_limits::quiet_NaN(); 140 | double durationStd = std::numeric_limits::quiet_NaN(); 141 | }; 142 | 143 | 144 | Result RunOneTest(size_t modulo, size_t numValues, size_t numIterations) 145 | { 146 | Result result; 147 | result.modulo = modulo; 148 | result.numValues = numValues; 149 | 150 | { 151 | std::cout << "tiny::optional:" << std::endl; 152 | Tester> tinyTest(modulo, numValues, numIterations); 153 | result.nulloptRatio = tinyTest.GetNulloptRatio(); 154 | result.durationTiny = tinyTest.TestAndGetDuration(); 155 | } 156 | { 157 | std::cout << "std::optional:" << std::endl; 158 | Tester> stdTest(modulo, numValues, numIterations); 159 | result.durationStd = stdTest.TestAndGetDuration(); 160 | } 161 | 162 | return result; 163 | } 164 | 165 | 166 | std::vector RunMultipleTests( 167 | std::vector modulos, 168 | std::vector> numValues) 169 | { 170 | static constexpr size_t cAdditionalIterMultiplier = 2; 171 | 172 | std::vector results; 173 | results.reserve(modulos.size()); 174 | for (size_t const modulo : modulos) { 175 | for (auto const & [numVals, numIterationsBase] : numValues) { 176 | size_t const numIterations = numIterationsBase * cAdditionalIterMultiplier; 177 | std::cout << "====== modulo=" << modulo << ", numVals=" << numVals << ", numIter=" << numIterations 178 | << " ======" << std::endl; 179 | results.emplace_back(RunOneTest(modulo, numVals, numIterations)); 180 | } 181 | } 182 | return results; 183 | } 184 | 185 | 186 | std::string CreatePrintableResultString(std::vector const & results) 187 | { 188 | std::stringstream o; 189 | o << std::setw(6) << "Modulo" 190 | << " " << std::setw(10) << "numVals" 191 | << " " << std::setw(9) << "Nullopts" 192 | << " " << std::setw(8) << "std[s]" 193 | << " " << std::setw(8) << "tiny[s]" 194 | << " " << std::setw(8) << "std/tiny" << std::endl; 195 | for (Result const & result : results) { 196 | o << std::setw(6) << result.modulo << " " << std::setw(10) << result.numValues << " " << std::setprecision(3) 197 | << std::setw(9) << result.nulloptRatio << " " << std::setprecision(5) << std::setw(8) << result.durationStd 198 | << " " << std::setw(8) << result.durationTiny << " " << std::setprecision(4) << std::setw(8) 199 | << result.durationStd / result.durationTiny << std::endl; 200 | } 201 | return o.str(); 202 | } 203 | 204 | 205 | std::string GetCompilerName() 206 | { 207 | #if defined(TINY_OPTIONAL_CLANG_BUILD) 208 | return "clang"; 209 | #elif defined(TINY_OPTIONAL_GCC_BUILD) 210 | return "gcc"; 211 | #elif defined(TINY_OPTIONAL_MSVC_BUILD) 212 | return "msvc"; 213 | #else 214 | #error Unknown compiler 215 | #endif 216 | } 217 | 218 | 219 | int main() 220 | { 221 | std::string const compilerName = GetCompilerName(); 222 | std::cout << "Running tests on compiler: " << compilerName << std::endl; 223 | 224 | // clang-format off 225 | std::vector results = RunMultipleTests( 226 | {8192}, // Most optionals have a value ==> branch prediction is correct in most cases 227 | { 228 | {1, 3000000000}, // tiny and std fit into cache line 229 | {2, 1000000000}, // 2 elements: All tiny fit into cache line, but not std 230 | 231 | {3, 900000000}, 232 | {4, 600000000}, 233 | {8, 400000000}, 234 | {32, 70000000}, 235 | {128, 20000000}, 236 | {512, 4000000}, 237 | {1024, 2000000}, 238 | {2048, 1000000}, 239 | {4096, 600000}, // All tiny and std fit into L1 240 | {8192, 300000}, // All tiny just fit into L1 but not std 241 | 242 | {16384, 200000}, // All tiny and std fit into L2 243 | {32768, 100000}, // All tiny fit just into L2 but not std 244 | 245 | {65536, 40000}, // Neither all tiny nor std fit into L2 246 | {80000, 35000}, 247 | {98304, 30000}, 248 | {110000, 25000}, 249 | {131072, 20000}, // All tiny and std just fit into L3 250 | 251 | {196608, 15000}, // All tiny fit into L3 but not std 252 | {262144, 10000}, // All tiny fit just into L3 but not std 253 | 254 | {393216, 7500}, // Neither all tiny nor std fit into L3 255 | {524288, 5000}, 256 | {786432, 2500}, 257 | {1048576, 1000}, // Neither all tiny nor std fit into L3 258 | {2097152, 500}, // Neither all tiny nor std fit into L3 259 | {5000000, 250}, 260 | {10000000, 100} 261 | }); 262 | // clang-format on 263 | 264 | std::cout << std::endl; 265 | std::cout << "========= Summary for " << compilerName << " =========" << std::endl; 266 | std::string const resultsStr = CreatePrintableResultString(results); 267 | std::cout << resultsStr; 268 | 269 | std::ofstream out("results_" + GetCompilerName() + ".dat"); 270 | out << resultsStr; 271 | } 272 | 273 | 274 | -------------------------------------------------------------------------------- /tests/ExerciseTinyOptionalPayload1.cpp: -------------------------------------------------------------------------------- 1 | #include "ExerciseTinyOptionalPayload.h" 2 | #include "Exercises.h" 3 | #include "TestTypes.h" 4 | #include "TestUtilities.h" 5 | #include "tiny/optional.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | void test_TinyOptionalPayload_Bool() 13 | { 14 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, false, true); 15 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, true, false); 16 | EXERCISE_OPTIONAL_WITH_CONSTRUCTOR_ARGS((tiny::optional{}), EXPECT_INPLACE, false, false, false); 17 | EXERCISE_OPTIONAL_WITH_CONSTRUCTOR_ARGS((tiny::optional{}), EXPECT_INPLACE, true, true, true); 18 | EXERCISE_OPTIONAL((tiny::optional{false}), cInPlaceExpectationForUnusedBits, false, true); // Uses deduction guide 19 | } 20 | 21 | 22 | void test_TinyOptionalPayload_FloatingPoint() 23 | { 24 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, 43.0, 44.0); 25 | EXERCISE_OPTIONAL( 26 | (tiny::optional{}), 27 | cInPlaceExpectationForUnusedBits, 28 | std::numeric_limits::quiet_NaN(), 29 | 44.0); 30 | EXERCISE_OPTIONAL( 31 | (tiny::optional{}), 32 | cInPlaceExpectationForUnusedBits, 33 | std::numeric_limits::signaling_NaN(), 34 | 44.0); 35 | EXERCISE_OPTIONAL((tiny::optional{100.0}), cInPlaceExpectationForUnusedBits, 43.0, 44.0); // Uses deduction guide 36 | 37 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, 43.0f, 44.0f); 38 | EXERCISE_OPTIONAL( 39 | (tiny::optional{}), 40 | cInPlaceExpectationForUnusedBits, 41 | std::numeric_limits::quiet_NaN(), 42 | 44.0f); 43 | #ifndef __FAST_MATH__ // NAN is broken with -ffast-math; clang issues -Wnan-infinity-disabled 44 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, NAN, 44.0f); 45 | #endif 46 | EXERCISE_OPTIONAL( 47 | (tiny::optional{}), 48 | cInPlaceExpectationForUnusedBits, 49 | std::numeric_limits::signaling_NaN(), 50 | 44.0f); 51 | EXERCISE_OPTIONAL((tiny::optional{100.0f}), cInPlaceExpectationForUnusedBits, 43.0f, 44.0f); // Uses deduction guide 52 | 53 | EXERCISE_OPTIONAL((tiny::optional{}), EXPECT_SEPARATE, 43.0L, 44.0L); 54 | } 55 | 56 | 57 | void test_TinyOptionalPayload_IntegersAndEnums() 58 | { 59 | EXERCISE_OPTIONAL((tiny::optional{}), EXPECT_SEPARATE, 42, 43); 60 | EXERCISE_OPTIONAL((tiny::optional{100}), EXPECT_SEPARATE, 42, 43); // Uses deduction guide 61 | 62 | EXERCISE_OPTIONAL((tiny::optional{}), EXPECT_INPLACE, 43, 44); 63 | EXERCISE_OPTIONAL((tiny::optional::max)()>{}), EXPECT_INPLACE, 43u, 44u); 64 | 65 | EXERCISE_OPTIONAL((tiny::optional::max)()>{}), EXPECT_INPLACE, (char)43, (char)44); 66 | EXERCISE_OPTIONAL((tiny::optional{}), EXPECT_INPLACE, 'a', 'b'); 67 | EXERCISE_OPTIONAL((tiny::optional{'c'}), EXPECT_SEPARATE, 'a', 'b'); // Uses deduction guide 68 | 69 | EXERCISE_OPTIONAL((tiny::optional{}), EXPECT_INPLACE, std::byte{42}, std::byte{43}); 70 | 71 | EXERCISE_OPTIONAL((tiny::optional{}), EXPECT_INPLACE, ScopedEnum::v2, ScopedEnum::v1); 72 | EXERCISE_OPTIONAL((tiny::optional{}), EXPECT_INPLACE, UE_v2, UE_v1); 73 | } 74 | 75 | 76 | void test_TinyOptionalPayload_IsEmptyFlagInMember() 77 | { 78 | // Exploiting a member to place the IsEmpty flag 79 | 80 | EXERCISE_OPTIONAL_WITH_CONSTRUCTOR_ARGS( 81 | (tiny::optional{}), 82 | cInPlaceExpectationForMemPtr, 83 | TestClassForInplace{}, 84 | TestClassForInplace(43, 44.0, 45, nullptr), 85 | 2, 86 | 5.0, 87 | 43u, 88 | nullptr); 89 | 90 | EXERCISE_OPTIONAL( 91 | (tiny::optional{}), 92 | cInPlaceExpectationForMemPtr, 93 | TestClassForInplace{}, 94 | TestClassForInplace(43, 44.0, 45, nullptr)); 95 | 96 | EXERCISE_OPTIONAL( 97 | (tiny::optional{}), 98 | cInPlaceExpectationForMemPtr, 99 | TestClassForInplace{}, 100 | TestClassForInplace(43, 44.0, 45, nullptr)); 101 | } 102 | 103 | 104 | void test_TinyOptionalPayload_Pointers() 105 | { 106 | #if defined(TINY_OPTIONAL_x64) && !defined(TINY_OPTIONAL_USE_SEPARATE_BOOL_INSTEAD_OF_UNUSED_BITS) 107 | // The 64-bit pointer sentinel is very deliberately chosen so that it can never be valid. 108 | // Check this. See SentinelForExploitingUnusedBits for more information. 109 | ASSERT_TRUE(IsAddressNonCanonical(tiny::impl::SentinelForExploitingUnusedBits::value)); 110 | #endif 111 | 112 | // Ordinary pointers 113 | { 114 | TestClass c1, c2; 115 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, &c1, &c2); 116 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, nullptr, &c1); 117 | EXERCISE_OPTIONAL((tiny::optional{&c1}), cInPlaceExpectationForUnusedBits, &c1, &c2); // Uses deduction guide 118 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, nullptr, &c1); 119 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, nullptr, &c1); 120 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, nullptr, &c1); 121 | } 122 | 123 | { 124 | TestClass c1, c2; 125 | EXERCISE_OPTIONAL_WITH_CONSTRUCTOR_ARGS((tiny::optional{}), EXPECT_INPLACE, &c1, &c2, &c1); 126 | // The following should trigger the assert that reset() should be used instead. 127 | // ExerciseOptional(o, nullptr); 128 | } 129 | 130 | // Also test void*, just for the sake of it. 131 | { 132 | int i = 0; 133 | int j = 42; 134 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, static_cast(&i), &j); 135 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, nullptr, static_cast(&i)); 136 | } 137 | 138 | // Function pointers 139 | { 140 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, &TestFunc1, &TestFunc2); 141 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, nullptr, &TestFunc2); 142 | } 143 | 144 | // Member pointers 145 | { 146 | // MSVC has a bug here: https://stackoverflow.com/q/76182002/3740047. The expression 'new MemberPointer()' (which 147 | // appears in the form of a placement new internally) is not zero initialized if the list of arguments to the '()' 148 | // is empty. 149 | #ifndef TINY_OPTIONAL_MSVC_BUILD 150 | EXERCISE_OPTIONAL((tiny::optional{}), EXPECT_SEPARATE, &TestClass::someValue, nullptr); 151 | #endif 152 | 153 | // Version where we pass nullptr explicitly to the constructors instead of an empty argument list, to work around 154 | // the above mentioned compiler bug. 155 | EXERCISE_OPTIONAL_WITH_CONSTRUCTOR_ARGS( 156 | (tiny::optional{}), 157 | EXPECT_SEPARATE, 158 | &TestClass::someValue, 159 | nullptr, 160 | nullptr); 161 | } 162 | 163 | // Member function pointers 164 | { 165 | EXERCISE_OPTIONAL( 166 | (tiny::optional{}), 167 | EXPECT_SEPARATE, 168 | &TestClass::IsValid, 169 | nullptr); 170 | } 171 | } 172 | 173 | 174 | void test_TinyOptionalPayload_StdTypes() 175 | { 176 | // Tests of various std types 177 | 178 | { 179 | std::vector testValue1{1, 2, 3, 4}; 180 | std::vector testValue2{5, 6, 7}; 181 | EXERCISE_OPTIONAL((tiny::optional>{}), EXPECT_SEPARATE, testValue1, testValue2); 182 | EXERCISE_OPTIONAL((tiny::optional{testValue1}), EXPECT_SEPARATE, testValue1, testValue2); 183 | } 184 | 185 | { 186 | TestClass c1, c2; 187 | EXERCISE_OPTIONAL_WITH_CONSTRUCTOR_ARGS( 188 | (tiny::optional>{}), 189 | EXPECT_SEPARATE, 190 | std::ref(c1), 191 | std::ref(c2), 192 | std::ref(c1)); 193 | EXERCISE_OPTIONAL_WITH_CONSTRUCTOR_ARGS( 194 | (tiny::optional>{}), 195 | EXPECT_SEPARATE, 196 | std::cref(c1), 197 | std::cref(c2), 198 | std::cref(c1)); 199 | } 200 | 201 | // Test for the std::hash specialization 202 | { 203 | static_assert(!IsDisabledHash>); 204 | std::unordered_set> const set = {42.0, std::nullopt, 43.0}; 205 | ASSERT_TRUE(set.count(42.0) > 0); 206 | ASSERT_TRUE(set.count(std::nullopt) > 0); 207 | ASSERT_TRUE(set.count(43.0) > 0); 208 | } 209 | { 210 | static_assert(!IsDisabledHash>); 211 | std::unordered_set> const set = {42, std::nullopt}; 212 | ASSERT_TRUE(set.count(42) > 0); 213 | ASSERT_TRUE(set.count(std::nullopt) > 0); 214 | } 215 | { 216 | static_assert(!IsDisabledHash>); 217 | std::unordered_set> const set 218 | = {TestClassForInplace(43, 44.0, 45, nullptr), std::nullopt}; 219 | ASSERT_TRUE(set.count(TestClassForInplace(43, 44.0, 45, nullptr)) > 0); 220 | ASSERT_TRUE(set.count(std::nullopt) > 0); 221 | } 222 | { 223 | // We did not specialize std::hash for TestClassWithInitializerList, so neither should tiny optional. 224 | static_assert(IsDisabledHash); 225 | static_assert(IsDisabledHash>); 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /tests/ComparisonTests.cpp: -------------------------------------------------------------------------------- 1 | #include "ComparisonTests.h" 2 | 3 | #include "TestTypes.h" 4 | #include "TestUtilities.h" 5 | #include "tiny/optional.h" 6 | 7 | 8 | namespace 9 | { 10 | 11 | #ifdef __cpp_lib_three_way_comparison 12 | // Basically the same as std::compare_three_way, but without the annoying std::three_way_comparable_with constraint, 13 | // which forbids e.g. `std::compare_three_way(std::optional{}, tiny::optional{})`, although the 14 | // expression `std::optional{} <=> tiny::optional{}` is valid and compiles. 15 | // See https://stackoverflow.com/a/66938093/3740047 for more details. 16 | struct my_compare_three_way 17 | { 18 | template 19 | constexpr auto operator()(T const & lhs, U const & rhs) const 20 | { 21 | return lhs <=> rhs; 22 | } 23 | }; 24 | #endif 25 | 26 | 27 | template 28 | void TestCompareOptWithOpt(Val1 val1, Val2 val2, Comparer comparer) 29 | { 30 | std::cout << "\tComparison '" << typeid(Comparer).name() << "':\n\t\tOpt1='" << typeid(Opt1).name() << "' and Opt2='" 31 | << typeid(Opt2).name() << "'\n\t\tval1='" << val1 << "' and val2='" << val2 << "'" << std::endl; 32 | 33 | Opt1 const opt1{val1}; 34 | Opt2 const opt2{val2}; 35 | std::optional const std1{val1}; 36 | std::optional const std2{val2}; 37 | 38 | // Work around some miscompilation bug seen in clang 20.1.4 and 21.1.3 on Windows with 39 | // the flags "-std=c++20 -O3 -DNDEBUG -ffast-math" for the call 40 | // (TestCompareOptWithOpt, std::optional>(42, NaN, comparer), ...) 41 | // The compilation succeeds, but the ASSERTs fail. 42 | // I guess it has something to do with the use of a NaN and -ffast-math; this is notoriously broken. 43 | // Adding the following dummy line works around the issue for some reason. 44 | #if defined(TINY_OPTIONAL_CLANG_BUILD) && (__clang_major__ == 20 || __clang_major__ == 21) && defined(__FAST_MATH__) && defined(NDEBUG) 45 | [[maybe_unused]] auto volatile const dummy = (comparer(opt1, opt2) < 0 ? "<" : comparer(opt1, opt2) > 0 ? ">" : "=="); 46 | #endif 47 | 48 | ASSERT_TRUE(comparer(opt1, opt2) == comparer(std1, std2)); 49 | ASSERT_TRUE(comparer(opt2, opt1) == comparer(std2, std1)); 50 | } 51 | 52 | 53 | template 54 | void TestCompareOptWithValue(Val1 val1, Val2 val2, Comparer comparer) 55 | { 56 | std::cout << "\tComparison '" << typeid(Comparer).name() << "':\n\t\tOpt1='" << typeid(Opt1).name() << "'\n\t\tval1='" 57 | << val1 << "' and val2='" << val2 << "'" << std::endl; 58 | 59 | Opt1 const opt1{val1}; 60 | std::optional const std1{val1}; 61 | 62 | ASSERT_TRUE(comparer(opt1, val2) == comparer(std1, val2)); 63 | ASSERT_TRUE(comparer(val2, opt1) == comparer(val2, std1)); 64 | } 65 | } // namespace 66 | 67 | 68 | // Some comparison tests involving NaNs fail with -ffast-math, C++20, gcc <=10 and optimizations enabled. fast-math is 69 | // quite problematic for reproducible results, so there does not seem to be a defect in the tiny::optional library, and 70 | // certainly not with the sentinel used within tiny::optional. Instead, the optimizer is giving inconsistent results. 71 | // Thus, simply disable the tests for these cases. 72 | #if !defined(TINY_OPTIONAL_GCC_BUILD) || __GNUC__ >= 11 || (!defined(__FAST_MATH__) && !defined(TINY_OPTIONAL_CPP20)) 73 | #define TINY_OPTIONAL_ENABLE_NAN_COMPARISON_TESTS 74 | #endif 75 | 76 | 77 | void test_Comparisons() 78 | { 79 | auto const runAllComparisons = [](auto &&... comparer) { 80 | // Basic comparisons. 81 | (TestCompareOptWithOpt, tiny::optional>(42, 42, comparer), ...); 82 | (TestCompareOptWithOpt, tiny::optional>(42, 999, comparer), ...); 83 | (TestCompareOptWithOpt, tiny::optional>(42, 1, comparer), ...); 84 | 85 | // One optional has a sentinel value. 86 | (TestCompareOptWithOpt, tiny::optional>(42, 42, comparer), ...); 87 | (TestCompareOptWithOpt, tiny::optional>(42, 999, comparer), ...); 88 | 89 | // The 1st contains '1' which is the sentinel of the 2nd. 90 | (TestCompareOptWithOpt, tiny::optional>(1, 42, comparer), ...); 91 | 92 | // Both optionals are empty 93 | (TestCompareOptWithOpt, tiny::optional>(std::nullopt, std::nullopt, comparer), ...); 94 | (TestCompareOptWithOpt, tiny::optional>(std::nullopt, std::nullopt, comparer), ...); 95 | 96 | // One optional is empty 97 | (TestCompareOptWithOpt, tiny::optional>(42, std::nullopt, comparer), ...); 98 | (TestCompareOptWithOpt, tiny::optional>(1, std::nullopt, comparer), ...); 99 | 100 | // Comparison between distinct payload types. 101 | (TestCompareOptWithOpt, tiny::optional>(42, 42.0, comparer), ...); 102 | (TestCompareOptWithOpt, tiny::optional>(42, 999.0, comparer), ...); 103 | (TestCompareOptWithOpt, tiny::optional>(42.0f, 999.0, comparer), ...); 104 | 105 | // Comparison between tiny::optional and std::optional 106 | (TestCompareOptWithOpt, std::optional>(42, 42, comparer), ...); 107 | (TestCompareOptWithOpt, std::optional>(42, 999, comparer), ...); 108 | (TestCompareOptWithOpt, std::optional>(42, 1, comparer), ...); 109 | (TestCompareOptWithOpt, std::optional>(std::nullopt, std::nullopt, comparer), ...); 110 | (TestCompareOptWithOpt, std::optional>(42, std::nullopt, comparer), ...); 111 | (TestCompareOptWithOpt, std::optional>(std::nullopt, 42, comparer), ...); 112 | 113 | // Comparison between tiny/std::optional and tiny::optional_sentinel_via_type. 114 | // clang-format off 115 | (TestCompareOptWithOpt, tiny::optional_sentinel_via_type>>(42, 43, comparer), ...); 116 | (TestCompareOptWithOpt, tiny::optional_sentinel_via_type>>(42, 43, comparer), ...); 117 | // clang-format on 118 | 119 | // Comparison between tiny/std::optional and tiny::optional_aip. 120 | (TestCompareOptWithOpt, tiny::optional_aip>(42, 43, comparer), ...); 121 | (TestCompareOptWithOpt, tiny::optional_aip>(42, 43, comparer), ...); 122 | 123 | // Comparisons with std::nullopt 124 | (TestCompareOptWithValue>(42, std::nullopt, comparer), ...); 125 | (TestCompareOptWithValue>(std::nullopt, std::nullopt, comparer), ...); 126 | 127 | // Comparisons with a value of the same type as the payload 128 | (TestCompareOptWithValue>(42, 42, comparer), ...); 129 | (TestCompareOptWithValue>(42, 999, comparer), ...); 130 | (TestCompareOptWithValue>(std::nullopt, 42, comparer), ...); 131 | 132 | // Comparisons with a value of a different type than the payload 133 | (TestCompareOptWithValue>(42, 42.0, comparer), ...); 134 | (TestCompareOptWithValue>(42, 999.0, comparer), ...); 135 | 136 | // Comparisons involving partially ordered values. 137 | #ifdef TINY_OPTIONAL_ENABLE_NAN_COMPARISON_TESTS 138 | static constexpr double NaN = std::numeric_limits::quiet_NaN(); 139 | (TestCompareOptWithOpt, tiny::optional>(42, NaN, comparer), ...); 140 | (TestCompareOptWithOpt, tiny::optional>(NaN, NaN, comparer), ...); 141 | (TestCompareOptWithOpt, tiny::optional>(std::nullopt, NaN, comparer), ...); 142 | (TestCompareOptWithValue>(42, NaN, comparer), ...); 143 | (TestCompareOptWithValue>(NaN, NaN, comparer), ...); 144 | (TestCompareOptWithValue>(std::nullopt, NaN, comparer), ...); 145 | #endif 146 | 147 | // Comparisons for optional_sentinel_via_type. 148 | using OptionalIntViaType = tiny::optional_sentinel_via_type>; 149 | (TestCompareOptWithOpt(42, 43, comparer), ...); 150 | (TestCompareOptWithOpt>( 151 | 42, 152 | 43, 153 | comparer), 154 | ...); 155 | (TestCompareOptWithValue(42, 999, comparer), ...); 156 | (TestCompareOptWithValue(42, std::nullopt, comparer), ...); 157 | 158 | // Rough cross check that the tests also work with std::optional 159 | (TestCompareOptWithOpt, std::optional>(42, 42, comparer), ...); 160 | (TestCompareOptWithOpt, std::optional>(42, 999.0, comparer), ...); 161 | (TestCompareOptWithOpt, std::optional>(42, std::nullopt, comparer), ...); 162 | (TestCompareOptWithValue>(42, 999, comparer), ...); 163 | (TestCompareOptWithValue>(std::nullopt, 42, comparer), ...); 164 | (TestCompareOptWithValue>(42, std::nullopt, comparer), ...); 165 | 166 | #ifdef TINY_OPTIONAL_ENABLE_NAN_COMPARISON_TESTS 167 | (TestCompareOptWithOpt, std::optional>(42, NaN, comparer), ...); 168 | #endif 169 | }; 170 | 171 | 172 | // clang-format off 173 | runAllComparisons( 174 | std::equal_to<>(), 175 | std::not_equal_to<>(), 176 | std::less<>(), 177 | std::less_equal<>(), 178 | std::greater<>(), 179 | std::greater_equal<>() 180 | #ifdef __cpp_lib_three_way_comparison 181 | , my_compare_three_way() 182 | #endif 183 | ); 184 | // clang-format on 185 | } 186 | -------------------------------------------------------------------------------- /tests/TestUtilities.cpp: -------------------------------------------------------------------------------- 1 | #include "TestUtilities.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #ifdef TINY_OPTIONAL_WINDOWS_BUILD 11 | #include 12 | #endif 13 | 14 | #ifdef TINY_OPTIONAL_X86_OR_X64 15 | #ifdef TINY_OPTIONAL_MSVC_BUILD 16 | #include 17 | #elif defined(TINY_OPTIONAL_CLANG_BUILD) || defined(TINY_OPTIONAL_GCC_BUILD) 18 | #include 19 | #else 20 | #error Unsupported compiler for cpuid 21 | #endif 22 | #endif 23 | 24 | 25 | #ifdef TINY_OPTIONAL_WINDOWS_BUILD 26 | 27 | namespace 28 | { 29 | class CloseHandleScope 30 | { 31 | public: 32 | explicit CloseHandleScope(HANDLE & handle) 33 | : mHandle(handle) 34 | { 35 | } 36 | 37 | ~CloseHandleScope() 38 | { 39 | Close(); 40 | } 41 | 42 | void Close() 43 | { 44 | if (mHandle != NULL) { 45 | CloseHandle(mHandle); 46 | mHandle = NULL; 47 | } 48 | } 49 | 50 | private: 51 | HANDLE & mHandle; 52 | }; 53 | } // namespace 54 | 55 | 56 | ExecutionResult ExecuteProgramSync(std::string const & commandline) 57 | { 58 | // Based on: 59 | // https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output 60 | // https://gitlab.com/eidheim/tiny-process-library/-/blob/master/process_win.cpp 61 | 62 | //----------------------------- 63 | // Run the program. 64 | 65 | SECURITY_ATTRIBUTES saAttr; 66 | saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 67 | saAttr.bInheritHandle = TRUE; 68 | saAttr.lpSecurityDescriptor = NULL; 69 | 70 | HANDLE hReadFromChild = NULL; 71 | HANDLE hWriteToChild = NULL; 72 | CloseHandleScope readHandleScope(hReadFromChild); 73 | CloseHandleScope writeHandleScope(hWriteToChild); 74 | 75 | if (!CreatePipe(&hReadFromChild, &hWriteToChild, &saAttr, 0)) { 76 | throw std::runtime_error( 77 | "Failed to open pipe to communicate with child process. Last error code: " + std::to_string(::GetLastError())); 78 | } 79 | if (!SetHandleInformation(hReadFromChild, HANDLE_FLAG_INHERIT, 0)) { 80 | throw std::runtime_error("Failed to set HANDLE_FLAG_INHERIT. Last error code: " + std::to_string(::GetLastError())); 81 | } 82 | 83 | PROCESS_INFORMATION piProcInfo; 84 | STARTUPINFOA siStartInfo; 85 | ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); 86 | ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); 87 | 88 | CloseHandleScope threadScope(piProcInfo.hThread); 89 | CloseHandleScope processScope(piProcInfo.hProcess); 90 | 91 | siStartInfo.cb = sizeof(STARTUPINFO); 92 | siStartInfo.hStdError = hWriteToChild; 93 | siStartInfo.hStdOutput = hWriteToChild; 94 | siStartInfo.dwFlags |= STARTF_USESTDHANDLES; 95 | 96 | std::string commandlineCopy = commandline; 97 | BOOL const bSuccess = CreateProcessA( 98 | NULL, 99 | commandlineCopy.data(), // command line 100 | NULL, // process security attributes 101 | NULL, // primary thread security attributes 102 | TRUE, // handles are inherited 103 | 0, // creation flags 104 | NULL, // use parent's environment 105 | NULL, // use parent's current directory 106 | &siStartInfo, // STARTUPINFO pointer 107 | &piProcInfo); // receives PROCESS_INFORMATION 108 | 109 | if (!bSuccess) { 110 | throw std::runtime_error("CreateProcess failed with error code " + std::to_string(::GetLastError())); 111 | } 112 | 113 | threadScope.Close(); 114 | writeHandleScope.Close(); 115 | 116 | 117 | //----------------------------- 118 | // Read stdout and stdderr until the process terminates. 119 | std::string receivedOutput; 120 | 121 | { 122 | static constexpr DWORD BUFFER_SIZE = 1024; 123 | CHAR chBuf[BUFFER_SIZE] = {0}; 124 | 125 | while (true) { 126 | DWORD dwRead = 0; 127 | BOOL const bReadSuccess = ReadFile(hReadFromChild, chBuf, BUFFER_SIZE, &dwRead, NULL); 128 | if (!bReadSuccess || dwRead == 0) { 129 | break; 130 | } 131 | 132 | receivedOutput += std::string(chBuf, dwRead); 133 | } 134 | } 135 | 136 | //----------------------------- 137 | // Check exit code of the process. 138 | 139 | DWORD exitCode = static_cast(-1); 140 | if (!GetExitCodeProcess(piProcInfo.hProcess, &exitCode)) { 141 | throw std::runtime_error("GetExitCodeProcess failed with error code " + std::to_string(::GetLastError())); 142 | } 143 | if (exitCode == STILL_ACTIVE) { 144 | throw std::runtime_error("Process did not exit yet although it should have."); 145 | } 146 | return ExecutionResult{commandline, static_cast(exitCode), std::move(receivedOutput)}; 147 | } 148 | 149 | #else // Not Windows, assume that popen() does what we want. 150 | 151 | ExecutionResult ExecuteProgramSync(std::string const & commandline) 152 | { 153 | // Redirect stderr, too. 154 | std::string const fullCmd = commandline + " 2>&1"; 155 | FILE * file = popen(fullCmd.c_str(), "r"); 156 | if (file == nullptr) { 157 | throw std::runtime_error("popen failed for command: " + fullCmd); 158 | } 159 | 160 | std::string receivedOutput; 161 | 162 | static constexpr size_t BUFFER_SIZE = 1024; 163 | char buffer[BUFFER_SIZE] = {0}; 164 | while (fgets(buffer, BUFFER_SIZE, file) != nullptr) { 165 | receivedOutput += buffer; 166 | } 167 | 168 | int const exitCode = pclose(file); 169 | if (exitCode == -1) { 170 | throw std::runtime_error("pclose failed"); 171 | } 172 | 173 | return ExecutionResult{fullCmd, WEXITSTATUS(exitCode), std::move(receivedOutput)}; 174 | } 175 | 176 | #endif 177 | 178 | 179 | //====================================================================================== 180 | // WindowsTemporaryCodeFileScope 181 | //====================================================================================== 182 | 183 | #ifdef TINY_OPTIONAL_WINDOWS_BUILD 184 | 185 | namespace 186 | { 187 | std::filesystem::path CreateTemporaryCodeFile(std::string const & code) 188 | { 189 | std::string const newDirName 190 | = "tiny_optional_tests_" + std::to_string(GetCurrentProcessId()) + "_" + std::to_string(GetCurrentThreadId()); 191 | 192 | auto const tempDir = std::filesystem::temp_directory_path() / newDirName; 193 | if (std::filesystem::is_directory(tempDir)) { 194 | std::filesystem::remove_all(tempDir); 195 | } 196 | std::filesystem::create_directory(tempDir); 197 | 198 | auto const tempFile = tempDir / "temp_tiny_optional_test.cpp"; 199 | 200 | // Overwrite a possibly existing file. 201 | std::ofstream out; 202 | out.open(tempFile); 203 | if (!out) { 204 | throw std::runtime_error("Failed to open temporary code file: " + tempFile.string()); 205 | } 206 | 207 | out << code << std::endl; 208 | if (!out) { 209 | throw std::runtime_error("Failed to write to temporary code file: " + tempFile.string()); 210 | } 211 | 212 | out.close(); 213 | 214 | if (!std::filesystem::exists(tempFile)) { 215 | throw std::runtime_error("Written temporary file does not exist after writing: " + tempFile.string()); 216 | } 217 | 218 | return std::filesystem::absolute(tempFile); 219 | } 220 | 221 | } // namespace 222 | 223 | WindowsTemporaryCodeFileScope::WindowsTemporaryCodeFileScope(std::string const & code) 224 | : mCodeFile(CreateTemporaryCodeFile(code)) 225 | { 226 | } 227 | 228 | 229 | WindowsTemporaryCodeFileScope ::~WindowsTemporaryCodeFileScope() 230 | { 231 | auto const tempDir = GetDirectory(); 232 | if (std::filesystem::is_directory(tempDir)) { 233 | // Try several times to remove the directory. It often happens that files that were opened by now exited processes 234 | // are still locked for a short amount of time. 235 | std::error_code ec; 236 | size_t attempt = 0; 237 | do { 238 | if (attempt > 0) { 239 | Sleep(1000); 240 | } 241 | ec = {}; 242 | std::filesystem::remove_all(tempDir, ec); 243 | ++attempt; 244 | } 245 | while (ec && attempt < 60); 246 | 247 | if (ec) { 248 | // Stop the tests. Note that we cannot throw an exception from within a destructor. 249 | std::cerr << "Error: Failed to remove temporary directory '" << tempDir << "' after several attempts." 250 | << std::endl; 251 | std::terminate(); 252 | } 253 | } 254 | } 255 | 256 | 257 | #endif 258 | 259 | 260 | //====================================================================================== 261 | // cpuid stuff 262 | //====================================================================================== 263 | 264 | #ifdef TINY_OPTIONAL_X86_OR_X64 265 | 266 | // Calls cpuid, returning eax, ebx, ecx and edx in that order. 267 | static std::array cpuid(std::uint32_t function_id, std::uint32_t subfunction_id = 0) 268 | { 269 | std::array regs{}; 270 | 271 | #if defined(TINY_OPTIONAL_MSVC_BUILD) 272 | __cpuidex(reinterpret_cast(regs.data()), function_id, subfunction_id); 273 | #elif defined(TINY_OPTIONAL_CLANG_BUILD) || defined(TINY_OPTIONAL_GCC_BUILD) 274 | __cpuid_count(function_id, subfunction_id, regs[0], regs[1], regs[2], regs[3]); 275 | #else 276 | #error Unsupported compiler 277 | #endif 278 | 279 | return regs; 280 | } 281 | 282 | 283 | // Returns the number of bits the CPU uses for virtual addresses. 284 | // Returns 48 on most current CPUs. 285 | // Based on https://stackoverflow.com/a/64519023/3740047 286 | static std::uint32_t GetVirtualAddressBits() 287 | { 288 | std::uint32_t virtualAddressSize; 289 | 290 | auto const extInfo = cpuid(0x80000000U); 291 | if (extInfo[0] < 0x80000008U) { 292 | virtualAddressSize = 32; 293 | } 294 | else { 295 | auto const memInfo = cpuid(0x80000008U); 296 | virtualAddressSize = (memInfo[0] >> 8) & 0xFF; 297 | } 298 | 299 | return virtualAddressSize; 300 | } 301 | 302 | 303 | bool IsAddressNonCanonical(std::uint64_t addr) 304 | { 305 | // See SentinelForExploitingUnusedBits for more information. 306 | 307 | // Returns 48 on most CPUs. 308 | std::uint64_t const virtualAddressBits = GetVirtualAddressBits(); 309 | 310 | // If the CPU uses 48 bits, this is 0x0000'7fff'ffff'ffff. 311 | std::uint64_t const maxCanonicalLow = (1ULL << (virtualAddressBits - 1)) - 1; 312 | // If the CPU uses 48 bits, this is 0xffff'8000'0000'0000. 313 | std::uint64_t const minCanonicalHigh = ~maxCanonicalLow; 314 | 315 | return maxCanonicalLow < addr && addr < minCanonicalHigh; 316 | } 317 | 318 | #endif 319 | -------------------------------------------------------------------------------- /tests/TestUtilities.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | 14 | //================================================================= 15 | // Detection macros 16 | //================================================================= 17 | 18 | #if defined(_WIN64) || defined(_WIN32) || defined(__MINGW32__) || defined(__MINGW64__) || defined(__CYGWIN__) 19 | #define TINY_OPTIONAL_WINDOWS_BUILD 20 | #endif 21 | 22 | #if defined(__clang__) 23 | #define TINY_OPTIONAL_CLANG_BUILD 24 | #elif defined(__GNUC__) || defined(__GNUG__) 25 | #define TINY_OPTIONAL_GCC_BUILD 26 | #elif defined(_MSC_VER) 27 | #define TINY_OPTIONAL_MSVC_BUILD 28 | #else 29 | #error Unknown compiler 30 | #endif 31 | 32 | // https://stackoverflow.com/a/66249936 33 | #if defined(__x86_64__) || defined(_M_X64) || defined(i386) || defined(__i386__) || defined(__i386) || defined(_M_IX86) 34 | #define TINY_OPTIONAL_X86_OR_X64 35 | #endif 36 | 37 | 38 | //================================================================= 39 | // Test assertions 40 | //================================================================= 41 | 42 | #ifdef TINY_OPTIONAL_MSVC_BUILD 43 | #define TINY_OPTIONAL_CURRENT_FUNC __FUNCSIG__ 44 | #else 45 | #define TINY_OPTIONAL_CURRENT_FUNC __PRETTY_FUNCTION__ 46 | #endif 47 | 48 | #define ASSERT_BOOL_IMPL(x, exprStr, expected, actual) \ 49 | do { \ 50 | if ((x)) { \ 51 | std::cerr << "\tExpression '" << exprStr << "' expected to be " << expected << ", but it is " << actual \ 52 | << ".\n\t\tIn line " << __LINE__ << ", function " << TINY_OPTIONAL_CURRENT_FUNC << ", file '" \ 53 | << __FILE__ << "'\n"; \ 54 | assert(!(x)); \ 55 | exit(42); \ 56 | } \ 57 | } \ 58 | while (false) 59 | 60 | #define ASSERT_FALSE(x) ASSERT_BOOL_IMPL(x, #x, "false", "true") 61 | #define ASSERT_TRUE(x) ASSERT_BOOL_IMPL(!(x), #x, "true", "false") 62 | 63 | #define FAIL() \ 64 | do { \ 65 | std::cerr << "\tFAIL() in line " << __LINE__ << ", function " << TINY_OPTIONAL_CURRENT_FUNC << ", file '" \ 66 | << __FILE__ << "'\n"; \ 67 | assert(false); \ 68 | exit(42); \ 69 | } \ 70 | while (false) 71 | 72 | 73 | #define EXPECT_EXCEPTION(expression, expectedException) \ 74 | do { \ 75 | try { \ 76 | expression; \ 77 | /* We should not execute this FAIL() because an exception should be thrown.*/ \ 78 | std::cerr << "\tExpected that expression '" << #expression << "' throws, but it did not." << std::endl; \ 79 | FAIL(); \ 80 | } \ 81 | catch (expectedException const &) { \ 82 | } \ 83 | catch (std::exception const & ex) { \ 84 | std::cerr << "\tUnexpected exception of type '" << typeid(ex).name() << "' caught. Message: " << ex.what() \ 85 | << std::endl; \ 86 | FAIL(); \ 87 | } \ 88 | catch (...) { \ 89 | std::cerr << "\tUnexpected unknown exception caught." << std::endl; \ 90 | FAIL(); \ 91 | } \ 92 | } \ 93 | while (false) 94 | 95 | 96 | //================================================================= 97 | // Other helpers 98 | //================================================================= 99 | 100 | 101 | // Returns true if lhs and rhs match, or if both are (any type of) NaN. 102 | template 103 | bool MatchingFloat(Float1T lhs, Float2T rhs) 104 | { 105 | static_assert(std::is_floating_point_v); 106 | static_assert(std::is_floating_point_v); 107 | if (std::isnan(lhs)) { 108 | return std::isnan(rhs); 109 | } 110 | else { 111 | return lhs == rhs; 112 | } 113 | } 114 | 115 | 116 | template 117 | bool AreEqual(Val1T && lhs, Val2T && rhs) 118 | { 119 | if constexpr (std::is_floating_point_v>) { 120 | return MatchingFloat(std::forward(lhs), std::forward(rhs)); 121 | } 122 | else { 123 | return std::forward(lhs) == std::forward(rhs); 124 | } 125 | } 126 | 127 | 128 | // Used to check that a container where data was moved from is now empty. 129 | // For not-supported containers, simply returns true. Especially, if T is e.g. an integer, 130 | // there is no difference between copying the value and moving the value. 131 | template 132 | bool EmptyOrNoContainer(T const &) 133 | { 134 | return true; 135 | } 136 | template 137 | bool EmptyOrNoContainer(std::vector const & c) 138 | { 139 | return c.empty(); 140 | } 141 | 142 | 143 | // Checks weather T is not hashable. 144 | // clang-format off 145 | template 146 | constexpr bool IsDisabledHash 147 | = !std::is_default_constructible_v> 148 | && !std::is_copy_constructible_v> 149 | && !std::is_move_constructible_v> 150 | && !std::is_copy_assignable_v> 151 | && !std::is_move_assignable_v>; 152 | // clang-format on 153 | 154 | 155 | // From boost 156 | template 157 | inline void hash_combine(std::size_t & seed, T const & v) 158 | { 159 | seed ^= std::hash()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); 160 | } 161 | 162 | 163 | inline std::ostream & operator<<(std::ostream & out, std::nullopt_t) 164 | { 165 | return out << "nullopt"; 166 | } 167 | 168 | 169 | inline std::string CompleteCppCodeSnippet(std::string const & codeSnippet) 170 | { 171 | std::string cpp = "#include \"tiny/optional.h\"\n"; 172 | cpp += "int main() {\n"; 173 | cpp += codeSnippet; 174 | cpp += "\n}\n"; 175 | return cpp; 176 | } 177 | 178 | 179 | struct ExecutionResult 180 | { 181 | std::string command; 182 | int exitCode = -1; 183 | std::string output; 184 | }; 185 | 186 | 187 | // Runs the specified program and waits until it terminates. 188 | // The returned string contains both stdout and stderr of the run program. 189 | ExecutionResult ExecuteProgramSync(std::string const & commandline); 190 | 191 | 192 | #ifdef TINY_OPTIONAL_WINDOWS_BUILD 193 | 194 | // Creates a folder in the 'Temp' directory and a cpp file in it containing the given code. 195 | // The destructor deletes everything again (folder + all the files in it). 196 | class WindowsTemporaryCodeFileScope 197 | { 198 | public: 199 | explicit WindowsTemporaryCodeFileScope(std::string const & code); 200 | ~WindowsTemporaryCodeFileScope(); 201 | 202 | // Returns the full path (absolute path including the filename). 203 | std::filesystem::path GetFullFilename() const 204 | { 205 | return mCodeFile; 206 | } 207 | 208 | // Returns just the filename with the extension. 209 | std::filesystem::path GetFilename() const 210 | { 211 | return mCodeFile.filename(); 212 | } 213 | 214 | // Returns the directory that contains the file. This is a temporary directory that get removed 215 | // when the scope is destructed. 216 | std::filesystem::path GetDirectory() const 217 | { 218 | return mCodeFile.parent_path(); 219 | } 220 | 221 | 222 | private: 223 | std::filesystem::path const mCodeFile; 224 | }; 225 | #endif 226 | 227 | 228 | #ifdef TINY_OPTIONAL_X86_OR_X64 229 | // Returns true if the given address is NOT a canonical address, i.e. returns true if the address can never be a valid 230 | // address. See `SentinelForExploitingUnusedBits` for more information. 231 | bool IsAddressNonCanonical(std::uint64_t addr); 232 | #endif 233 | -------------------------------------------------------------------------------- /tests/ExerciseTinyOptionalPayload2.cpp: -------------------------------------------------------------------------------- 1 | #include "ExerciseTinyOptionalPayload.h" 2 | #include "Exercises.h" 3 | #include "TestTypes.h" 4 | #include "TestUtilities.h" 5 | #include "tiny/optional.h" 6 | 7 | #ifdef TINY_OPTIONAL_WINDOWS_BUILD 8 | #include 9 | #endif 10 | 11 | 12 | void test_TinyOptionalPayload_NestedOptionals() 13 | { 14 | // Tests of nested optionals. These basically check that the various constructors are not ambiguous. 15 | { 16 | tiny::optional tiny1{43.0}; 17 | tiny::optional tiny2{44.0}; 18 | tiny::optional tinyEmpty; 19 | EXERCISE_OPTIONAL((tiny::optional>{}), EXPECT_SEPARATE, tiny1, tiny2); 20 | EXERCISE_OPTIONAL((tiny::optional>{}), EXPECT_SEPARATE, tinyEmpty, tiny2); 21 | 22 | EXERCISE_OPTIONAL((std::optional>{}), EXPECT_SEPARATE, tiny1, tiny2); 23 | EXERCISE_OPTIONAL((std::optional>{}), EXPECT_SEPARATE, tinyEmpty, tiny2); 24 | 25 | std::optional std1{43.0}; 26 | std::optional std2{44.0}; 27 | std::optional stdEmpty; 28 | EXERCISE_OPTIONAL((tiny::optional>{}), EXPECT_SEPARATE, std1, std2); 29 | EXERCISE_OPTIONAL((tiny::optional>{}), EXPECT_SEPARATE, stdEmpty, std2); 30 | } 31 | } 32 | 33 | void test_TinyOptionalPayload_ConstAndVolatile() 34 | { 35 | // Tests of const and volatile 36 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, 43.0, 44.0); 37 | static_assert(std::is_same_v::value_type, double const>); 38 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, 43.0, 44.0); 39 | static_assert(std::is_same_v::value_type, double volatile>); 40 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, 43.0, 44.0); 41 | static_assert(std::is_same_v::value_type, double volatile const>); 42 | 43 | EXERCISE_OPTIONAL((tiny::optional{}), EXPECT_INPLACE, 43, 44); 44 | static_assert(std::is_same_v::value_type, int const>); 45 | EXERCISE_OPTIONAL((tiny::optional{}), EXPECT_INPLACE, 43, 44); 46 | static_assert(std::is_same_v::value_type, int volatile>); 47 | EXERCISE_OPTIONAL((tiny::optional{}), EXPECT_INPLACE, 43, 44); 48 | static_assert(std::is_same_v::value_type, int volatile const>); 49 | 50 | EXERCISE_OPTIONAL((tiny::optional{}), EXPECT_SEPARATE, 43, 44); 51 | static_assert(std::is_same_v::value_type, int const>); 52 | EXERCISE_OPTIONAL((tiny::optional{}), EXPECT_SEPARATE, 43, 44); 53 | static_assert(std::is_same_v::value_type, int volatile>); 54 | EXERCISE_OPTIONAL((tiny::optional{}), EXPECT_SEPARATE, 43, 44); 55 | static_assert(std::is_same_v::value_type, int volatile const>); 56 | } 57 | 58 | void test_TinyOptionalPayload_Cpp20NTTP() 59 | { 60 | // Non type template parameters (NTTP) since C++20 support floating point values and literal class types. 61 | // Clang supports this fully since clang 18. But we cannot simply use a feature test on "__cpp_nontype_template_args" 62 | // because clang 18 still does not define it as 201911L (but instead uses 201411L, which seems incorrect). 63 | #if defined(TINY_OPTIONAL_CPP20) && (!defined(TINY_OPTIONAL_CLANG_BUILD) || __clang_major__ >= 18) 64 | { 65 | // Floating point, directly in the payload 66 | EXERCISE_OPTIONAL((tiny::optional{}), EXPECT_INPLACE, 43.0f, 44.0f); 67 | EXERCISE_OPTIONAL((tiny::optional{}), EXPECT_INPLACE, 43.0, 44.0); 68 | EXERCISE_OPTIONAL((tiny::optional{}), EXPECT_INPLACE, 43.0L, 44.0L); 69 | 70 | // Floating point, stored in member. 71 | EXERCISE_OPTIONAL( 72 | (tiny::optional{}), 73 | cInPlaceExpectationForMemPtr, 74 | TestClassForInplace{}, 75 | TestClassForInplace(43, 44.0, 45, nullptr)); 76 | 77 | // Literal class, directly as payload. 78 | constexpr LiteralClass lc1(42, 43.0); 79 | constexpr LiteralClass lc2(44, 45.0); 80 | constexpr LiteralClass lc3(46, 47.0); 81 | EXERCISE_OPTIONAL_WITH_CONSTRUCTOR_ARGS((tiny::optional{}), EXPECT_INPLACE, lc2, lc3, 48, 49.0); 82 | 83 | // Literal class, stored in member. 84 | EXERCISE_OPTIONAL( 85 | (tiny::optional{}), 86 | cInPlaceExpectationForMemPtr, 87 | TestClassWithLiteralClass{}, 88 | TestClassWithLiteralClass(43, 44.0, 45)); 89 | } 90 | #endif 91 | } 92 | 93 | void test_TinyOptionalPayload_WindowsHandles() 94 | { 95 | // On Windows, there are various handle types (HANDLE, HINSTANCE, HWND, etc). They are essentially void*. The problem 96 | // is that there special HANDLE values that the WinAPI can return. These tests here essentially test that we 97 | // do not use one of those special values as sentinel to indicate the empty state. See 98 | // https://stackoverflow.com/a/3905643/3740047 and https://stackoverflow.com/a/45632388/3740047. 99 | 100 | #ifdef TINY_OPTIONAL_WINDOWS_BUILD 101 | { 102 | // clang-format off 103 | 104 | // Some error codes returned by common WinAPI functions. 105 | // https://devblogs.microsoft.com/oldnewthing/20040302-00/?p=40443 106 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, static_cast(NULL), INVALID_HANDLE_VALUE); 107 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, static_cast(0), INVALID_HANDLE_VALUE); 108 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, reinterpret_cast(-1), INVALID_HANDLE_VALUE); 109 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE); 110 | 111 | // Pseudo handles: 112 | // https://github.com/winsiderss/systeminformer/blob/c19d69317a8eedcce773eb7317462eac8dfebf66/phnt/include/ntpsapi.h#L1294 113 | // https://devblogs.microsoft.com/oldnewthing/20210105-00/?p=104667 114 | // Current possible values: 115 | // NtCurrentProcess() ((HANDLE)(LONG_PTR)-1) 116 | // NtCurrentThread() ((HANDLE)(LONG_PTR)-2) 117 | // NtCurrentSession() ((HANDLE)(LONG_PTR)-3) 118 | // NtCurrentProcessToken() ((HANDLE)(LONG_PTR)-4) // NtOpenProcessToken(NtCurrentProcess()) 119 | // NtCurrentThreadToken() ((HANDLE)(LONG_PTR)-5) // NtOpenThreadToken(NtCurrentThread()) 120 | // NtCurrentThreadEffectiveToken() ((HANDLE)(LONG_PTR)-6) // NtOpenThreadToken(NtCurrentThread()) 121 | // + NtOpenProcessToken(NtCurrentProcess()) 122 | // NtCurrentSilo() ((HANDLE)(LONG_PTR)-1) 123 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, ::GetCurrentProcess(), INVALID_HANDLE_VALUE); 124 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, ::GetCurrentThread(), INVALID_HANDLE_VALUE); 125 | #ifndef TINY_OPTIONAL_GCC_BUILD // Functions are unknown to mingw for some reason. 126 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, ::GetCurrentProcessToken(), INVALID_HANDLE_VALUE); 127 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, ::GetCurrentThreadToken(), INVALID_HANDLE_VALUE); 128 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, ::GetCurrentThreadEffectiveToken(), INVALID_HANDLE_VALUE); 129 | #endif 130 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, ((HANDLE)(LONG_PTR)(-1)), INVALID_HANDLE_VALUE); 131 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, ((HANDLE)(LONG_PTR)(-2)), INVALID_HANDLE_VALUE); 132 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, ((HANDLE)(LONG_PTR)(-3)), INVALID_HANDLE_VALUE); 133 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, ((HANDLE)(LONG_PTR)(-4)), INVALID_HANDLE_VALUE); 134 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, ((HANDLE)(LONG_PTR)(-5)), INVALID_HANDLE_VALUE); 135 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, ((HANDLE)(LONG_PTR)(-6)), INVALID_HANDLE_VALUE); 136 | 137 | 138 | // Some return values of ShellExecute(). 139 | EXERCISE_OPTIONAL( 140 | (tiny::optional{}), 141 | cInPlaceExpectationForUnusedBits, 142 | reinterpret_cast(ERROR_FILE_NOT_FOUND), 143 | static_cast(0)); 144 | EXERCISE_OPTIONAL( 145 | (tiny::optional{}), 146 | cInPlaceExpectationForUnusedBits, 147 | reinterpret_cast(SE_ERR_SHARE), 148 | static_cast(0)); 149 | 150 | // clang-format on 151 | } 152 | #endif 153 | 154 | // Same as above, but instead of using the Windows specific types, we directly use void*. Less direct, but that way we 155 | // can also run these tests on non-Windows builds, to ensure some behavior consistency between the operating systems. 156 | { 157 | // clang-format off 158 | 159 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, static_cast(0), nullptr); 160 | 161 | // Pseudo handles. The -1 is additionally INVALID_HANDLE_VALUE. 162 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, reinterpret_cast(-1), nullptr); 163 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, reinterpret_cast(-2), nullptr); 164 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, reinterpret_cast(-3), nullptr); 165 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, reinterpret_cast(-4), nullptr); 166 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, reinterpret_cast(-5), nullptr); 167 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, reinterpret_cast(-6), nullptr); 168 | 169 | // As for ShellExecute() above. 170 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, reinterpret_cast(2), nullptr); 171 | EXERCISE_OPTIONAL((tiny::optional{}), cInPlaceExpectationForUnusedBits, reinterpret_cast(26), nullptr); 172 | 173 | // clang-format on 174 | } 175 | } 176 | 177 | void test_TinyOptionalPayload_OtherTypes() 178 | { 179 | // We befriended the present function 180 | EXERCISE_OPTIONAL( 181 | (tiny::optional{}), 182 | cInPlaceExpectationForMemPtr, 183 | TestClassPrivate{42.0}, 184 | TestClassPrivate{43.0}); 185 | 186 | EXERCISE_OPTIONAL((tiny::optional{}), EXPECT_SEPARATE, TestClass(true, 4.0), TestClass(true, 5.0)); 187 | // Uses deduction guide 188 | EXERCISE_OPTIONAL((tiny::optional{TestClass{}}), EXPECT_SEPARATE, TestClass(true, 4.0), TestClass(true, 5.0)); 189 | 190 | // Just to have some class with noexcept(false) stuff. 191 | EXERCISE_OPTIONAL( 192 | (tiny::optional{}), 193 | EXPECT_SEPARATE, 194 | TestClassWithExcept{42}, 195 | TestClassWithExcept{43}); 196 | 197 | // Targets functions of tiny::optional that work with std::initializer_list. 198 | { 199 | std::initializer_list const initList = {3, 4, 5}; 200 | EXERCISE_OPTIONAL_WITH_CONSTRUCTOR_ARGS( 201 | (tiny::optional{}), 202 | EXPECT_SEPARATE, 203 | TestClassWithInitializerList({1}), 204 | TestClassWithInitializerList({2}), 205 | initList); 206 | EXERCISE_OPTIONAL_WITH_CONSTRUCTOR_ARGS( 207 | (tiny::optional{}), 208 | EXPECT_SEPARATE, 209 | TestClassWithInitializerList({1}), 210 | TestClassWithInitializerList({2}), 211 | initList, 212 | ScopedEnum::v2); 213 | } 214 | 215 | // Tests that target cases where the deduction guide of std::optional would come into play. 216 | // https://en.cppreference.com/w/cpp/utility/optional/deduction_guides 217 | { 218 | int arr1[2] = {1, 2}; 219 | int arr2[2] = {3, 4}; 220 | static_assert(std::is_same_v); 221 | EXERCISE_OPTIONAL((tiny::optional{arr1}), cInPlaceExpectationForUnusedBits, arr1, arr2); 222 | 223 | struct NonCopyable 224 | { 225 | NonCopyable() = default; 226 | NonCopyable(NonCopyable const &) = delete; 227 | NonCopyable(NonCopyable &&) = default; 228 | }; 229 | 230 | static_assert(std::is_same_v); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /include/tiny_optional.natvis: -------------------------------------------------------------------------------- 1 | 2 | 3 | 30 | 31 | 32 | 46 | 47 | 48 | 49 | 50 | 51 | nullopt (optional is empty) 52 | Not empty. Value={{{mStorage.payload}}} 53 | 54 | mStorage.payload 55 | "nullopt",sb 56 | (*this).is_compressed 57 | 58 | 59 | 60 | 61 | 63 | 64 | nullopt (optional is empty) 65 | Not empty. Value={{{mStorage.storage}}} 66 | 67 | "nullopt",sb 68 | mStorage.storage 69 | (*this).is_compressed 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | {*this,view(TinyOptionalInplaceStorageView)} 78 | 79 | this,view(TinyOptionalInplaceStorageView) 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | {*this,view(TinyOptionalInplaceStorageView)} 88 | 89 | this,view(TinyOptionalInplaceStorageView) 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | {*this,view(TinyOptionalInplaceStorageView)} 98 | 99 | this,view(TinyOptionalInplaceStorageView) 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | {*this,view(TinyOptionalInplaceStorageView)} 108 | 109 | this,view(TinyOptionalInplaceStorageView) 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | {*this,view(TinyOptionalInplaceStorageView)} 118 | 119 | this,view(TinyOptionalInplaceStorageView) 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | {*this,view(TinyOptionalInplaceStorageView)} 128 | 129 | this,view(TinyOptionalInplaceStorageView) 130 | 131 | 132 | 133 | 134 | 151 | 152 | 157 | 158 | {*this,view(TinyOptionalInplaceStorageView)} 159 | 160 | (Preview for custom flag manipulators not supported in natvis) 161 | 162 | this,view(TinyOptionalInplaceStorageView) 163 | 164 | mStorage.storage 165 | 166 | 167 | 168 | 174 | 175 | 176 | 181 | 182 | {*this,view(TinyOptionalInplaceStorageView)} 183 | 184 | (Preview for custom flag manipulators not supported in natvis) 185 | 186 | this,view(TinyOptionalInplaceStorageView) 187 | 188 | mStorage.storage 189 | 190 | 191 | 192 | 193 | 196 | 197 | 198 | {*this,view(TinyOptionalInplaceStorageView)} 199 | 200 | this,view(TinyOptionalInplaceStorageView) 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | {*this,view(TinyOptionalInplaceStorageView)} 209 | 210 | this,view(TinyOptionalInplaceStorageView) 211 | 212 | 213 | 214 | 215 | 216 | 217 | (Preview is not supported) 218 | 219 | mStorage.storage 220 | (*this).is_compressed 221 | 222 | 223 | 224 | -------------------------------------------------------------------------------- /tests/ExerciseOptionalWithCustomFlagManipulator.cpp: -------------------------------------------------------------------------------- 1 | #include "ExerciseOptionalWithCustomFlagManipulator.h" 2 | 3 | #include "Exercises.h" 4 | #include "TestUtilities.h" 5 | #include "tiny/optional.h" 6 | 7 | #include 8 | #include 9 | 10 | #if defined(__GNUG__) && !defined(__clang__) 11 | // Disable incorrect warning for gcc. 12 | #pragma GCC diagnostic push 13 | #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" 14 | #endif 15 | 16 | 17 | namespace 18 | { 19 | //==================================================================== 20 | // Example class in a namespace: Test::Class1 21 | //==================================================================== 22 | 23 | namespace Test 24 | { 25 | struct Class1 26 | { 27 | std::string someString; 28 | int someInt = 42; 29 | double someDouble = 42.2; 30 | 31 | Class1() 32 | : someString("someString") 33 | { 34 | } 35 | 36 | explicit Class1(std::string const & s) 37 | : someString(s) 38 | { 39 | } 40 | 41 | friend bool operator==(Class1 const & lhs, Class1 const & rhs) 42 | { 43 | return lhs.someString == rhs.someString && lhs.someInt == rhs.someInt && lhs.someDouble == rhs.someDouble; 44 | } 45 | }; 46 | } // namespace Test 47 | 48 | inline std::string const CLASS1_SENTINEL = "SENTINEL"; 49 | } // namespace 50 | 51 | 52 | template <> 53 | struct tiny::optional_flag_manipulator 54 | { 55 | static bool is_empty(Test::Class1 const & payload) noexcept 56 | { 57 | return payload.someString == CLASS1_SENTINEL; 58 | } 59 | 60 | static void init_empty_flag(Test::Class1 & uninitializedPayloadMemory) noexcept 61 | { 62 | ::new (&uninitializedPayloadMemory) Test::Class1(CLASS1_SENTINEL); 63 | } 64 | 65 | static void invalidate_empty_flag(Test::Class1 & emptyPayload) noexcept 66 | { 67 | emptyPayload.~Class1(); 68 | } 69 | }; 70 | 71 | 72 | //==================================================================== 73 | // Example class: Class2 (contains Class1) 74 | //==================================================================== 75 | 76 | namespace 77 | { 78 | struct Class2 79 | { 80 | int member1 = 999; 81 | Test::Class1 member2; 82 | 83 | Class2() = default; 84 | 85 | Class2(int i, std::string const & s) 86 | : member1(i) 87 | , member2(s) 88 | { 89 | } 90 | 91 | friend bool operator==(Class2 const & lhs, Class2 const & rhs) 92 | { 93 | return lhs.member1 == rhs.member1 && lhs.member2 == rhs.member2; 94 | } 95 | }; 96 | } // namespace 97 | 98 | 99 | //==================================================================== 100 | // Example class: OuterClass::NestedClass 101 | //==================================================================== 102 | 103 | namespace 104 | { 105 | struct OuterClass 106 | { 107 | struct NestedClass 108 | { 109 | bool isEmpty = false; 110 | 111 | friend bool operator==(NestedClass const & lhs, NestedClass const & rhs) 112 | { 113 | return lhs.isEmpty == rhs.isEmpty; 114 | } 115 | }; 116 | 117 | NestedClass nestedClass; 118 | 119 | friend bool operator==(OuterClass const & lhs, OuterClass const & rhs) 120 | { 121 | return lhs.nestedClass == rhs.nestedClass; 122 | } 123 | }; 124 | } // namespace 125 | 126 | 127 | template <> 128 | struct tiny::optional_flag_manipulator 129 | { 130 | static bool is_empty(OuterClass::NestedClass const & payload) noexcept 131 | { 132 | return payload.isEmpty; 133 | } 134 | 135 | static void init_empty_flag(OuterClass::NestedClass & uninitializedPayloadMemory) noexcept 136 | { 137 | ::new (&uninitializedPayloadMemory) OuterClass::NestedClass(); 138 | uninitializedPayloadMemory.isEmpty = true; 139 | } 140 | 141 | static void invalidate_empty_flag(OuterClass::NestedClass & emptyPayload) noexcept 142 | { 143 | emptyPayload.~NestedClass(); 144 | } 145 | }; 146 | 147 | 148 | //==================================================================== 149 | // Example class: OutermostClass 150 | //==================================================================== 151 | 152 | namespace 153 | { 154 | struct OutermostClass 155 | { 156 | std::shared_ptr someString; 157 | OuterClass outerClass; 158 | 159 | explicit OutermostClass(std::string const & s) 160 | : someString(std::make_shared(s)) 161 | { 162 | } 163 | 164 | friend bool operator==(OutermostClass const & lhs, OutermostClass const & rhs) 165 | { 166 | if (lhs.outerClass == rhs.outerClass) { 167 | if (lhs.someString == nullptr && rhs.someString == nullptr) { 168 | return true; 169 | } 170 | else if (lhs.someString != nullptr && rhs.someString != nullptr) { 171 | return *lhs.someString == *rhs.someString; 172 | } 173 | } 174 | 175 | return false; 176 | } 177 | }; 178 | } // namespace 179 | 180 | 181 | // Exploiting undefined behavior: In the empty state of the optional, we do not construct a full instance of 182 | // OutermostClass. Instead, we only change the 'payload.outerClass.nestedClass.isEmpty' 183 | template <> 184 | struct tiny::optional_flag_manipulator 185 | { 186 | static bool is_empty(OutermostClass const & payload) noexcept 187 | { 188 | return payload.outerClass.nestedClass.isEmpty; 189 | } 190 | 191 | static void init_empty_flag(OutermostClass & uninitializedPayloadMemory) noexcept 192 | { 193 | ::new (&uninitializedPayloadMemory.outerClass.nestedClass.isEmpty) bool(true); 194 | } 195 | 196 | static void invalidate_empty_flag(OutermostClass & /*emptyPayload*/) noexcept { } 197 | }; 198 | 199 | 200 | //==================================================================== 201 | // Example class: ExpensiveToInitialize (from the readme) 202 | //==================================================================== 203 | 204 | namespace 205 | { 206 | struct ExpensiveToInitialize 207 | { 208 | std::string someString; 209 | int someInt; // -1 is never valid and exploited as sentinel 210 | 211 | explicit ExpensiveToInitialize() 212 | : someString("Some very long string...") 213 | , someInt(0) 214 | { 215 | // More expensive stuff. 216 | } 217 | 218 | friend bool operator==(ExpensiveToInitialize const & lhs, ExpensiveToInitialize const & rhs) 219 | { 220 | return lhs.someString == rhs.someString && lhs.someInt == rhs.someInt; 221 | } 222 | }; 223 | } // namespace 224 | 225 | 226 | // Exploiting undefined behavior: In the empty state of the optional, we do not 227 | // construct a full instance of `ExpensiveToInitialize`. Instead, we only change 228 | // the `payload.someInt` member. 229 | template <> 230 | struct tiny::optional_flag_manipulator 231 | { 232 | static bool is_empty(ExpensiveToInitialize const & payload) noexcept 233 | { 234 | return payload.someInt == -1; 235 | } 236 | 237 | static void init_empty_flag(ExpensiveToInitialize & uninitializedPayloadMemory) noexcept 238 | { 239 | ::new (&uninitializedPayloadMemory.someInt) int(-1); 240 | } 241 | 242 | static void invalidate_empty_flag(ExpensiveToInitialize & /*emptyPayload*/) noexcept 243 | { 244 | // Empty, because `someInt` has no destructor that we could call. 245 | } 246 | }; 247 | 248 | 249 | //==================================================================== 250 | // Example enums 251 | //==================================================================== 252 | 253 | namespace 254 | { 255 | namespace EnumNamespace 256 | { 257 | enum TestUnscopedEnum 258 | { 259 | INVALID = -1, 260 | UNSCOPED_VALUE1, 261 | UNSCOPED_VALUE2 262 | }; 263 | } 264 | } // namespace 265 | 266 | 267 | template <> 268 | struct tiny::optional_flag_manipulator 269 | : tiny::sentinel_flag_manipulator 270 | { 271 | }; 272 | 273 | 274 | namespace 275 | { 276 | enum class TestScopedEnum 277 | { 278 | VALUE1, 279 | VALUE2, 280 | MAX_NUM 281 | }; 282 | } 283 | 284 | 285 | template <> 286 | struct tiny::optional_flag_manipulator 287 | : tiny::sentinel_flag_manipulator 288 | { 289 | }; 290 | 291 | 292 | //==================================================================== 293 | // Specialization via enable_if, which uses the 2nd template argument of optional_flag_manipulator. 294 | // For this we simply use enums since they are more convenient than creating two full blown classes. 295 | //==================================================================== 296 | 297 | namespace 298 | { 299 | enum class SpecialEnum1 : int 300 | { 301 | VALUE1 = 0, 302 | VALUE2 = 1 303 | }; 304 | 305 | 306 | enum class SpecialEnum2 : int 307 | { 308 | VALUE1 = 0, 309 | VALUE2 = 1 310 | }; 311 | } // namespace 312 | 313 | 314 | template 315 | struct tiny::optional_flag_manipulator< 316 | SpecialEnumType, 317 | std::enable_if_t || std::is_same_v>> 318 | { 319 | static bool is_empty(SpecialEnumType const & payload) noexcept 320 | { 321 | return static_cast(payload) == -1; 322 | } 323 | 324 | static void init_empty_flag(SpecialEnumType & uninitializedPayloadMemory) noexcept 325 | { 326 | ::new (&uninitializedPayloadMemory) SpecialEnumType(static_cast(-1)); 327 | } 328 | 329 | static void invalidate_empty_flag(SpecialEnumType & emptyPayload) noexcept 330 | { 331 | emptyPayload.~SpecialEnumType(); 332 | } 333 | }; 334 | 335 | 336 | //==================================================================== 337 | // Tests of the types 338 | //==================================================================== 339 | 340 | void test_TinyOptionalWithRegisteredCustomFlagManipulator() 341 | { 342 | // Basic test: Especially EXPECT_INPLACE, since the tiny::optional should use the flag manipulator. 343 | { 344 | EXERCISE_OPTIONAL((tiny::optional{}), EXPECT_INPLACE, Test::Class1{"val1"}, Test::Class1{"val2"}); 345 | } 346 | 347 | // Storing the 'IsEmpty' flag in Class2::member2, which should be possible because of the flag manipulator for the 348 | // type of member2, i.e. of Class1. 349 | { 350 | EXERCISE_OPTIONAL( 351 | (tiny::optional{}), 352 | cInPlaceExpectationForMemPtr, 353 | Class2(42, "val1"), 354 | Class2(43, "val2")); 355 | } 356 | 357 | // We specify some member in Class1 where the IsEmpty flag should be stored. Thus, tiny::optional should actually 358 | // ignore the flag manipulator and use the sentinel instead. Thus, the special "SENTINEL" string is expected to be a 359 | // valid value. 360 | { 361 | EXERCISE_OPTIONAL( 362 | (tiny::optional{}), 363 | cInPlaceExpectationForMemPtr, 364 | Test::Class1(CLASS1_SENTINEL), 365 | Test::Class1("val2")); 366 | 367 | EXERCISE_OPTIONAL( 368 | (tiny::optional{}), 369 | cInPlaceExpectationForMemPtr, 370 | Test::Class1(CLASS1_SENTINEL), 371 | Test::Class1("val2")); 372 | } 373 | 374 | // Test with a NestedClass. 375 | { 376 | EXERCISE_OPTIONAL( 377 | (tiny::optional{}), 378 | EXPECT_INPLACE, 379 | OuterClass::NestedClass{}, 380 | OuterClass::NestedClass{}); 381 | #ifdef TINY_OPTIONAL_TRIVIAL_SPECIAL_MEMBER_FUNCTIONS 382 | // NestedClass is trivially copyable/movable, and so should tiny::optional be in C++20. 383 | static_assert(std::is_trivially_copy_constructible_v>); 384 | static_assert(std::is_trivially_move_constructible_v>); 385 | static_assert(std::is_trivially_move_assignable_v>); 386 | static_assert(std::is_trivially_move_assignable_v>); 387 | #endif 388 | } 389 | 390 | // Test where we exploit some undefined behavior so as not to construct the full payload while the optional is in the 391 | // empty state. 392 | { 393 | EXERCISE_OPTIONAL_WITH_CONSTRUCTOR_ARGS( 394 | (tiny::optional{}), 395 | EXPECT_INPLACE, 396 | OutermostClass{"val1"}, 397 | OutermostClass{"val2"}, 398 | "val3"); 399 | // OutermostClass is not trivially copyable/moveable, and thus tiny::optional should not be. 400 | static_assert(!std::is_trivially_copy_constructible_v>); 401 | static_assert(!std::is_trivially_move_constructible_v>); 402 | static_assert(!std::is_trivially_move_assignable_v>); 403 | static_assert(!std::is_trivially_move_assignable_v>); 404 | 405 | // Example from the readme. 406 | EXERCISE_OPTIONAL( 407 | (tiny::optional{}), 408 | EXPECT_INPLACE, 409 | ExpensiveToInitialize{}, 410 | ExpensiveToInitialize{}); 411 | } 412 | 413 | // Version with a **const** payload: Since we do not provide a specialization of the flag manipulator for const 414 | // payloads, we expect that the library uses a separate bool to indicate the empty state. 415 | { 416 | EXERCISE_OPTIONAL( 417 | (tiny::optional{}), 418 | EXPECT_SEPARATE, 419 | Test::Class1{"val1"}, 420 | Test::Class1{"val2"}); 421 | } 422 | 423 | // Version where, at the point of definition, the "tiny/optional.h" header is not included. 424 | { 425 | EXERCISE_OPTIONAL( 426 | (tiny::optional>{}), 427 | EXPECT_INPLACE, 428 | ClassInHeader{}, 429 | ClassInHeader{}); 430 | } 431 | 432 | // Enums 433 | { 434 | EXERCISE_OPTIONAL( 435 | (tiny::optional{}), 436 | EXPECT_INPLACE, 437 | EnumNamespace::UNSCOPED_VALUE1, 438 | EnumNamespace::UNSCOPED_VALUE2); 439 | #ifdef TINY_OPTIONAL_TRIVIAL_SPECIAL_MEMBER_FUNCTIONS 440 | // TestUnscopedEnum is trivially copyable/movable, and so should tiny::optional be in C++20. 441 | static_assert(std::is_trivially_copy_constructible_v>); 442 | static_assert(std::is_trivially_move_constructible_v>); 443 | static_assert(std::is_trivially_move_assignable_v>); 444 | static_assert(std::is_trivially_move_assignable_v>); 445 | #endif 446 | 447 | EXERCISE_OPTIONAL( 448 | (tiny::optional{}), 449 | EXPECT_INPLACE, 450 | TestScopedEnum::VALUE1, 451 | TestScopedEnum::VALUE2); 452 | #ifdef TINY_OPTIONAL_TRIVIAL_SPECIAL_MEMBER_FUNCTIONS 453 | // TestScopedEnum is trivially copyable/movable, and so should tiny::optional be in C++20. 454 | static_assert(std::is_trivially_copy_constructible_v>); 455 | static_assert(std::is_trivially_move_constructible_v>); 456 | static_assert(std::is_trivially_move_assignable_v>); 457 | static_assert(std::is_trivially_move_assignable_v>); 458 | #endif 459 | } 460 | 461 | // Cases where the flag manipulator specialization was defined with the help of std::enable_if. 462 | { 463 | EXERCISE_OPTIONAL((tiny::optional{}), EXPECT_INPLACE, SpecialEnum1::VALUE1, SpecialEnum1::VALUE2); 464 | EXERCISE_OPTIONAL((tiny::optional{}), EXPECT_INPLACE, SpecialEnum2::VALUE1, SpecialEnum2::VALUE2); 465 | } 466 | 467 | // A special test for transform(): It always returns a tiny::optional. Nevertheless, it should see 468 | // the flag manipulator. 469 | { 470 | tiny::optional opt1 = Test::Class1{"val1"}; 471 | auto const opt2 = opt1.transform([](Test::Class1 const & c1) { return OutermostClass{c1.someString}; }); 472 | static_assert(sizeof(opt1) == sizeof(Test::Class1)); 473 | static_assert(sizeof(opt2) == sizeof(OutermostClass)); 474 | static_assert(std::is_same_v, tiny::optional>); 475 | ASSERT_TRUE(opt2->someString != nullptr); 476 | ASSERT_TRUE(opt1->someString == *opt2->someString); 477 | } 478 | 479 | // Test that tiny::optional_aip picks up the tiny::optional_flag_manipulator specialization and uses it. If it did 480 | // not, we would get a compilation error here. 481 | { 482 | EXERCISE_OPTIONAL((tiny::optional_aip{}), EXPECT_INPLACE, Test::Class1{"val1"}, Test::Class1{"val2"}); 483 | } 484 | } 485 | 486 | #if defined(__GNUG__) && !defined(__clang__) 487 | #pragma GCC diagnostic pop 488 | #endif 489 | --------------------------------------------------------------------------------