├── .clang-format ├── .clang-tidy ├── .codespellrc ├── .github ├── actions │ ├── install-clang │ │ └── action.yml │ └── install-gcc │ │ └── action.yml └── workflows │ ├── ci.yml │ ├── codeql-analysis.yml │ └── update_readme.yml ├── .gitignore ├── .vscode └── settings.json ├── BUILDING.md ├── CMakeLists.txt ├── CMakePresets.json ├── CMakeUserPresets.template.json ├── HACKING.md ├── LICENSE ├── README.md ├── benchmarks ├── CMakeLists.txt └── src │ ├── benchmark_helpers.h │ ├── construction_benchmarks.cpp │ └── unit_benchmarks.cpp ├── cmake-format.yaml ├── cmake ├── code-coverage.cmake ├── dev-mode.cmake ├── docs-ci.cmake ├── docs.cmake ├── folders.cmake ├── install-config.cmake ├── install-rules.cmake ├── lint-targets.cmake ├── lint.cmake ├── open-cpp-coverage.cmake.example ├── prelude.cmake ├── project-is-top-level.cmake ├── spell-targets.cmake ├── spell.cmake └── variables.cmake ├── examples ├── .clang-tidy ├── CMakeLists.txt ├── firstname_lastname_example.cpp ├── specializers_example.cpp ├── unit_energy_example.cpp └── unit_example.cpp ├── include └── stronk │ ├── can_decrement.h │ ├── can_format.h │ ├── can_hash.h │ ├── can_increment.h │ ├── can_stream.h │ ├── extensions │ ├── absl.h │ ├── absl.hpp │ ├── doctest.h │ ├── doctest.hpp │ ├── fmt.h │ ├── fmt.hpp │ ├── glaze.h │ ├── glaze.hpp │ ├── gtest.h │ ├── gtest.hpp │ ├── nlohmann_json.h │ └── nlohmann_json.hpp │ ├── prefabs.h │ ├── prefabs │ └── stronk_flag.hpp │ ├── skills │ ├── can_abs.hpp │ ├── can_be_used_as_flag.hpp │ ├── can_decrement.hpp │ ├── can_divide.hpp │ ├── can_format.hpp │ ├── can_hash.hpp │ ├── can_increment.hpp │ ├── can_index.hpp │ ├── can_isnan.hpp │ ├── can_iterate.hpp │ ├── can_multiply.hpp │ └── can_stream.hpp │ ├── stronk.h │ ├── stronk.hpp │ ├── unit.h │ ├── unit.hpp │ └── utilities │ ├── constexpr_helpers.hpp │ ├── dimensions.hpp │ ├── equality.hpp │ ├── macros.hpp │ └── strings.hpp ├── sonar-project.properties ├── tests ├── .clang-tidy ├── CMakeLists.txt └── src │ ├── extensions │ ├── absl_tests.cpp │ ├── doctest_tests.cpp │ ├── fmt_tests.cpp │ ├── glaze_tests.cpp │ ├── gtest_tests.cpp │ └── nlohmann_json_tests.cpp │ ├── prefabs │ └── stronk_flag_tests.cpp │ ├── skills │ ├── can_abs_tests.cpp │ ├── can_decrement_tests.cpp │ ├── can_divide_tests.cpp │ ├── can_format_tests.cpp │ ├── can_hash_tests.cpp │ ├── can_increment_tests.cpp │ ├── can_index_tests.cpp │ ├── can_isnan_tests.cpp │ ├── can_iterate_tests.cpp │ ├── can_multiply_tests.cpp │ └── can_stream_tests.cpp │ ├── specializers_tests.cpp │ ├── stronk_tests.cpp │ ├── unit_tests.cpp │ └── utilities │ └── dimensions_tests.cpp ├── tools ├── check_include_what_you_use.sh ├── check_install_packages_match.py ├── embed_code.py └── run_clangd_tidy_on_changed.sh ├── vcpkg.json └── version.py /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: Chromium 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveMacros: false 7 | AlignConsecutiveAssignments: false 8 | AlignConsecutiveBitFields: false 9 | AlignConsecutiveDeclarations: false 10 | AlignEscapedNewlines: true 11 | AlignOperands: AlignAfterOperator 12 | AlignTrailingComments: true 13 | AllowAllArgumentsOnNextLine: true 14 | AllowAllConstructorInitializersOnNextLine: false 15 | AllowAllParametersOfDeclarationOnNextLine: true 16 | AllowShortEnumsOnASingleLine: false 17 | AllowShortBlocksOnASingleLine: Empty 18 | AllowShortCaseLabelsOnASingleLine: false 19 | AllowShortFunctionsOnASingleLine: Empty 20 | AllowShortLambdasOnASingleLine: All 21 | AllowShortIfStatementsOnASingleLine: Never 22 | AllowShortLoopsOnASingleLine: false 23 | AlwaysBreakAfterReturnType: None 24 | AlwaysBreakBeforeMultilineStrings: true 25 | AlwaysBreakTemplateDeclarations: Yes 26 | BinPackArguments: false 27 | BinPackParameters: false 28 | BraceWrapping: 29 | AfterCaseLabel: false 30 | AfterClass: true 31 | AfterControlStatement: MultiLine 32 | AfterEnum: true 33 | AfterFunction: true 34 | AfterNamespace: true 35 | AfterStruct: true 36 | AfterUnion: true 37 | AfterExternBlock: true 38 | BeforeCatch: false 39 | BeforeElse: false 40 | BeforeLambdaBody: true 41 | BeforeWhile: false 42 | IndentBraces: false 43 | SplitEmptyFunction: true 44 | SplitEmptyRecord: true 45 | SplitEmptyNamespace: true 46 | BreakBeforeBinaryOperators: NonAssignment 47 | BreakBeforeBraces: Custom 48 | BreakInheritanceList: BeforeComma 49 | BreakBeforeTernaryOperators: true 50 | BreakConstructorInitializersBeforeComma: true 51 | BreakConstructorInitializers: BeforeComma 52 | BreakAfterJavaFieldAnnotations: true 53 | BreakAfterAttributes: Always 54 | BreakStringLiterals: true 55 | ColumnLimit: 120 56 | CommentPragmas: "^ IWYU pragma:" 57 | CompactNamespaces: false 58 | ConstructorInitializerIndentWidth: 4 59 | ContinuationIndentWidth: 4 60 | Cpp11BracedListStyle: true 61 | DeriveLineEnding: false 62 | DerivePointerAlignment: false 63 | DisableFormat: false 64 | ExperimentalAutoDetectBinPacking: false 65 | FixNamespaceComments: true 66 | ForEachMacros: 67 | - foreach 68 | - Q_FOREACH 69 | - BOOST_FOREACH 70 | IncludeBlocks: Regroup 71 | IncludeCategories: 72 | # Standard library headers come before anything else 73 | - Regex: "^<[a-z_]+>" 74 | Priority: -1 75 | - Regex: '^<.+\.h(pp)?>' 76 | Priority: 1 77 | - Regex: "^<.*" 78 | Priority: 2 79 | - Regex: ".*" 80 | Priority: 3 81 | IncludeIsMainRegex: "" 82 | IncludeIsMainSourceRegex: "" 83 | IndentCaseLabels: true 84 | IndentCaseBlocks: false 85 | IndentGotoLabels: true 86 | IndentPPDirectives: AfterHash 87 | IndentExternBlock: NoIndent 88 | IndentWidth: 4 89 | IndentWrappedFunctionNames: false 90 | # InsertNewlineAtEOF: true # supported in clang-format 16 91 | InsertTrailingCommas: Wrapped 92 | JavaScriptQuotes: Double 93 | JavaScriptWrapImports: true 94 | KeepEmptyLinesAtTheStartOfBlocks: false 95 | MacroBlockBegin: "" 96 | MacroBlockEnd: "" 97 | MaxEmptyLinesToKeep: 1 98 | NamespaceIndentation: None 99 | PenaltyBreakAssignment: 2 100 | PenaltyBreakBeforeFirstCallParameter: 1 101 | PenaltyBreakComment: 300 102 | PenaltyBreakFirstLessLess: 120 103 | PenaltyBreakString: 1000 104 | PenaltyBreakTemplateDeclaration: 10 105 | PenaltyExcessCharacter: 1000000 106 | PenaltyReturnTypeOnItsOwnLine: 200 107 | PointerAlignment: Left 108 | QualifierAlignment: Custom 109 | QualifierOrder: ["inline", "constexpr", "static", "const", "type", "volatile"] 110 | RawStringFormats: 111 | - Language: Cpp 112 | Delimiters: 113 | - cc 114 | - CC 115 | - cpp 116 | - Cpp 117 | - CPP 118 | - "c++" 119 | - "C++" 120 | CanonicalDelimiter: "" 121 | BasedOnStyle: google 122 | - Language: TextProto 123 | Delimiters: 124 | - pb 125 | - PB 126 | - proto 127 | - PROTO 128 | EnclosingFunctions: 129 | - EqualsProto 130 | - EquivToProto 131 | - PARSE_PARTIAL_TEXT_PROTO 132 | - PARSE_TEST_PROTO 133 | - PARSE_TEXT_PROTO 134 | - ParseTextOrDie 135 | - ParseTextProtoOrDie 136 | - ParseTestProto 137 | - ParsePartialTestProto 138 | CanonicalDelimiter: "" 139 | BasedOnStyle: google 140 | ReflowComments: true 141 | SortIncludes: true 142 | SortUsingDeclarations: true 143 | SpaceAfterCStyleCast: false 144 | SpaceAfterLogicalNot: false 145 | SpaceAfterTemplateKeyword: false 146 | SpaceBeforeAssignmentOperators: true 147 | SpaceBeforeCpp11BracedList: true 148 | SpaceBeforeCtorInitializerColon: true 149 | SpaceBeforeInheritanceColon: true 150 | SpaceBeforeParens: ControlStatementsExceptForEachMacros 151 | SpaceBeforeRangeBasedForLoopColon: true 152 | SpaceInEmptyBlock: false 153 | SpaceInEmptyParentheses: false 154 | SpacesBeforeTrailingComments: 2 155 | SpacesInAngles: false 156 | SpacesInConditionalStatement: false 157 | SpacesInContainerLiterals: false 158 | SpacesInCStyleCastParentheses: false 159 | SpacesInParentheses: false 160 | SpacesInSquareBrackets: false 161 | SpaceBeforeSquareBrackets: false 162 | IndentRequiresClause: true 163 | InsertBraces: true 164 | BreakBeforeConceptDeclarations: Always 165 | Standard: Latest 166 | StatementMacros: 167 | - Q_UNUSED 168 | - QT_REQUIRE_VERSION 169 | TabWidth: 4 170 | UseCRLF: false 171 | UseTab: Never 172 | WhitespaceSensitiveMacros: 173 | - STRINGIZE 174 | - PP_STRINGIZE 175 | - BOOST_PP_STRINGIZE 176 | PackConstructorInitializers: Never 177 | --- 178 | Language: ObjC 179 | DisableFormat: true 180 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | # Enable ALL the things! Except not really 3 | # boost-use-ranges: We don't use boost 4 | # misc-non-private-member-variables-in-classes: the options don't do anything 5 | # modernize-use-nodiscard: too aggressive, attribute is situationally useful 6 | # cppcoreguidelines-pro-type-member-init, hicpp-member-init: We have a lot of types that expects us to use designated initializers. 7 | Checks: "*,\ 8 | -altera-*,\ 9 | -boost-use-ranges,\ 10 | -cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,\ 11 | -cppcoreguidelines-pro-type-member-init,-hicpp-member-init,\ 12 | -fuchsia-*,\ 13 | fuchsia-multiple-inheritance,\ 14 | -google-readability-todo,\ 15 | -llvm-header-guard,\ 16 | -llvm-include-order,\ 17 | -llvmlibc-*,\ 18 | -misc-non-private-member-variables-in-classes,\ 19 | -modernize-use-nodiscard,\ 20 | -readability-identifier-length,\ 21 | -readability-function-cognitive-complexity,\ 22 | " 23 | HeaderFilterRegex: "^.*/include/stronk" 24 | WarningsAsErrors: "" 25 | CheckOptions: 26 | - key: "bugprone-argument-comment.StrictMode" 27 | value: "true" 28 | # Prefer using enum classes with 2 values for parameters instead of bools 29 | - key: "bugprone-argument-comment.CommentBoolLiterals" 30 | value: "true" 31 | - key: "bugprone-misplaced-widening-cast.CheckImplicitCasts" 32 | value: "true" 33 | - key: "bugprone-sizeof-expression.WarnOnSizeOfIntegerExpression" 34 | value: "true" 35 | - key: "bugprone-suspicious-string-compare.WarnOnLogicalNotComparison" 36 | value: "true" 37 | - key: "readability-simplify-boolean-expr.ChainedConditionalReturn" 38 | value: "true" 39 | - key: "readability-simplify-boolean-expr.ChainedConditionalAssignment" 40 | value: "true" 41 | - key: "readability-uniqueptr-delete-release.PreferResetCall" 42 | value: "true" 43 | - key: "cppcoreguidelines-init-variables.MathHeader" 44 | value: "" 45 | - key: "cppcoreguidelines-narrowing-conversions.PedanticMode" 46 | value: "true" 47 | - key: "readability-else-after-return.WarnOnUnfixable" 48 | value: "true" 49 | - key: "readability-else-after-return.WarnOnConditionVariables" 50 | value: "true" 51 | - key: "readability-inconsistent-declaration-parameter-name.Strict" 52 | value: "true" 53 | - key: "readability-qualified-auto.AddConstToQualified" 54 | value: "true" 55 | - key: "readability-redundant-access-specifiers.CheckFirstDeclaration" 56 | value: "true" 57 | # These seem to be the most common identifier styles 58 | - key: "readability-identifier-naming.AbstractClassCase" 59 | value: "CamelCase" 60 | - key: "readability-identifier-naming.ClassCase" 61 | value: "CamelCase" 62 | - key: "readability-identifier-naming.ClassConstantCase" 63 | value: "lower_case" 64 | - key: "readability-identifier-naming.ClassMemberCase" 65 | value: "lower_case" 66 | - key: "readability-identifier-naming.ClassMethodCase" 67 | value: "lower_case" 68 | - key: "readability-identifier-naming.ConstantCase" 69 | value: "lower_case" 70 | - key: "readability-identifier-naming.ConstantMemberCase" 71 | value: "lower_case" 72 | - key: "readability-identifier-naming.ConstantParameterCase" 73 | value: "lower_case" 74 | - key: "readability-identifier-naming.ConstantPointerParameterCase" 75 | value: "lower_case" 76 | - key: "readability-identifier-naming.ConstexprFunctionCase" 77 | value: "lower_case" 78 | - key: "readability-identifier-naming.ConstexprMethodCase" 79 | value: "lower_case" 80 | - key: "readability-identifier-naming.ConstexprVariableCase" 81 | value: "lower_case" 82 | - key: "readability-identifier-naming.EnumCase" 83 | value: "CamelCase" 84 | - key: "readability-identifier-naming.EnumConstantCase" 85 | value: "UPPER_CASE" 86 | - key: "readability-identifier-naming.FunctionCase" 87 | value: "lower_case" 88 | - key: "readability-identifier-naming.FunctionIgnoredRegexp" 89 | value: "^AbslHashValue$" 90 | - key: "readability-identifier-naming.GlobalConstantCase" 91 | value: "lower_case" 92 | - key: "readability-identifier-naming.GlobalConstantPointerCase" 93 | value: "lower_case" 94 | - key: "readability-identifier-naming.GlobalFunctionCase" 95 | value: "lower_case" 96 | - key: "readability-identifier-naming.GlobalFunctionIgnoredRegexp" 97 | value: "^AbslHashValue$" 98 | - key: "readability-identifier-naming.GlobalPointerCase" 99 | value: "lower_case" 100 | - key: "readability-identifier-naming.GlobalVariableCase" 101 | value: "lower_case" 102 | - key: "readability-identifier-naming.InlineNamespaceCase" 103 | value: "lower_case" 104 | - key: "readability-identifier-naming.LocalConstantCase" 105 | value: "lower_case" 106 | - key: "readability-identifier-naming.LocalConstantPointerCase" 107 | value: "lower_case" 108 | - key: "readability-identifier-naming.LocalPointerCase" 109 | value: "lower_case" 110 | - key: "readability-identifier-naming.LocalVariableCase" 111 | value: "lower_case" 112 | - key: "readability-identifier-naming.LocalVariableIgnoredRegexp" 113 | value: ".*_" 114 | - key: "readability-identifier-naming.MacroDefinitionCase" 115 | value: "UPPER_CASE" 116 | - key: "readability-identifier-naming.MemberCase" 117 | value: "lower_case" 118 | - key: "readability-identifier-naming.MethodCase" 119 | value: "lower_case" 120 | - key: "readability-identifier-naming.NamespaceCase" 121 | value: "lower_case" 122 | - key: "readability-identifier-naming.ParameterCase" 123 | value: "lower_case" 124 | - key: "readability-identifier-naming.ParameterIgnoredRegexp" 125 | value: ".*_" 126 | - key: "readability-identifier-naming.ParameterPackCase" 127 | value: "lower_case" 128 | - key: "readability-identifier-naming.PointerParameterCase" 129 | value: "lower_case" 130 | - key: "readability-identifier-naming.PrivateMemberCase" 131 | value: "lower_case" 132 | - key: "readability-identifier-naming.PrivateMemberPrefix" 133 | value: "_" 134 | - key: "readability-identifier-naming.PrivateMethodCase" 135 | value: "lower_case" 136 | - key: "readability-identifier-naming.ProtectedMemberCase" 137 | value: "lower_case" 138 | - key: "readability-identifier-naming.ProtectedMemberPrefix" 139 | value: "_" 140 | - key: "readability-identifier-naming.ProtectedMethodCase" 141 | value: "lower_case" 142 | - key: "readability-identifier-naming.PublicMemberCase" 143 | value: "lower_case" 144 | - key: "readability-identifier-naming.PublicMethodCase" 145 | value: "lower_case" 146 | - key: "readability-identifier-naming.ScopedEnumConstantCase" 147 | value: "UPPER_CASE" 148 | - key: "readability-identifier-naming.StaticConstantCase" 149 | value: "lower_case" 150 | - key: "readability-identifier-naming.StaticVariableCase" 151 | value: "lower_case" 152 | - key: "readability-identifier-naming.StructCase" 153 | value: "lower_case" 154 | - key: "readability-identifier-naming.TemplateParameterCase" 155 | value: "CamelCase" 156 | - key: "readability-identifier-naming.TemplateTemplateParameterCase" 157 | value: "CamelCase" 158 | - key: "readability-identifier-naming.TypedefCase" 159 | value: "lower_case" 160 | - key: "readability-identifier-naming.TypeTemplateParameterCase" 161 | value: "CamelCase" 162 | - key: "readability-identifier-naming.UnionCase" 163 | value: "lower_case" 164 | - key: "readability-identifier-naming.ValueTemplateParameterCase" 165 | value: "CamelCase" 166 | - key: "readability-identifier-naming.VariableCase" 167 | value: "lower_case" 168 | - key: "readability-identifier-naming.VirtualMethodCase" 169 | value: "lower_case" 170 | - key: "optin.performance.Padding.AllowedPad" 171 | value: "8" 172 | - key: "misc-include-cleaner.IgnoreHeaders" 173 | value: "\ 174 | fmt/std.h;\ 175 | twig/utilities/absl_formatters.hpp;\ 176 | " 177 | -------------------------------------------------------------------------------- /.codespellrc: -------------------------------------------------------------------------------- 1 | [codespell] 2 | builtin = clear,rare,en-GB_to_en-US,names,informal,code 3 | check-filenames = 4 | check-hidden = 5 | skip = */.git,*/build,*/prefix,*/.vscode,*/.sonar,*/llvm,*/.github,*/.clang-tidy 6 | quiet-level = 2 7 | ignore-words-list = deque,dur,lightyears 8 | -------------------------------------------------------------------------------- /.github/actions/install-clang/action.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Install LLVM and Clang" 3 | description: "Installs LLVM and Clang on CI runners" 4 | inputs: 5 | version: 6 | description: "Which version of LLVM to install" 7 | required: true 8 | runs: 9 | using: "composite" 10 | steps: 11 | - name: Install LLVM and Clang 12 | if: ${{ runner.environment != 'self-hosted' }} 13 | shell: bash 14 | run: | 15 | # if clang-version is already installed return 16 | if which clang-${{ inputs.version }} > /dev/null; then 17 | exit 0 18 | fi 19 | 20 | wget https://apt.llvm.org/llvm.sh 21 | chmod +x llvm.sh 22 | sudo ./llvm.sh ${{ inputs.version }} 23 | rm llvm.sh 24 | sudo apt-get install -y clang-tidy-${{ inputs.version }} clang-format-${{ inputs.version }} 25 | -------------------------------------------------------------------------------- /.github/actions/install-gcc/action.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Install GCC" 3 | description: "Installs GCC on CI runners" 4 | inputs: 5 | version: 6 | description: "Which version of GCC to install. E.g. '13' or 'gcc-13'" 7 | required: true 8 | runs: 9 | using: "composite" 10 | steps: 11 | - name: Install GCC 12 | if: ${{ runner.environment != 'self-hosted' }} 13 | shell: bash 14 | run: | 15 | GCC_VERSION=`echo ${{ inputs.version }} | cut -d'-' -f2` 16 | 17 | if ! apt-cache show "gcc-$GCC_VERSION" > /dev/null; then 18 | # Add test repository if gcc version is not available. 19 | sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y 20 | sudo apt-get update -q 21 | fi 22 | 23 | sudo apt-get install gcc-$GCC_VERSION g++-$GCC_VERSION g++-$GCC_VERSION-multilib -y -q 24 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.ref }} 5 | cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} 6 | 7 | on: 8 | push: 9 | branches: 10 | - main 11 | 12 | pull_request: 13 | branches: 14 | - main 15 | 16 | workflow_dispatch: 17 | 18 | env: 19 | VCPKG_COMMIT: "d5ec528843d29e3a52d745a64b469f810b2cedbf" 20 | 21 | jobs: 22 | version_bump: 23 | runs-on: ubuntu-24.04 24 | steps: 25 | - uses: actions/checkout@v4 26 | with: 27 | fetch-depth: 0 28 | 29 | - name: Test version bumped 30 | if: github.ref != 'refs/heads/main' 31 | run: echo "$(git diff HEAD origin/main version.py)" | grep __version__ -wq 32 | 33 | lint: 34 | runs-on: ubuntu-24.04 35 | 36 | steps: 37 | - uses: actions/checkout@v4 38 | 39 | - uses: actions/setup-python@v5 40 | with: { python-version: "3.12" } 41 | 42 | - name: Install codespell 43 | run: pip3 install codespell 44 | 45 | - name: Install LLVM and Clang 46 | uses: ./.github/actions/install-clang 47 | with: 48 | version: "19" 49 | 50 | - name: Lint 51 | run: cmake -D FORMAT_COMMAND=clang-format-19 -P cmake/lint.cmake 52 | 53 | - name: Spell check 54 | if: always() 55 | run: cmake -P cmake/spell.cmake 56 | 57 | - name: Dependency Setup Check 58 | run: python3 ./tools/check_install_packages_match.py 59 | 60 | sonar: 61 | needs: lint 62 | if: ${{ !cancelled() && (needs.lint.result == 'success' || needs.lint.result == 'skipped') }} # Don't skip on main build, even though lint is skipped. 63 | 64 | runs-on: ubuntu-24.04 65 | env: 66 | BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory 67 | 68 | steps: 69 | - uses: actions/checkout@v4 70 | with: 71 | fetch-depth: 0 72 | lfs: true 73 | 74 | - name: Install LLVM and Clang 75 | uses: ./.github/actions/install-clang 76 | with: 77 | version: "19" 78 | 79 | - name: Install Build Wrapper 80 | uses: SonarSource/sonarqube-scan-action/install-build-wrapper@v4 81 | 82 | - name: Install vcpkg 83 | uses: friendlyanon/setup-vcpkg@v1 84 | with: 85 | committish: "${{ env.VCPKG_COMMIT }}" 86 | cache-version: "vcpkg-sonar" 87 | 88 | - name: Configure 89 | run: | 90 | cmake --preset=ci-coverage 91 | 92 | - name: Build with build-wrapper 93 | run: | 94 | build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} cmake --build build -j 4 95 | 96 | - name: Test and code coverage 97 | timeout-minutes: 10 98 | run: cmake --build build -t ccov-all -j 4 99 | 100 | - name: Run sonar-scanner 101 | uses: SonarSource/sonarqube-scan-action@v4 102 | env: 103 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 104 | with: 105 | args: > 106 | --define sonar.cfamily.compile-commands="${{ env.BUILD_WRAPPER_OUT_DIR }}/compile_commands.json" 107 | 108 | - name: clang-tidy 109 | run: | 110 | python3 -m venv .venv && source .venv/bin/activate 111 | pip3 install clangd-tidy 112 | if [ "${{ github.ref }}" = "refs/heads/main" ]; then 113 | bash ./tools/run_clangd_tidy_on_changed.sh all # all files 114 | else 115 | bash ./tools/run_clangd_tidy_on_changed.sh # only changed files 116 | fi 117 | 118 | - name: Run include what you use check. 119 | run: ./tools/check_include_what_you_use.sh # if this fails you can run the script locally. 120 | 121 | sanitize: 122 | needs: [lint] 123 | 124 | runs-on: ubuntu-24.04 125 | 126 | steps: 127 | - uses: actions/checkout@v4 128 | 129 | - name: Install LLVM and Clang 130 | uses: ./.github/actions/install-clang 131 | with: 132 | version: "19" 133 | 134 | - name: Install vcpkg 135 | uses: friendlyanon/setup-vcpkg@v1 136 | with: 137 | committish: "${{ env.VCPKG_COMMIT }}" 138 | cache-version: "vcpkg-sanitize" 139 | 140 | - name: Configure 141 | run: cmake --preset=ci-sanitize 142 | 143 | - name: Build 144 | run: cmake --build build -j 2 145 | 146 | - name: Test 147 | run: ctest --preset=sanitize -j 2 148 | 149 | test: 150 | needs: [lint] 151 | 152 | strategy: 153 | matrix: 154 | os: [ubuntu-24.04] 155 | compiler: [gcc-11, gcc-12, gcc-13, gcc-14, clang-18, clang-19] 156 | include: 157 | - os: windows-2022 158 | compiler: msvc 159 | - os: macos-13 160 | compiler: gcc-11 161 | - os: macos-13 162 | compiler: gcc-12 163 | 164 | runs-on: ${{ matrix.os }} 165 | 166 | steps: 167 | - uses: actions/checkout@v4 168 | 169 | - name: Extract compiler version 170 | if: matrix.os == 'ubuntu-24.04' 171 | id: extract-version 172 | run: | 173 | COMPILER_VERSION=$(echo "${{ matrix.compiler }}" | cut -d'-' -f2) 174 | echo "COMPILER_VERSION=$COMPILER_VERSION" >> $GITHUB_OUTPUT 175 | 176 | - name: Install LLVM and Clang 177 | if: matrix.os == 'ubuntu-24.04' && contains(matrix.compiler, 'clang') 178 | uses: ./.github/actions/install-clang 179 | with: 180 | version: ${{ steps.extract-version.outputs.COMPILER_VERSION }} 181 | 182 | - name: Install GCC 183 | if: matrix.os == 'ubuntu-24.04' && contains(matrix.compiler, 'gcc') 184 | uses: ./.github/actions/install-gcc 185 | with: 186 | version: ${{ steps.extract-version.outputs.COMPILER_VERSION }} 187 | 188 | - name: Install vcpkg 189 | uses: friendlyanon/setup-vcpkg@v1 190 | with: 191 | committish: "${{ env.VCPKG_COMMIT }}" 192 | cache-version: "vcpkg-${{ matrix.os }}-${{ matrix.compiler }}" 193 | 194 | - name: Configure 195 | shell: pwsh 196 | run: cmake "--preset=ci-$("${{ matrix.os }}".split("-")[0])-${{ matrix.compiler }}" 197 | 198 | - name: Build 199 | run: cmake --build build --config Release -j 2 200 | 201 | - name: Install 202 | run: cmake --install build --config Release --prefix prefix 203 | 204 | - name: Test 205 | run: ctest --preset=default -C Release -j 2 206 | 207 | - name: Benchmarks 208 | if: ${{ !contains(matrix.os, 'windows') }} 209 | run: ${{ github.workspace }}/build/benchmarks/stronk_benchmarks 210 | 211 | - name: Benchmarks-windows 212 | if: ${{ contains(matrix.os, 'windows') }} 213 | run: | 214 | ${{ github.workspace }}/build/benchmarks/Release/stronk_benchmarks 215 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: ["main"] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: ["main"] 20 | schedule: 21 | - cron: "44 8 * * 1" 22 | env: 23 | VCPKG_COMMIT: "d5ec528843d29e3a52d745a64b469f810b2cedbf" 24 | 25 | jobs: 26 | analyze: 27 | name: Analyze 28 | runs-on: ubuntu-24.04 29 | permissions: 30 | actions: read 31 | contents: read 32 | security-events: write 33 | 34 | strategy: 35 | fail-fast: false 36 | matrix: 37 | language: ["cpp"] 38 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 39 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 40 | 41 | steps: 42 | - name: Checkout repository 43 | uses: actions/checkout@v4 44 | 45 | # Initializes the CodeQL tools for scanning. 46 | - name: Initialize CodeQL 47 | uses: github/codeql-action/init@v3 48 | with: 49 | languages: ${{ matrix.language }} 50 | # If you wish to specify custom queries, you can do so here or in a config file. 51 | # By default, queries listed here will override any specified in a config file. 52 | # Prefix the list here with "+" to use these queries and those in the config file. 53 | 54 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 55 | # queries: security-extended,security-and-quality 56 | 57 | - name: Install vcpkg 58 | uses: friendlyanon/setup-vcpkg@v1 59 | with: 60 | committish: "${{ env.VCPKG_COMMIT }}" 61 | cache-version: "vcpkg-codeql" 62 | 63 | - uses: ./.github/actions/install-gcc 64 | with: 65 | version: "14" 66 | 67 | - name: Build 68 | run: | 69 | cmake --preset=ci-codeql 70 | cmake --build build --config Release -j 2 71 | cmake --install build --prefix bin 72 | 73 | - name: Perform CodeQL Analysis 74 | uses: github/codeql-action/analyze@v3 75 | -------------------------------------------------------------------------------- /.github/workflows/update_readme.yml: -------------------------------------------------------------------------------- 1 | name: Embed examples into README 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | auto-update-readme: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | ref: ${{ github.head_ref }} 15 | 16 | - name: Update Readme 17 | run: python tools/embed_code.py -i --file ./README.md 18 | 19 | - uses: stefanzweifel/git-auto-commit-action@v5 20 | with: 21 | commit_message: Updated README.md 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vs/ 3 | .vscode/ 4 | build/ 5 | cmake/open-cpp-coverage.cmake 6 | cmake-build-*/ 7 | prefix/ 8 | CMakeLists.txt.user 9 | CMakeUserPresets.json 10 | install/ 11 | bin/ 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "clangd.path": "clangd-19", 3 | "clangd.arguments": [ 4 | "-compile-commands-dir=${workspaceFolder}/build" 5 | ], 6 | "clang-format.executable": "clang-format-19", 7 | "files.associations": { 8 | "cctype": "cpp", 9 | "clocale": "cpp", 10 | "cmath": "cpp", 11 | "cstdarg": "cpp", 12 | "cstddef": "cpp", 13 | "cstdio": "cpp", 14 | "cstdlib": "cpp", 15 | "cstring": "cpp", 16 | "ctime": "cpp", 17 | "cwchar": "cpp", 18 | "cwctype": "cpp", 19 | "any": "cpp", 20 | "array": "cpp", 21 | "atomic": "cpp", 22 | "bit": "cpp", 23 | "*.tcc": "cpp", 24 | "bitset": "cpp", 25 | "chrono": "cpp", 26 | "codecvt": "cpp", 27 | "compare": "cpp", 28 | "complex": "cpp", 29 | "concepts": "cpp", 30 | "condition_variable": "cpp", 31 | "cstdint": "cpp", 32 | "deque": "cpp", 33 | "list": "cpp", 34 | "map": "cpp", 35 | "set": "cpp", 36 | "string": "cpp", 37 | "unordered_map": "cpp", 38 | "vector": "cpp", 39 | "exception": "cpp", 40 | "algorithm": "cpp", 41 | "functional": "cpp", 42 | "iterator": "cpp", 43 | "memory": "cpp", 44 | "memory_resource": "cpp", 45 | "numeric": "cpp", 46 | "optional": "cpp", 47 | "random": "cpp", 48 | "ratio": "cpp", 49 | "source_location": "cpp", 50 | "string_view": "cpp", 51 | "system_error": "cpp", 52 | "tuple": "cpp", 53 | "type_traits": "cpp", 54 | "utility": "cpp", 55 | "fstream": "cpp", 56 | "initializer_list": "cpp", 57 | "iomanip": "cpp", 58 | "iosfwd": "cpp", 59 | "iostream": "cpp", 60 | "istream": "cpp", 61 | "limits": "cpp", 62 | "mutex": "cpp", 63 | "new": "cpp", 64 | "numbers": "cpp", 65 | "ostream": "cpp", 66 | "semaphore": "cpp", 67 | "sstream": "cpp", 68 | "stdexcept": "cpp", 69 | "stop_token": "cpp", 70 | "streambuf": "cpp", 71 | "thread": "cpp", 72 | "cfenv": "cpp", 73 | "cinttypes": "cpp", 74 | "typeindex": "cpp", 75 | "typeinfo": "cpp", 76 | "variant": "cpp", 77 | "forward_list": "cpp", 78 | "unordered_set": "cpp", 79 | "shared_mutex": "cpp", 80 | "*.inc": "cpp", 81 | "__hash_table": "cpp", 82 | "__bit_reference": "cpp", 83 | "__bits": "cpp", 84 | "__config": "cpp", 85 | "__debug": "cpp", 86 | "__locale": "cpp", 87 | "__node_handle": "cpp", 88 | "__nullptr": "cpp", 89 | "__split_buffer": "cpp", 90 | "__string": "cpp", 91 | "__threading_support": "cpp", 92 | "__tuple": "cpp", 93 | "ios": "cpp", 94 | "locale": "cpp", 95 | "__tree": "cpp", 96 | "__errc": "cpp" 97 | }, 98 | "cSpell.words": [ 99 | "absl", 100 | "consteval", 101 | "decltype", 102 | "sonarcloud", 103 | "stronk", 104 | "structs", 105 | "VCPKG" 106 | ], 107 | "sonarlint.pathToCompileCommands": "${workspaceFolder}/build/compile_commands.json", 108 | "sonarlint.connectedMode.project": { 109 | "connectionId": "twig-energy", 110 | "projectKey": "twig-energy_stronk" 111 | }, 112 | "sarif-viewer.connectToGithubCodeScanning": "off" 113 | } 114 | -------------------------------------------------------------------------------- /BUILDING.md: -------------------------------------------------------------------------------- 1 | # Building and installing with CMake 2 | 3 | ## Dependencies 4 | 5 | For a list of dependencies, please refer to [vcpkg.json](vcpkg.json). 6 | 7 | ## Build and installing 8 | Make sure you have at least CMake 3.15 installed 9 | 10 | ### Build on Unix 11 | building in release mode with a single-configuration 12 | generator, like the Unix Makefiles one: 13 | 14 | ```sh 15 | cmake -S . -B build -D CMAKE_BUILD_TYPE=Release 16 | cmake --build build 17 | ``` 18 | 19 | Install the release mode artifacts 20 | ```sh 21 | cmake --install build 22 | ``` 23 | 24 | ### Build using Visual studio 25 | 26 | 27 | Building in release mode with a multi-configuration 28 | generator, like the Visual Studio ones: 29 | 30 | ```sh 31 | cmake -S . -B build 32 | cmake --build build --config Release 33 | ``` 34 | 35 | Install the release mode artifacts 36 | ```sh 37 | cmake --install build --config Release 38 | ``` 39 | 40 | ### Building on Apple Silicon 41 | 42 | CMake supports building on Apple Silicon properly since 3.20.1. Make sure you 43 | have the [latest version][1] installed and follow the Unix build and installation instruction. 44 | 45 | 46 | ### Building with MSVC 47 | 48 | Note that MSVC by default is not standards compliant and you need to pass some 49 | flags to make it behave properly. See the `flags-windows` preset in the 50 | [CMakePresets.json](CMakePresets.json) file for the flags and with what 51 | variable to provide them to CMake during configuration. 52 | 53 | ## CMake package 54 | 55 | This project exports a CMake package to be used with the [`find_package`][3] 56 | command of CMake: 57 | 58 | * Package name: `stronk` 59 | * Target name: `twig::stronk` 60 | 61 | Example usage: 62 | 63 | ```cmake 64 | find_package(stronk REQUIRED) 65 | # Declare the imported target as a build requirement using PRIVATE, where 66 | # project_target is a target created in the consuming project 67 | target_link_libraries( 68 | project_target PRIVATE 69 | twig::stronk 70 | ) 71 | ``` 72 | 73 | [1]: https://cmake.org/download/ 74 | [2]: https://cmake.org/cmake/help/latest/manual/cmake.1.html#install-a-project 75 | [3]: https://cmake.org/cmake/help/latest/command/find_package.html 76 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.28) 2 | 3 | include(cmake/prelude.cmake) 4 | 5 | file(READ "${CMAKE_CURRENT_LIST_DIR}/version.py" version_line) 6 | string( 7 | REGEX MATCH 8 | "__version__ = \"(.*)\"" 9 | _ 10 | ${version_line} 11 | ) 12 | set(version ${CMAKE_MATCH_1}) 13 | 14 | project( 15 | stronk 16 | VERSION ${version} 17 | DESCRIPTION "An easy to customize strong type library with built in support for unit-like behavior" 18 | HOMEPAGE_URL "https://github.com/twig-energy/stronk" 19 | LANGUAGES CXX 20 | ) 21 | 22 | include(cmake/project-is-top-level.cmake) 23 | include(cmake/variables.cmake) 24 | include(cmake/dev-mode.cmake) 25 | 26 | find_package(boost_type_index CONFIG REQUIRED) 27 | 28 | # ---- Declare library ---- 29 | add_library(twig_stronk INTERFACE) 30 | add_library(twig::stronk ALIAS twig_stronk) 31 | 32 | set_property(TARGET twig_stronk PROPERTY EXPORT_NAME stronk) 33 | 34 | target_include_directories(twig_stronk ${warning_guard} INTERFACE "$") 35 | 36 | target_compile_features(twig_stronk INTERFACE cxx_std_20) 37 | 38 | target_link_libraries(twig_stronk INTERFACE Boost::type_index) 39 | 40 | # ---- Install rules ---- 41 | if (NOT CMAKE_SKIP_INSTALL_RULES) 42 | include(cmake/install-rules.cmake) 43 | endif () 44 | 45 | if (PROJECT_IS_TOP_LEVEL) 46 | option(BUILD_TESTING "Build tests tree." "${stronk_DEVELOPER_MODE}") 47 | if (BUILD_TESTING) 48 | add_subdirectory(tests) 49 | endif () 50 | 51 | option(BUILD_EXAMPLES "Build examples tree." "${stronk_DEVELOPER_MODE}") 52 | if (BUILD_EXAMPLES) 53 | add_subdirectory(examples) 54 | endif () 55 | 56 | option(BUILD_BENCHMARKS "Build benchmarks tree." "${stronk_DEVELOPER_MODE}") 57 | if (BUILD_BENCHMARKS) 58 | add_subdirectory(benchmarks) 59 | endif () 60 | endif () 61 | 62 | # ---- Developer mode ---- 63 | if (NOT stronk_DEVELOPER_MODE) 64 | return() 65 | elseif (NOT PROJECT_IS_TOP_LEVEL) 66 | message(AUTHOR_WARNING "Developer mode is intended for developers of stronk") 67 | endif () 68 | 69 | # ---- Code coverage ---- 70 | if (ENABLE_COVERAGE) 71 | target_code_coverage(twig_stronk INTERFACE ALL) 72 | endif () 73 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 28, 6 | "patch": 0 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "default", 11 | "binaryDir": "${sourceDir}/build" 12 | }, 13 | { 14 | "name": "dev-mode", 15 | "inherits": "default", 16 | "hidden": true, 17 | "cacheVariables": { 18 | "stronk_DEVELOPER_MODE": "ON", 19 | "VCPKG_MANIFEST_FEATURES": "test;fmt;abseil;doctest;nlohmann-json;glaze;benchmark" 20 | } 21 | }, 22 | { 23 | "name": "vcpkg", 24 | "hidden": true, 25 | "cacheVariables": { 26 | "CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" 27 | } 28 | }, 29 | { 30 | "name": "vcpkg-win64-static", 31 | "hidden": true, 32 | "cacheVariables": { 33 | "VCPKG_TARGET_TRIPLET": "x64-windows-static-md" 34 | } 35 | }, 36 | { 37 | "name": "clang-tidy", 38 | "hidden": true, 39 | "cacheVariables": { 40 | "CMAKE_CXX_CLANG_TIDY": "clang-tidy-17;--header-filter=^${sourceDir}/(include|tests|benchmarks)" 41 | } 42 | }, 43 | { 44 | "name": "clang-18", 45 | "hidden": true, 46 | "environment": { 47 | "CXX": "clang++-18", 48 | "CC": "clang-18" 49 | }, 50 | "cacheVariables": { 51 | "LLVM_SYMBOLIZER_PATH": "llvm-symbolizer-18", 52 | "LLVM_COV_COMMAND": "llvm-cov-18", 53 | "LLVM_PROFDATA_COMMAND": "llvm-profdata-18" 54 | } 55 | }, 56 | { 57 | "name": "clang-19", 58 | "hidden": true, 59 | "environment": { 60 | "CXX": "clang++-19", 61 | "CC": "clang-19" 62 | }, 63 | "cacheVariables": { 64 | "LLVM_SYMBOLIZER_PATH": "llvm-symbolizer-19", 65 | "LLVM_COV_COMMAND": "llvm-cov-19", 66 | "LLVM_PROFDATA_COMMAND": "llvm-profdata-19" 67 | } 68 | }, 69 | { 70 | "name": "gcc-11", 71 | "hidden": true, 72 | "environment": { 73 | "CXX": "g++-11", 74 | "CC": "gcc-11" 75 | } 76 | }, 77 | { 78 | "name": "gcc-12", 79 | "hidden": true, 80 | "environment": { 81 | "CXX": "g++-12", 82 | "CC": "gcc-12" 83 | } 84 | }, 85 | { 86 | "name": "gcc-13", 87 | "hidden": true, 88 | "environment": { 89 | "CXX": "g++-13", 90 | "CC": "gcc-13" 91 | } 92 | }, 93 | { 94 | "name": "gcc-14", 95 | "hidden": true, 96 | "environment": { 97 | "CXX": "g++-14", 98 | "CC": "gcc-14" 99 | } 100 | }, 101 | { 102 | "name": "msvc", 103 | "hidden": true 104 | }, 105 | { 106 | "name": "ci-std", 107 | "description": "This preset makes sure the project actually builds with at least the specified standard", 108 | "hidden": true, 109 | "cacheVariables": { 110 | "CMAKE_CXX_EXTENSIONS": "OFF", 111 | "CMAKE_CXX_STANDARD": "20", 112 | "CMAKE_CXX_STANDARD_REQUIRED": "ON" 113 | } 114 | }, 115 | { 116 | "name": "flags-unix", 117 | "hidden": true, 118 | "cacheVariables": { 119 | "CMAKE_CXX_FLAGS": "-Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wcast-qual -Wshadow -Wformat=2 -Wundef -Werror=float-equal -Wno-gnu-zero-variadic-macro-arguments" 120 | } 121 | }, 122 | { 123 | "name": "flags-windows", 124 | "description": "Note that all the flags after /W4 are required for MSVC to conform to the language standard", 125 | "hidden": true, 126 | "cacheVariables": { 127 | "CMAKE_CXX_FLAGS": "/utf-8 /W4 /permissive- /volatile:iso /Zc:preprocessor /EHsc /Zc:__cplusplus /Zc:externConstexpr /Zc:throwingNew" 128 | } 129 | }, 130 | { 131 | "name": "ci-unix", 132 | "generator": "Unix Makefiles", 133 | "hidden": true, 134 | "inherits": [ 135 | "flags-unix", 136 | "ci-std" 137 | ], 138 | "cacheVariables": { 139 | "CMAKE_BUILD_TYPE": "Release" 140 | } 141 | }, 142 | { 143 | "name": "ci-win64", 144 | "inherits": [ 145 | "flags-windows", 146 | "ci-std" 147 | ], 148 | "generator": "Visual Studio 17 2022", 149 | "architecture": "x64", 150 | "hidden": true 151 | }, 152 | { 153 | "name": "coverage-linux", 154 | "inherits": [ 155 | "clang-19", 156 | "dev-mode", 157 | "ci-unix" 158 | ], 159 | "hidden": true, 160 | "cacheVariables": { 161 | "ENABLE_COVERAGE": "ON", 162 | "CODE_COVERAGE": "ON", 163 | "CMAKE_BUILD_TYPE": "Coverage", 164 | "CMAKE_CXX_FLAGS_COVERAGE": "-Og -g -fprofile-instr-generate -fcoverage-mapping", 165 | "CMAKE_EXE_LINKER_FLAGS_COVERAGE": "-fprofile-instr-generate -fcoverage-mapping", 166 | "CMAKE_SHARED_LINKER_FLAGS_COVERAGE": "-fprofile-instr-generate -fcoverage-mapping", 167 | "CMAKE_MAP_IMPORTED_CONFIG_COVERAGE": "Coverage;RelWithDebInfo;Release;Debug;", 168 | "CMAKE_EXPORT_COMPILE_COMMANDS": "ON" 169 | } 170 | }, 171 | { 172 | "name": "ci-coverage", 173 | "inherits": [ 174 | "ci-build", 175 | "coverage-linux", 176 | "vcpkg" 177 | ], 178 | "cacheVariables": { 179 | "CMAKE_COVERAGE_OUTPUT_DIRECTORY": "${sourceDir}/build/ccov" 180 | } 181 | }, 182 | { 183 | "name": "sanitize-unix", 184 | "inherits": [ 185 | "ci-unix" 186 | ], 187 | "cacheVariables": { 188 | "CMAKE_BUILD_TYPE": "Sanitize", 189 | "CMAKE_CXX_FLAGS_SANITIZE": "-O2 -g -fsanitize=address,undefined -fno-omit-frame-pointer -fno-common", 190 | "CMAKE_MAP_IMPORTED_CONFIG_SANITIZE": "Sanitize;RelWithDebInfo;Release;Debug;" 191 | } 192 | }, 193 | { 194 | "name": "ci-sanitize", 195 | "inherits": [ 196 | "ci-unix", 197 | "sanitize-unix", 198 | "dev-mode", 199 | "vcpkg", 200 | "clang-19" 201 | ] 202 | }, 203 | { 204 | "name": "ci-build", 205 | "hidden": true 206 | }, 207 | { 208 | "name": "ci-macos-gcc-11", 209 | "inherits": [ 210 | "ci-build", 211 | "ci-unix", 212 | "dev-mode", 213 | "vcpkg", 214 | "gcc-14" 215 | ] 216 | }, 217 | { 218 | "name": "ci-macos-gcc-12", 219 | "inherits": [ 220 | "ci-build", 221 | "ci-unix", 222 | "dev-mode", 223 | "vcpkg", 224 | "gcc-12" 225 | ] 226 | }, 227 | { 228 | "name": "ci-windows-msvc", 229 | "inherits": [ 230 | "ci-build", 231 | "ci-win64", 232 | "dev-mode", 233 | "vcpkg", 234 | "vcpkg-win64-static", 235 | "msvc" 236 | ] 237 | }, 238 | { 239 | "name": "ci-ubuntu", 240 | "inherits": [ 241 | "ci-build", 242 | "ci-unix", 243 | "vcpkg", 244 | "dev-mode" 245 | ] 246 | }, 247 | { 248 | "name": "ci-ubuntu-gcc-11", 249 | "inherits": [ 250 | "ci-ubuntu", 251 | "gcc-11" 252 | ] 253 | }, 254 | { 255 | "name": "ci-ubuntu-gcc-12", 256 | "inherits": [ 257 | "ci-ubuntu", 258 | "gcc-12" 259 | ] 260 | }, 261 | { 262 | "name": "ci-ubuntu-gcc-13", 263 | "inherits": [ 264 | "ci-ubuntu", 265 | "gcc-13" 266 | ] 267 | }, 268 | { 269 | "name": "ci-ubuntu-gcc-14", 270 | "inherits": [ 271 | "ci-ubuntu", 272 | "gcc-14" 273 | ] 274 | }, 275 | { 276 | "name": "ci-ubuntu-clang-18", 277 | "inherits": [ 278 | "ci-ubuntu", 279 | "clang-18" 280 | ] 281 | }, 282 | { 283 | "name": "ci-ubuntu-clang-19", 284 | "inherits": [ 285 | "ci-ubuntu", 286 | "clang-19" 287 | ] 288 | }, 289 | { 290 | "name": "ci-codeql", 291 | "inherits": [ 292 | "ci-build", 293 | "ci-unix", 294 | "vcpkg", 295 | "dev-mode", 296 | "gcc-14" 297 | ] 298 | } 299 | ], 300 | "testPresets": [ 301 | { 302 | "name": "default", 303 | "configurePreset": "default", 304 | "output": { 305 | "outputOnFailure": true 306 | }, 307 | "execution": { 308 | "noTestsAction": "error", 309 | "stopOnFailure": true 310 | } 311 | }, 312 | { 313 | "name": "sanitize", 314 | "inherits": "default", 315 | "environment": { 316 | "ASAN_OPTIONS": "strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1:detect_leaks=1", 317 | "UBSAN_OPTIONS": "print_stacktrace=1" 318 | } 319 | }, 320 | { 321 | "name": "sanitize-thread", 322 | "inherits": "default", 323 | "environment": { 324 | "TSAN_OPTIONS": "suppressions=${sourceDir}/sanitize-thread-suppressions.txt" 325 | } 326 | } 327 | ] 328 | } 329 | -------------------------------------------------------------------------------- /CMakeUserPresets.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 28, 6 | "patch": 0 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "dev-common", 11 | "hidden": true, 12 | "inherits": [ 13 | "dev-mode", 14 | "vcpkg" 15 | ], 16 | "cacheVariables": {}, 17 | "environment": { 18 | "VCPKG_ROOT": "path/to/vcpkg" 19 | } 20 | }, 21 | { 22 | "name": "dev-linux", 23 | "inherits": [ 24 | "dev-common", 25 | "ci-unix", 26 | "clang-19" 27 | ], 28 | "cacheVariables": { 29 | "CMAKE_BUILD_TYPE": "Debug", 30 | "CMAKE_EXPORT_COMPILE_COMMANDS": "ON" 31 | } 32 | }, 33 | { 34 | "name": "dev-win64", 35 | "inherits": [ 36 | "dev-common", 37 | "ci-win64", 38 | "vcpkg-win64-static" 39 | ], 40 | "environment": { 41 | "UseMultiToolTask": "true", 42 | "EnforceProcessCountAcrossBuilds": "true" 43 | } 44 | }, 45 | { 46 | "name": "dev", 47 | "inherits": "dev-linux" 48 | }, 49 | { 50 | "name": "dev-coverage", 51 | "inherits": [ 52 | "dev-linux", 53 | "coverage-linux", 54 | "vcpkg" 55 | ] 56 | }, 57 | { 58 | "name": "dev-sanitize", 59 | "inherits": [ 60 | "dev-linux", 61 | "sanitize-unix", 62 | "vcpkg" 63 | ] 64 | } 65 | ], 66 | "buildPresets": [ 67 | { 68 | "name": "dev", 69 | "configurePreset": "dev", 70 | "configuration": "Debug", 71 | "jobs": 48 72 | } 73 | ], 74 | "testPresets": [ 75 | { 76 | "name": "dev", 77 | "configurePreset": "dev", 78 | "configuration": "Debug", 79 | "output": { 80 | "outputOnFailure": true 81 | }, 82 | "execution": { 83 | "jobs": 48, 84 | "noTestsAction": "error" 85 | } 86 | } 87 | ] 88 | } 89 | -------------------------------------------------------------------------------- /HACKING.md: -------------------------------------------------------------------------------- 1 | # Hacking 2 | 3 | Here is some wisdom to help you build and test this project as a developer and 4 | potential contributor. 5 | 6 | If you plan to contribute, please read the [CONTRIBUTING](CONTRIBUTING.md) 7 | guide. 8 | 9 | ## Developer mode 10 | 11 | Build system targets that are only useful for developers of this project are 12 | hidden if the `stronk_DEVELOPER_MODE` option is disabled. Enabling this 13 | option makes tests and other developer targets and options available. Not 14 | enabling this option means that you are a consumer of this project and thus you 15 | have no need for these targets and options. 16 | 17 | Developer mode is always set to on in CI workflows. 18 | 19 | ### Presets 20 | 21 | This project makes use of [presets][1] to simplify the process of configuring 22 | the project. As a developer, you are recommended to always have the [latest 23 | CMake version][2] installed to make use of the latest Quality-of-Life 24 | additions. 25 | 26 | You have a few options to pass `stronk_DEVELOPER_MODE` to the configure 27 | command, but this project prefers to use presets. 28 | 29 | As a developer, you should create a `CMakeUserPresets.json` file at the root of 30 | the project: 31 | 32 | ```json 33 | { 34 | "version": 3, 35 | "cmakeMinimumRequired": { 36 | "major": 3, 37 | "minor": 28, 38 | "patch": 0 39 | }, 40 | "configurePresets": [ 41 | { 42 | "name": "dev", 43 | "inherits": ["dev-mode", "vcpkg", "ci-", ""], 44 | "cacheVariables": { 45 | "CMAKE_BUILD_TYPE": "Debug" 46 | }, 47 | "environment": { 48 | "VCPKG_ROOT": "" 49 | } 50 | } 51 | ], 52 | "buildPresets": [ 53 | { 54 | "name": "dev", 55 | "configurePreset": "dev", 56 | "configuration": "Debug" 57 | } 58 | ], 59 | "testPresets": [ 60 | { 61 | "name": "dev", 62 | "configurePreset": "dev", 63 | "configuration": "Debug", 64 | "output": { 65 | "outputOnFailure": true 66 | } 67 | } 68 | ] 69 | } 70 | ``` 71 | 72 | You should replace `` in your newly created presets file with the name of 73 | the operating system you have, which may be `win64` or `unix`. You can see what 74 | these correspond to in the [`CMakePresets.json`](CMakePresets.json) file. 75 | 76 | `CMakeUserPresets.json` is also the perfect place in which you can put all 77 | sorts of things that you would otherwise want to pass to the configure command 78 | in the terminal. 79 | 80 | ### Dependency manager 81 | 82 | The above preset will make use of the [vcpkg][vcpkg] dependency manager. On Windows, you might also want 83 | to inherit from the `vcpkg-win64-static` preset, which will make vcpkg install 84 | the dependencies as static libraries. This is only necessary if you don't want 85 | to setup `PATH` to run tests. 86 | 87 | [vcpkg]: https://github.com/microsoft/vcpkg 88 | 89 | ### Configure, build and test 90 | 91 | If you followed the above instructions, then you can configure, build and test 92 | the project respectively with the following commands from the project root on 93 | any operating system with any build system: 94 | 95 | ```sh 96 | cmake --preset=dev 97 | cmake --build --preset=dev 98 | ctest --preset=dev 99 | ``` 100 | 101 | If you are using a compatible editor (e.g. VSCode) or IDE (e.g. CLion, VS), you 102 | will also be able to select the above created user presets for automatic 103 | integration. 104 | 105 | Please note that both the build and test commands accept a `-j` flag to specify 106 | the number of jobs to use, which should ideally be specified to the number of 107 | threads your CPU has. You may also want to add that to your preset using the 108 | `jobs` property, see the [presets documentation][1] for more details. 109 | 110 | ### Developer mode targets 111 | 112 | These are targets you may invoke using the build command from above, with an 113 | additional `-t ` flag: 114 | 115 | #### `coverage` 116 | 117 | Available if `ENABLE_COVERAGE` is enabled. This target processes the output of 118 | the previously run tests when built with coverage configuration. The commands 119 | this target runs can be found in the `COVERAGE_TRACE_COMMAND` and 120 | `COVERAGE_HTML_COMMAND` cache variables. The trace command produces an info 121 | file by default, which can be submitted to services with CI integration. The 122 | HTML command uses the trace command's output to generate a HTML document to 123 | `/coverage_html` by default. 124 | 125 | #### `format-check` and `format-fix` 126 | 127 | These targets run the clang-format tool on the codebase to check errors and to 128 | fix them respectively. Customization available using the `FORMAT_PATTERNS` and 129 | `FORMAT_COMMAND` cache variables. 130 | 131 | #### `run-examples` 132 | 133 | Runs all the examples created by the `add_example` command. 134 | 135 | #### `spell-check` and `spell-fix` 136 | 137 | These targets run the codespell tool on the codebase to check errors and to fix 138 | them respectively. Customization available using the `SPELL_COMMAND` cache 139 | variable. 140 | 141 | [1]: https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html 142 | [2]: https://cmake.org/download/ 143 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 twig.energy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /benchmarks/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.28) 2 | 3 | project(stronkBenchmarks LANGUAGES CXX) 4 | 5 | include(../cmake/project-is-top-level.cmake) 6 | include(../cmake/folders.cmake) 7 | 8 | # ---- Dependencies ---- 9 | if (PROJECT_IS_TOP_LEVEL) 10 | find_package(stronk REQUIRED) 11 | endif () 12 | 13 | find_package(benchmark CONFIG REQUIRED) 14 | find_package(fmt CONFIG REQUIRED) 15 | find_package(absl CONFIG REQUIRED) 16 | 17 | # ---- Benchmarks ---- 18 | add_executable(stronk_benchmarks src/construction_benchmarks.cpp src/unit_benchmarks.cpp) 19 | 20 | target_link_libraries( 21 | stronk_benchmarks 22 | PRIVATE absl::hash 23 | benchmark::benchmark_main 24 | fmt::fmt 25 | twig::stronk 26 | ) 27 | target_compile_features(stronk_benchmarks PRIVATE cxx_std_20) 28 | 29 | # TODO(anders.wind) remove the following after benchmark 1.7.1 is released on vcpkg https://github.com/microsoft/vcpkg/issues/27673 30 | target_compile_definitions(stronk_benchmarks PRIVATE BENCHMARK_STATIC_DEFINE) 31 | 32 | # ---- End-of-file commands ---- 33 | add_folders(Benchmarks) 34 | -------------------------------------------------------------------------------- /benchmarks/src/benchmark_helpers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include "stronk/stronk.hpp" 13 | #include "stronk/unit.hpp" 14 | 15 | struct a_unit : twig::stronk_default_unit> 16 | { 17 | }; 18 | 19 | using int8_t_wrapping_type = a_unit::value; 20 | using int64_t_wrapping_type = a_unit::value; 21 | using double_wrapping_type = a_unit::value; 22 | 23 | struct string_wrapping_type : twig::stronk 24 | { 25 | using stronk::stronk; 26 | }; 27 | 28 | namespace details 29 | { 30 | template 31 | auto default_max_for_random() -> T 32 | { 33 | if constexpr (std::is_floating_point_v) { 34 | return T {1.0}; 35 | } else { 36 | return std::numeric_limits::max(); 37 | } 38 | } 39 | 40 | inline static auto get_random_device() -> std::mt19937& 41 | { 42 | static thread_local std::random_device dev; 43 | static thread_local std::mt19937 rng(dev()); 44 | return rng; 45 | } 46 | 47 | template 48 | auto rand(T mi, T ma) -> T 49 | { 50 | if constexpr (std::is_floating_point_v) { 51 | auto dist = std::uniform_real_distribution(mi, ma); 52 | return dist(get_random_device()); 53 | } else if constexpr (std::is_integral_v && sizeof(T) <= 1) { 54 | using random_supported_type = std::conditional_t, uint16_t, int16_t>; 55 | auto dist = std::uniform_int_distribution(static_cast(mi), 56 | static_cast(ma)); 57 | return static_cast(dist(get_random_device())); 58 | } else if constexpr (std::is_integral_v) { 59 | auto dist = std::uniform_int_distribution(mi, ma); 60 | return dist(get_random_device()); 61 | } else { 62 | static_assert(twig::stronk_details::not_implemented_type {}); 63 | } 64 | } 65 | 66 | template 67 | auto rand() -> T 68 | { 69 | return rand(T {0}, default_max_for_random()); 70 | } 71 | 72 | } // namespace details 73 | 74 | template 75 | struct generate_randomish 76 | { 77 | auto operator()() const -> T 78 | { 79 | return details::rand(); 80 | } 81 | }; 82 | template<> 83 | struct generate_randomish 84 | { 85 | auto operator()() const -> std::string 86 | { 87 | auto ss = std::stringstream(); 88 | for (auto i = 0; i < 16; i++) { 89 | ss << generate_randomish {}(); 90 | } 91 | return ss.str(); 92 | } 93 | }; 94 | 95 | template 96 | struct generate_randomish 97 | { 98 | auto operator()() const -> T 99 | { 100 | return T {generate_randomish {}()}; 101 | } 102 | }; 103 | 104 | template 105 | auto generate_none_zero_randomish() -> T 106 | { 107 | return generate_randomish {}() + static_cast(1); 108 | } 109 | -------------------------------------------------------------------------------- /benchmarks/src/construction_benchmarks.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "./benchmark_helpers.h" 9 | 10 | namespace 11 | { 12 | template 13 | void benchmark_default_onto_reserved_vector(benchmark::State& state) 14 | { 15 | auto vec = std::vector(static_cast(state.range(0))); 16 | benchmark::DoNotOptimize(vec.data()); 17 | for (auto _ : state) { 18 | for (auto i = 0ULL; i < static_cast(state.range(0)); i++) { 19 | vec[i] = T {}; 20 | } 21 | benchmark::ClobberMemory(); 22 | } 23 | } 24 | } // namespace 25 | 26 | BENCHMARK_TEMPLATE(benchmark_default_onto_reserved_vector, int8_t)->Range(32ULL, 8ULL << 10ULL); 27 | BENCHMARK_TEMPLATE(benchmark_default_onto_reserved_vector, int8_t_wrapping_type)->Range(32ULL, 8ULL << 10ULL); 28 | 29 | BENCHMARK_TEMPLATE(benchmark_default_onto_reserved_vector, int64_t)->Range(32ULL, 8ULL << 10ULL); 30 | BENCHMARK_TEMPLATE(benchmark_default_onto_reserved_vector, int64_t_wrapping_type)->Range(32ULL, 8ULL << 10ULL); 31 | 32 | BENCHMARK_TEMPLATE(benchmark_default_onto_reserved_vector, std::string)->Range(32ULL, 8ULL << 10ULL); 33 | BENCHMARK_TEMPLATE(benchmark_default_onto_reserved_vector, string_wrapping_type)->Range(32ULL, 8ULL << 10ULL); 34 | 35 | namespace 36 | { 37 | template 38 | void benchmark_rand_onto_reserved_vector(benchmark::State& state) 39 | { 40 | auto vec = std::vector(static_cast(state.range(0))); 41 | benchmark::DoNotOptimize(vec.data()); 42 | for (auto _ : state) { 43 | for (auto i = 0ULL; i < static_cast(state.range(0)); i++) { 44 | vec[i] = generate_randomish {}(); 45 | } 46 | benchmark::ClobberMemory(); 47 | } 48 | } 49 | } // namespace 50 | 51 | BENCHMARK_TEMPLATE(benchmark_rand_onto_reserved_vector, int8_t)->Range(32ULL, 8ULL << 10ULL); 52 | BENCHMARK_TEMPLATE(benchmark_rand_onto_reserved_vector, int8_t_wrapping_type)->Range(32ULL, 8ULL << 10ULL); 53 | 54 | BENCHMARK_TEMPLATE(benchmark_rand_onto_reserved_vector, int64_t)->Range(32ULL, 8ULL << 10ULL); 55 | BENCHMARK_TEMPLATE(benchmark_rand_onto_reserved_vector, int64_t_wrapping_type)->Range(32ULL, 8ULL << 10ULL); 56 | 57 | BENCHMARK_TEMPLATE(benchmark_rand_onto_reserved_vector, std::string)->Range(32ULL, 8ULL << 10ULL); 58 | BENCHMARK_TEMPLATE(benchmark_rand_onto_reserved_vector, string_wrapping_type)->Range(32ULL, 8ULL << 10ULL); 59 | 60 | namespace 61 | { 62 | template 63 | void benchmark_copy_vector_of(benchmark::State& state) 64 | { 65 | auto vec = std::vector(static_cast(state.range(0))); 66 | std::generate(vec.begin(), vec.end(), []() { return generate_randomish {}(); }); 67 | benchmark::DoNotOptimize(vec.data()); 68 | for (auto _ : state) { 69 | auto copy_into = vec; 70 | benchmark::DoNotOptimize(copy_into.data()); 71 | benchmark::ClobberMemory(); 72 | } 73 | } 74 | } // namespace 75 | 76 | BENCHMARK_TEMPLATE(benchmark_copy_vector_of, int8_t)->Range(32ULL, 8ULL << 10ULL); 77 | BENCHMARK_TEMPLATE(benchmark_copy_vector_of, int8_t_wrapping_type)->Range(32ULL, 8ULL << 10ULL); 78 | 79 | BENCHMARK_TEMPLATE(benchmark_copy_vector_of, int64_t)->Range(32ULL, 8ULL << 10ULL); 80 | BENCHMARK_TEMPLATE(benchmark_copy_vector_of, int64_t_wrapping_type)->Range(32ULL, 8ULL << 10ULL); 81 | 82 | BENCHMARK_TEMPLATE(benchmark_copy_vector_of, std::string)->Range(32ULL, 8ULL << 10ULL); 83 | BENCHMARK_TEMPLATE(benchmark_copy_vector_of, string_wrapping_type)->Range(32ULL, 8ULL << 10ULL); 84 | -------------------------------------------------------------------------------- /benchmarks/src/unit_benchmarks.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "./benchmark_helpers.h" 10 | 11 | namespace 12 | { 13 | template 14 | void benchmark_add_units(benchmark::State& state) 15 | { 16 | auto vec_a = std::vector(static_cast(state.range(0))); 17 | auto vec_b = std::vector(static_cast(state.range(0))); 18 | std::ranges::generate(vec_a, []() { return generate_randomish {}(); }); 19 | std::ranges::generate(vec_b, []() { return generate_randomish {}(); }); 20 | 21 | for (auto _ : state) { 22 | for (auto i = 0ULL; i < static_cast(state.range(0)); i++) { 23 | T res; 24 | benchmark::DoNotOptimize(res); 25 | res = vec_a[i] + vec_b[i]; 26 | benchmark::ClobberMemory(); 27 | } 28 | } 29 | } 30 | 31 | template 32 | void benchmark_add_units_simd(benchmark::State& state) 33 | { 34 | auto vec_a = std::vector(static_cast(state.range(0))); 35 | auto vec_b = std::vector(static_cast(state.range(0))); 36 | std::generate(vec_a.begin(), vec_a.end(), []() { return generate_randomish {}(); }); 37 | std::generate(vec_b.begin(), vec_b.end(), []() { return generate_randomish {}(); }); 38 | 39 | for (auto _ : state) { 40 | for (auto i = 0ULL; i < static_cast(state.range(0)) - WidthV; i++) { 41 | auto array_c = std::array {}; 42 | benchmark::DoNotOptimize(array_c.data()); 43 | // We expect the inner loop to be vectorized to SIMD instructions 44 | for (size_t j = 0; j < WidthV; j++) { 45 | array_c[j] = vec_a[i + j] + vec_b[i + j]; 46 | } 47 | benchmark::ClobberMemory(); 48 | } 49 | } 50 | } 51 | } // namespace 52 | 53 | // // benchmark_add_units 54 | BENCHMARK_TEMPLATE(benchmark_add_units, int8_t)->Arg(8192); 55 | BENCHMARK_TEMPLATE(benchmark_add_units, int8_t_wrapping_type)->Arg(8192); 56 | 57 | BENCHMARK_TEMPLATE(benchmark_add_units, int64_t)->Arg(8192); 58 | BENCHMARK_TEMPLATE(benchmark_add_units, int64_t_wrapping_type)->Arg(8192); 59 | 60 | BENCHMARK_TEMPLATE(benchmark_add_units, double)->Arg(8192); 61 | BENCHMARK_TEMPLATE(benchmark_add_units, double_wrapping_type)->Arg(8192); 62 | 63 | // benchmark_add_units_simd 64 | BENCHMARK_TEMPLATE(benchmark_add_units_simd, int8_t, 32)->Arg(8192 / 32); 65 | BENCHMARK_TEMPLATE(benchmark_add_units_simd, int8_t_wrapping_type, 32)->Arg(8192 / 32); 66 | 67 | BENCHMARK_TEMPLATE(benchmark_add_units_simd, int64_t, 32)->Arg(8192 / 32); 68 | BENCHMARK_TEMPLATE(benchmark_add_units_simd, int64_t_wrapping_type, 32)->Arg(8192 / 32); 69 | 70 | BENCHMARK_TEMPLATE(benchmark_add_units_simd, double, 32)->Arg(8192 / 32); 71 | BENCHMARK_TEMPLATE(benchmark_add_units_simd, double_wrapping_type, 32)->Arg(8192 / 32); 72 | 73 | namespace 74 | { 75 | template 76 | void benchmark_subtract_units(benchmark::State& state) 77 | { 78 | auto vec_a = std::vector(static_cast(state.range(0))); 79 | auto vec_b = std::vector(static_cast(state.range(0))); 80 | std::generate(vec_a.begin(), vec_a.end(), []() { return generate_randomish {}(); }); 81 | std::generate(vec_b.begin(), vec_b.end(), []() { return generate_randomish {}(); }); 82 | 83 | for (auto _ : state) { 84 | for (auto i = 0ULL; i < static_cast(state.range(0)); i++) { 85 | T res; 86 | benchmark::DoNotOptimize(res); 87 | res = vec_a[i] - vec_b[i]; 88 | benchmark::ClobberMemory(); 89 | } 90 | } 91 | } 92 | 93 | template 94 | void benchmark_subtract_units_simd(benchmark::State& state) 95 | { 96 | auto vec_a = std::vector(static_cast(state.range(0))); 97 | auto vec_b = std::vector(static_cast(state.range(0))); 98 | std::generate(vec_a.begin(), vec_a.end(), []() { return generate_randomish {}(); }); 99 | std::generate(vec_b.begin(), vec_b.end(), []() { return generate_randomish {}(); }); 100 | 101 | for (auto _ : state) { 102 | for (auto i = 0ULL; i < static_cast(state.range(0)) - WidthV; i++) { 103 | auto array_c = std::array {}; 104 | benchmark::DoNotOptimize(array_c.data()); 105 | // We expect the inner loop to be vectorized to SIMD instructions 106 | for (size_t j = 0; j < WidthV; j++) { 107 | array_c[j] = vec_a[i + j] - vec_b[i + j]; 108 | } 109 | benchmark::ClobberMemory(); 110 | } 111 | } 112 | } 113 | } // namespace 114 | 115 | BENCHMARK_TEMPLATE(benchmark_subtract_units, int8_t)->Arg(8192); 116 | BENCHMARK_TEMPLATE(benchmark_subtract_units, int8_t_wrapping_type)->Arg(8192); 117 | 118 | BENCHMARK_TEMPLATE(benchmark_subtract_units, int64_t)->Arg(8192); 119 | BENCHMARK_TEMPLATE(benchmark_subtract_units, int64_t_wrapping_type)->Arg(8192); 120 | 121 | BENCHMARK_TEMPLATE(benchmark_subtract_units, double)->Arg(8192); 122 | BENCHMARK_TEMPLATE(benchmark_subtract_units, double_wrapping_type)->Arg(8192); 123 | 124 | // simd 125 | BENCHMARK_TEMPLATE(benchmark_subtract_units_simd, int8_t, 32)->Arg(8192 / 32); 126 | BENCHMARK_TEMPLATE(benchmark_subtract_units_simd, int8_t_wrapping_type, 32)->Arg(8192 / 32); 127 | 128 | BENCHMARK_TEMPLATE(benchmark_subtract_units_simd, int64_t, 32)->Arg(8192 / 32); 129 | BENCHMARK_TEMPLATE(benchmark_subtract_units_simd, int64_t_wrapping_type, 32)->Arg(8192 / 32); 130 | 131 | BENCHMARK_TEMPLATE(benchmark_subtract_units_simd, double, 32)->Arg(8192 / 32); 132 | BENCHMARK_TEMPLATE(benchmark_subtract_units_simd, double_wrapping_type, 32)->Arg(8192 / 32); 133 | 134 | namespace 135 | { 136 | template 137 | void benchmark_multiply_units(benchmark::State& state) 138 | { 139 | auto vec_a = std::vector(static_cast(state.range(0))); 140 | auto vec_b = std::vector(static_cast(state.range(0))); 141 | std::generate(vec_a.begin(), vec_a.end(), []() { return generate_randomish {}(); }); 142 | std::generate(vec_b.begin(), vec_b.end(), []() { return generate_randomish {}(); }); 143 | using res_t = decltype(T {} * O {}); 144 | 145 | for (auto _ : state) { 146 | for (auto i = 0ULL; i < static_cast(state.range(0)); i++) { 147 | res_t res; 148 | benchmark::DoNotOptimize(res); 149 | res = vec_a[i] * vec_b[i]; 150 | benchmark::ClobberMemory(); 151 | } 152 | } 153 | } 154 | 155 | template 156 | void benchmark_multiply_units_simd(benchmark::State& state) 157 | { 158 | auto vec_a = std::vector(static_cast(state.range(0))); 159 | auto vec_b = std::vector(static_cast(state.range(0))); 160 | std::generate(vec_a.begin(), vec_a.end(), []() { return generate_randomish {}(); }); 161 | std::generate(vec_b.begin(), vec_b.end(), []() { return generate_randomish {}(); }); 162 | using res_t = decltype(T {} * O {}); 163 | 164 | for (auto _ : state) { 165 | for (auto i = 0ULL; i < static_cast(state.range(0)) - WidthV; i++) { 166 | auto array_c = std::array {}; 167 | benchmark::DoNotOptimize(array_c.data()); 168 | // We expect the inner loop to be vectorized to SIMD instructions 169 | for (size_t j = 0; j < WidthV; j++) { 170 | array_c[j] = vec_a[i + j] * vec_b[i + j]; 171 | } 172 | benchmark::ClobberMemory(); 173 | } 174 | } 175 | } 176 | } // namespace 177 | 178 | BENCHMARK_TEMPLATE(benchmark_multiply_units, int8_t, int8_t)->Arg(8192); 179 | BENCHMARK_TEMPLATE(benchmark_multiply_units, int8_t_wrapping_type, int8_t_wrapping_type)->Arg(8192); 180 | 181 | BENCHMARK_TEMPLATE(benchmark_multiply_units, int64_t, int64_t)->Arg(8192); 182 | BENCHMARK_TEMPLATE(benchmark_multiply_units, int64_t_wrapping_type, int64_t_wrapping_type)->Arg(8192); 183 | 184 | BENCHMARK_TEMPLATE(benchmark_multiply_units, double, double)->Arg(8192); 185 | BENCHMARK_TEMPLATE(benchmark_multiply_units, double_wrapping_type, double_wrapping_type)->Arg(8192); 186 | 187 | BENCHMARK_TEMPLATE(benchmark_multiply_units, int64_t, double)->Arg(8192); 188 | BENCHMARK_TEMPLATE(benchmark_multiply_units, int64_t_wrapping_type, double_wrapping_type)->Arg(8192); 189 | 190 | BENCHMARK_TEMPLATE(benchmark_multiply_units, double, int64_t)->Arg(8192); 191 | BENCHMARK_TEMPLATE(benchmark_multiply_units, double_wrapping_type, int64_t_wrapping_type)->Arg(8192); 192 | 193 | BENCHMARK_TEMPLATE(benchmark_multiply_units_simd, int8_t, int8_t, 32)->Arg(8192 / 32); 194 | BENCHMARK_TEMPLATE(benchmark_multiply_units_simd, int8_t_wrapping_type, int8_t_wrapping_type, 32)->Arg(8192 / 32); 195 | 196 | BENCHMARK_TEMPLATE(benchmark_multiply_units_simd, int64_t, int64_t, 32)->Arg(8192 / 32); 197 | BENCHMARK_TEMPLATE(benchmark_multiply_units_simd, int64_t_wrapping_type, int64_t_wrapping_type, 32)->Arg(8192 / 32); 198 | 199 | BENCHMARK_TEMPLATE(benchmark_multiply_units_simd, double, double, 32)->Arg(8192 / 32); 200 | BENCHMARK_TEMPLATE(benchmark_multiply_units_simd, double_wrapping_type, double_wrapping_type, 32)->Arg(8192 / 32); 201 | 202 | BENCHMARK_TEMPLATE(benchmark_multiply_units_simd, int64_t, double, 32)->Arg(8192 / 32); 203 | BENCHMARK_TEMPLATE(benchmark_multiply_units_simd, int64_t_wrapping_type, double_wrapping_type, 32)->Arg(8192 / 32); 204 | 205 | BENCHMARK_TEMPLATE(benchmark_multiply_units_simd, double, int64_t, 32)->Arg(8192 / 32); 206 | BENCHMARK_TEMPLATE(benchmark_multiply_units_simd, double_wrapping_type, int64_t_wrapping_type, 32)->Arg(8192 / 32); 207 | 208 | namespace 209 | { 210 | template 211 | void benchmark_divide_units(benchmark::State& state) 212 | { 213 | auto vec_a = std::vector(static_cast(state.range(0))); 214 | auto vec_b = std::vector(static_cast(state.range(0))); 215 | std::generate(vec_a.begin(), vec_a.end(), []() { return generate_randomish {}(); }); 216 | std::generate(vec_b.begin(), vec_b.end(), []() { return generate_none_zero_randomish(); }); 217 | using res_t = decltype(T {} / O {}); 218 | 219 | for (auto _ : state) { 220 | for (auto i = 0ULL; i < static_cast(state.range(0)); i++) { 221 | res_t res; 222 | benchmark::DoNotOptimize(res); 223 | res = vec_a[i] / vec_b[i]; 224 | benchmark::ClobberMemory(); 225 | } 226 | } 227 | } 228 | 229 | template 230 | void benchmark_divide_units_simd(benchmark::State& state) 231 | { 232 | auto vec_a = std::vector(static_cast(state.range(0))); 233 | auto vec_b = std::vector(static_cast(state.range(0))); 234 | std::generate(vec_a.begin(), vec_a.end(), []() { return generate_randomish {}(); }); 235 | std::generate(vec_b.begin(), vec_b.end(), []() { return generate_none_zero_randomish(); }); 236 | using res_t = decltype(T {} / O {}); 237 | 238 | for (auto _ : state) { 239 | for (auto i = 0ULL; i < static_cast(state.range(0)) - WidthV; i++) { 240 | auto array_c = std::array {}; 241 | benchmark::DoNotOptimize(array_c.data()); 242 | // We expect the inner loop to be vectorized to SIMD instructions 243 | for (size_t j = 0; j < WidthV; j++) { 244 | array_c[j] = vec_a[i + j] / vec_b[i + j]; 245 | } 246 | benchmark::ClobberMemory(); 247 | } 248 | } 249 | } 250 | } // namespace 251 | 252 | BENCHMARK_TEMPLATE(benchmark_divide_units, int8_t, int8_t)->Arg(8192); 253 | BENCHMARK_TEMPLATE(benchmark_divide_units, int8_t_wrapping_type, int8_t_wrapping_type)->Arg(8192); 254 | 255 | BENCHMARK_TEMPLATE(benchmark_divide_units, int64_t, int64_t)->Arg(8192); 256 | BENCHMARK_TEMPLATE(benchmark_divide_units, int64_t_wrapping_type, int64_t_wrapping_type)->Arg(8192); 257 | 258 | BENCHMARK_TEMPLATE(benchmark_divide_units, double, double)->Arg(8192); 259 | BENCHMARK_TEMPLATE(benchmark_divide_units, double_wrapping_type, double_wrapping_type)->Arg(8192); 260 | 261 | BENCHMARK_TEMPLATE(benchmark_divide_units, int64_t, double)->Arg(8192); 262 | BENCHMARK_TEMPLATE(benchmark_divide_units, int64_t_wrapping_type, double_wrapping_type)->Arg(8192); 263 | 264 | BENCHMARK_TEMPLATE(benchmark_divide_units, double, int64_t)->Arg(8192); 265 | BENCHMARK_TEMPLATE(benchmark_divide_units, double_wrapping_type, int64_t_wrapping_type)->Arg(8192); 266 | 267 | BENCHMARK_TEMPLATE(benchmark_divide_units_simd, int8_t, int8_t, 32)->Arg(8192 / 32); 268 | BENCHMARK_TEMPLATE(benchmark_divide_units_simd, int8_t_wrapping_type, int8_t_wrapping_type, 32)->Arg(8192 / 32); 269 | 270 | BENCHMARK_TEMPLATE(benchmark_divide_units_simd, int64_t, int64_t, 32)->Arg(8192 / 32); 271 | BENCHMARK_TEMPLATE(benchmark_divide_units_simd, int64_t_wrapping_type, int64_t_wrapping_type, 32)->Arg(8192 / 32); 272 | 273 | BENCHMARK_TEMPLATE(benchmark_divide_units_simd, double, double, 32)->Arg(8192 / 32); 274 | BENCHMARK_TEMPLATE(benchmark_divide_units_simd, double_wrapping_type, double_wrapping_type, 32)->Arg(8192 / 32); 275 | 276 | BENCHMARK_TEMPLATE(benchmark_divide_units_simd, int64_t, double, 32)->Arg(8192 / 32); 277 | BENCHMARK_TEMPLATE(benchmark_divide_units_simd, int64_t_wrapping_type, double_wrapping_type, 32)->Arg(8192 / 32); 278 | 279 | BENCHMARK_TEMPLATE(benchmark_divide_units_simd, double, int64_t, 32)->Arg(8192 / 32); 280 | BENCHMARK_TEMPLATE(benchmark_divide_units_simd, double_wrapping_type, int64_t_wrapping_type, 32)->Arg(8192 / 32); 281 | -------------------------------------------------------------------------------- /cmake-format.yaml: -------------------------------------------------------------------------------- 1 | _help_parse: Options affecting listfile parsing 2 | parse: 3 | _help_additional_commands: 4 | - Specify structure for custom cmake functions 5 | additional_commands: 6 | generate_export_header: 7 | kwargs: 8 | BASE_NAME: "*" 9 | EXPORT_FILE_NAME: "*" 10 | CUSTOM_CONTENT_FROM_VARIABLE: "*" 11 | target_link_libraries: 12 | pargs: 13 | nargs: 1 14 | kwargs: 15 | PRIVATE: 16 | pargs: 17 | nargs: "*" 18 | sortable: true 19 | PUBLIC: 20 | pargs: 21 | nargs: "*" 22 | sortable: true 23 | INTERFACE: 24 | pargs: 25 | nargs: "*" 26 | sortable: true 27 | target_sources: 28 | pargs: 29 | nargs: 1 30 | kwargs: 31 | PRIVATE: 32 | pargs: 33 | nargs: "*" 34 | sortable: true 35 | PUBLIC: 36 | pargs: 37 | nargs: "*" 38 | sortable: true 39 | INTERFACE: 40 | pargs: 41 | nargs: "*" 42 | sortable: true 43 | FILE_SET: 44 | pargs: 45 | nargs: 1 46 | kwargs: 47 | TYPE: 1 48 | BASE_DIRS: 49 | pargs: 50 | nargs: "+" 51 | sortable: true 52 | FILES: 53 | pargs: 54 | nargs: "+" 55 | sortable: true 56 | _help_override_spec: 57 | - Override configurations per-command where available 58 | override_spec: {} 59 | _help_vartags: 60 | - Specify variable tags. 61 | vartags: [] 62 | _help_proptags: 63 | - Specify property tags. 64 | proptags: [] 65 | _help_format: Options affecting formatting. 66 | format: 67 | _help_disable: 68 | - Disable formatting entirely, making cmake-format a no-op 69 | disable: false 70 | _help_line_width: 71 | - How wide to allow formatted cmake files 72 | line_width: 160 73 | _help_tab_size: 74 | - How many spaces to tab for indent 75 | tab_size: 4 76 | _help_use_tabchars: 77 | - If true, lines are indented using tab characters (utf-8 78 | - 0x09) instead of space characters (utf-8 0x20). 79 | - In cases where the layout would require a fractional tab 80 | - character, the behavior of the fractional indentation is 81 | - governed by 82 | use_tabchars: false 83 | _help_fractional_tab_policy: 84 | - If is True, then the value of this variable 85 | - indicates how fractional indentions are handled during 86 | - whitespace replacement. If set to 'use-space', fractional 87 | - indentation is left as spaces (utf-8 0x20). If set to 88 | - "`round-up` fractional indentation is replaced with a single" 89 | - tab character (utf-8 0x09) effectively shifting the column 90 | - to the next tabstop 91 | fractional_tab_policy: use-space 92 | _help_max_subgroups_hwrap: 93 | - If an argument group contains more than this many sub-groups 94 | - (parg or kwarg groups) then force it to a vertical layout. 95 | max_subgroups_hwrap: 2 96 | _help_max_pargs_hwrap: 97 | - If a positional argument group contains more than this many 98 | - arguments, then force it to a vertical layout. 99 | max_pargs_hwrap: 3 100 | _help_max_rows_cmdline: 101 | - If a cmdline positional group consumes more than this many 102 | - lines without nesting, then invalidate the layout (and nest) 103 | max_rows_cmdline: 2 104 | _help_separate_ctrl_name_with_space: 105 | - If true, separate flow control names from their parentheses 106 | - with a space 107 | separate_ctrl_name_with_space: true 108 | _help_separate_fn_name_with_space: 109 | - If true, separate function names from parentheses with a 110 | - space 111 | separate_fn_name_with_space: false 112 | _help_dangle_parens: 113 | - If a statement is wrapped to more than one line, than dangle 114 | - the closing parenthesis on its own line. 115 | dangle_parens: true 116 | _help_dangle_align: 117 | - If the trailing parenthesis must be 'dangled' on its on 118 | - "line, then align it to this reference: `prefix`: the start" 119 | - "of the statement, `prefix-indent`: the start of the" 120 | - "statement, plus one indentation level, `child`: align to" 121 | - the column of the arguments 122 | dangle_align: prefix 123 | _help_min_prefix_chars: 124 | - If the statement spelling length (including space and 125 | - parenthesis) is smaller than this amount, then force reject 126 | - nested layouts. 127 | min_prefix_chars: 4 128 | _help_max_prefix_chars: 129 | - If the statement spelling length (including space and 130 | - parenthesis) is larger than the tab width by more than this 131 | - amount, then force reject un-nested layouts. 132 | max_prefix_chars: 10 133 | _help_max_lines_hwrap: 134 | - If a candidate layout is wrapped horizontally but it exceeds 135 | - this many lines, then reject the layout. 136 | max_lines_hwrap: 2 137 | _help_line_ending: 138 | - What style line endings to use in the output. 139 | line_ending: unix 140 | _help_command_case: 141 | - Format command names consistently as 'lower' or 'upper' case 142 | command_case: canonical 143 | _help_keyword_case: 144 | - Format keywords consistently as 'lower' or 'upper' case 145 | keyword_case: unchanged 146 | _help_always_wrap: 147 | - A list of command names which should always be wrapped 148 | always_wrap: [] 149 | _help_enable_sort: 150 | - If true, the argument lists which are known to be sortable 151 | - will be sorted lexicographicall 152 | enable_sort: true 153 | _help_autosort: 154 | - If true, the parsers may infer whether or not an argument 155 | - list is sortable (without annotation). 156 | autosort: true 157 | _help_require_valid_layout: 158 | - By default, if cmake-format cannot successfully fit 159 | - everything into the desired linewidth it will apply the 160 | - last, most aggressive attempt that it made. If this flag is 161 | - True, however, cmake-format will print error, exit with non- 162 | - zero status code, and write-out nothing 163 | require_valid_layout: false 164 | _help_layout_passes: 165 | - A dictionary mapping layout nodes to a list of wrap 166 | - decisions. See the documentation for more information. 167 | layout_passes: {} 168 | _help_markup: Options affecting comment reflow and formatting. 169 | markup: 170 | _help_bullet_char: 171 | - What character to use for bulleted lists 172 | bullet_char: "*" 173 | _help_enum_char: 174 | - What character to use as punctuation after numerals in an 175 | - enumerated list 176 | enum_char: . 177 | _help_first_comment_is_literal: 178 | - If comment markup is enabled, don't reflow the first comment 179 | - block in each listfile. Use this to preserve formatting of 180 | - your copyright/license statements. 181 | first_comment_is_literal: false 182 | _help_literal_comment_pattern: 183 | - If comment markup is enabled, don't reflow any comment block 184 | - which matches this (regex) pattern. Default is `None` 185 | - (disabled). 186 | literal_comment_pattern: null 187 | _help_fence_pattern: 188 | - Regular expression to match preformat fences in comments 189 | - default= ``r'^\s*([`~]{3}[`~]*)(.*)$'`` 190 | fence_pattern: ^\s*([`~]{3}[`~]*)(.*)$ 191 | _help_ruler_pattern: 192 | - Regular expression to match rulers in comments default= 193 | - '``r''^\s*[^\w\s]{3}.*[^\w\s]{3}$''``' 194 | ruler_pattern: ^\s*[^\w\s]{3}.*[^\w\s]{3}$ 195 | _help_explicit_trailing_pattern: 196 | - If a comment line matches starts with this pattern then it 197 | - is explicitly a trailing comment for the preceding 198 | - argument. Default is '#<' 199 | explicit_trailing_pattern: "#<" 200 | _help_hashruler_min_length: 201 | - If a comment line starts with at least this many consecutive 202 | - hash characters, then don't lstrip() them off. This allows 203 | - for lazy hash rulers where the first hash char is not 204 | - separated by space 205 | hashruler_min_length: 10 206 | _help_canonicalize_hashrulers: 207 | - If true, then insert a space between the first hash char and 208 | - remaining hash chars in a hash ruler, and normalize its 209 | - length to fill the column 210 | canonicalize_hashrulers: true 211 | _help_enable_markup: 212 | - enable comment markup parsing and reflow 213 | enable_markup: true 214 | _help_lint: Options affecting the linter 215 | lint: 216 | _help_disabled_codes: 217 | - a list of lint codes to disable 218 | disabled_codes: [] 219 | _help_function_pattern: 220 | - regular expression pattern describing valid function names 221 | function_pattern: "[0-9a-z_]+" 222 | _help_macro_pattern: 223 | - regular expression pattern describing valid macro names 224 | macro_pattern: "[0-9A-Z_]+" 225 | _help_global_var_pattern: 226 | - regular expression pattern describing valid names for 227 | - variables with global (cache) scope 228 | global_var_pattern: "[A-Z][0-9A-Z_]+" 229 | _help_internal_var_pattern: 230 | - regular expression pattern describing valid names for 231 | - variables with global scope (but internal semantic) 232 | internal_var_pattern: _[A-Z][0-9A-Z_]+ 233 | _help_local_var_pattern: 234 | - regular expression pattern describing valid names for 235 | - variables with local scope 236 | local_var_pattern: "[a-z][a-z0-9_]+" 237 | _help_private_var_pattern: 238 | - regular expression pattern describing valid names for 239 | - privatedirectory variables 240 | private_var_pattern: _[0-9a-z_]+ 241 | _help_public_var_pattern: 242 | - regular expression pattern describing valid names for public 243 | - directory variables 244 | public_var_pattern: "[A-Z][0-9A-Z_]+" 245 | _help_argument_var_pattern: 246 | - regular expression pattern describing valid names for 247 | - function/macro arguments and loop variables. 248 | argument_var_pattern: "[a-z][a-z0-9_]+" 249 | _help_keyword_pattern: 250 | - regular expression pattern describing valid names for 251 | - keywords used in functions or macros 252 | keyword_pattern: "[A-Z][0-9A-Z_]+" 253 | _help_max_conditionals_custom_parser: 254 | - In the heuristic for C0201, how many conditionals to match 255 | - within a loop in before considering the loop a parser. 256 | max_conditionals_custom_parser: 2 257 | _help_min_statement_spacing: 258 | - Require at least this many newlines between statements 259 | min_statement_spacing: 1 260 | _help_max_statement_spacing: 261 | - Require no more than this many newlines between statements 262 | max_statement_spacing: 2 263 | max_returns: 6 264 | max_branches: 12 265 | max_arguments: 5 266 | max_localvars: 15 267 | max_statements: 50 268 | _help_encode: Options affecting file encoding 269 | encode: 270 | _help_emit_byteorder_mark: 271 | - If true, emit the unicode byte-order mark (BOM) at the start 272 | - of the file 273 | emit_byteorder_mark: false 274 | _help_input_encoding: 275 | - Specify the encoding of the input file. Defaults to utf-8 276 | input_encoding: utf-8 277 | _help_output_encoding: 278 | - Specify the encoding of the output file. Defaults to utf-8. 279 | - Note that cmake only claims to support utf-8 so be careful 280 | - when using anything else 281 | output_encoding: utf-8 282 | _help_misc: Miscellaneous configurations options. 283 | misc: 284 | _help_per_command: 285 | - A dictionary containing any per-command configuration 286 | - overrides. Currently only `command_case` is supported. 287 | per_command: {} 288 | -------------------------------------------------------------------------------- /cmake/dev-mode.cmake: -------------------------------------------------------------------------------- 1 | include(cmake/folders.cmake) 2 | 3 | if (NOT stronk_DEVELOPER_MODE) 4 | return() 5 | elseif (NOT PROJECT_IS_TOP_LEVEL) 6 | message(AUTHOR_WARNING "Developer mode is intended for developers of stronk") 7 | endif () 8 | 9 | option(ENABLE_COVERAGE "Enable coverage support separate from CTest's" OFF) 10 | if (ENABLE_COVERAGE) 11 | include(cmake/code-coverage.cmake) 12 | add_code_coverage_all_targets(EXCLUDE tests/* examples/*) 13 | endif () 14 | 15 | include(CTest) 16 | 17 | if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") 18 | include(cmake/open-cpp-coverage.cmake OPTIONAL) 19 | endif () 20 | 21 | include(cmake/lint-targets.cmake) 22 | include(cmake/spell-targets.cmake) 23 | 24 | add_folders(Project) 25 | -------------------------------------------------------------------------------- /cmake/docs-ci.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.28) 2 | 3 | foreach (var IN ITEMS PROJECT_BINARY_DIR PROJECT_SOURCE_DIR) 4 | if (NOT DEFINED "${var}") 5 | message(FATAL_ERROR "${var} must be defined") 6 | endif () 7 | endforeach () 8 | set(bin "${PROJECT_BINARY_DIR}") 9 | set(src "${PROJECT_SOURCE_DIR}") 10 | 11 | # ---- Dependencies ---- 12 | 13 | set(mcss_SOURCE_DIR "${bin}/docs/.ci") 14 | if (NOT IS_DIRECTORY "${mcss_SOURCE_DIR}") 15 | file(MAKE_DIRECTORY "${mcss_SOURCE_DIR}") 16 | file( 17 | DOWNLOAD https://github.com/friendlyanon/m.css/releases/download/release-1/mcss.zip "${mcss_SOURCE_DIR}/mcss.zip" 18 | STATUS status 19 | EXPECTED_MD5 00cd2757ebafb9bcba7f5d399b3bec7f 20 | ) 21 | if (NOT 22 | status 23 | MATCHES 24 | "^0;" 25 | ) 26 | message(FATAL_ERROR "Download failed with ${status}") 27 | endif () 28 | execute_process( 29 | COMMAND "${CMAKE_COMMAND}" -E tar xf mcss.zip 30 | WORKING_DIRECTORY "${mcss_SOURCE_DIR}" 31 | RESULT_VARIABLE result 32 | ) 33 | if (NOT 34 | result 35 | EQUAL 36 | "0" 37 | ) 38 | message(FATAL_ERROR "Extraction failed with ${result}") 39 | endif () 40 | file(REMOVE "${mcss_SOURCE_DIR}/mcss.zip") 41 | endif () 42 | 43 | find_program(Python3_EXECUTABLE NAMES python3 python) 44 | if (NOT Python3_EXECUTABLE) 45 | message(FATAL_ERROR "Python executable was not found") 46 | endif () 47 | 48 | # ---- Process project() call in CMakeLists.txt ---- 49 | 50 | file(READ "${src}/CMakeLists.txt" content) 51 | 52 | string(FIND "${content}" "project(" index) 53 | if (index EQUAL "-1") 54 | message(FATAL_ERROR "Could not find \"project(\"") 55 | endif () 56 | string( 57 | SUBSTRING "${content}" 58 | "${index}" 59 | -1 60 | content 61 | ) 62 | 63 | string(FIND "${content}" "\n)\n" index) 64 | if (index EQUAL "-1") 65 | message(FATAL_ERROR "Could not find \"\\n)\\n\"") 66 | endif () 67 | string( 68 | SUBSTRING "${content}" 69 | 0 70 | "${index}" 71 | content 72 | ) 73 | 74 | file(WRITE "${bin}/docs-ci.project.cmake" "docs_${content}\n)\n") 75 | 76 | macro (list_pop_front list out) 77 | list( 78 | GET 79 | "${list}" 80 | 0 81 | "${out}" 82 | ) 83 | list(REMOVE_AT "${list}" 0) 84 | endmacro () 85 | 86 | function (docs_project name) 87 | cmake_parse_arguments( 88 | PARSE_ARGV 89 | 1 90 | "" 91 | "" 92 | "VERSION;DESCRIPTION;HOMEPAGE_URL" 93 | LANGUAGES 94 | ) 95 | set(PROJECT_NAME 96 | "${name}" 97 | PARENT_SCOPE 98 | ) 99 | if (DEFINED _VERSION) 100 | set(PROJECT_VERSION 101 | "${_VERSION}" 102 | PARENT_SCOPE 103 | ) 104 | string( 105 | REGEX MATCH 106 | "^[0-9]+(\\.[0-9]+)*" 107 | versions 108 | "${_VERSION}" 109 | ) 110 | string( 111 | REPLACE . 112 | ";" 113 | versions 114 | "${versions}" 115 | ) 116 | set(suffixes 117 | MAJOR 118 | MINOR 119 | PATCH 120 | TWEAK 121 | ) 122 | while ( 123 | NOT 124 | versions 125 | STREQUAL 126 | "" 127 | AND NOT 128 | suffixes 129 | STREQUAL 130 | "" 131 | ) 132 | list_pop_front(versions version) 133 | list_pop_front(suffixes suffix) 134 | set("PROJECT_VERSION_${suffix}" 135 | "${version}" 136 | PARENT_SCOPE 137 | ) 138 | endwhile () 139 | endif () 140 | if (DEFINED _DESCRIPTION) 141 | set(PROJECT_DESCRIPTION 142 | "${_DESCRIPTION}" 143 | PARENT_SCOPE 144 | ) 145 | endif () 146 | if (DEFINED _HOMEPAGE_URL) 147 | set(PROJECT_HOMEPAGE_URL 148 | "${_HOMEPAGE_URL}" 149 | PARENT_SCOPE 150 | ) 151 | endif () 152 | endfunction () 153 | 154 | include("${bin}/docs-ci.project.cmake") 155 | 156 | # ---- Generate docs ---- 157 | 158 | if (NOT DEFINED DOXYGEN_OUTPUT_DIRECTORY) 159 | set(DOXYGEN_OUTPUT_DIRECTORY "${bin}/docs") 160 | endif () 161 | set(out "${DOXYGEN_OUTPUT_DIRECTORY}") 162 | 163 | foreach (file IN ITEMS Doxyfile conf.py) 164 | configure_file("${src}/docs/${file}.in" "${bin}/docs/${file}" @ONLY) 165 | endforeach () 166 | 167 | set(mcss_script "${mcss_SOURCE_DIR}/documentation/doxygen.py") 168 | set(config "${bin}/docs/conf.py") 169 | 170 | file(REMOVE_RECURSE "${out}/html" "${out}/xml") 171 | 172 | execute_process( 173 | COMMAND "${Python3_EXECUTABLE}" "${mcss_script}" "${config}" 174 | WORKING_DIRECTORY "${bin}/docs" 175 | RESULT_VARIABLE result 176 | ) 177 | if (NOT 178 | result 179 | EQUAL 180 | "0" 181 | ) 182 | message(FATAL_ERROR "m.css returned with ${result}") 183 | endif () 184 | -------------------------------------------------------------------------------- /cmake/docs.cmake: -------------------------------------------------------------------------------- 1 | # ---- Dependencies ---- 2 | 3 | set(extract_timestamps "") 4 | if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.24") 5 | set(extract_timestamps DOWNLOAD_EXTRACT_TIMESTAMP YES) 6 | endif() 7 | 8 | include(FetchContent) 9 | FetchContent_Declare( 10 | mcss URL 11 | https://github.com/friendlyanon/m.css/releases/download/release-1/mcss.zip 12 | URL_MD5 00cd2757ebafb9bcba7f5d399b3bec7f 13 | SOURCE_DIR "${PROJECT_BINARY_DIR}/mcss" 14 | UPDATE_DISCONNECTED YES 15 | ${extract_timestamps} 16 | ) 17 | FetchContent_MakeAvailable(mcss) 18 | 19 | find_package(Python3 3.6 REQUIRED) 20 | 21 | # ---- Declare documentation target ---- 22 | 23 | set( 24 | DOXYGEN_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/docs" 25 | CACHE PATH "Path for the generated Doxygen documentation" 26 | ) 27 | 28 | set(working_dir "${PROJECT_BINARY_DIR}/docs") 29 | 30 | foreach(file IN ITEMS Doxyfile conf.py) 31 | configure_file("docs/${file}.in" "${working_dir}/${file}" @ONLY) 32 | endforeach() 33 | 34 | set(mcss_script "${mcss_SOURCE_DIR}/documentation/doxygen.py") 35 | set(config "${working_dir}/conf.py") 36 | 37 | add_custom_target( 38 | docs 39 | COMMAND "${CMAKE_COMMAND}" -E remove_directory 40 | "${DOXYGEN_OUTPUT_DIRECTORY}/html" 41 | "${DOXYGEN_OUTPUT_DIRECTORY}/xml" 42 | COMMAND "${Python3_EXECUTABLE}" "${mcss_script}" "${config}" 43 | COMMENT "Building documentation using Doxygen and m.css" 44 | WORKING_DIRECTORY "${working_dir}" 45 | VERBATIM 46 | ) 47 | -------------------------------------------------------------------------------- /cmake/folders.cmake: -------------------------------------------------------------------------------- 1 | set_property(GLOBAL PROPERTY USE_FOLDERS YES) 2 | 3 | # Call this function at the end of a directory scope to assign a folder to 4 | # targets created in that directory. Utility targets will be assigned to the 5 | # UtilityTargets folder, otherwise to the ${name}Targets folder. If a target 6 | # already has a folder assigned, then that target will be skipped. 7 | function(add_folders name) 8 | get_property(targets DIRECTORY PROPERTY BUILDSYSTEM_TARGETS) 9 | foreach(target IN LISTS targets) 10 | get_property(folder TARGET "${target}" PROPERTY FOLDER) 11 | if(DEFINED folder) 12 | continue() 13 | endif() 14 | set(folder Utility) 15 | get_property(type TARGET "${target}" PROPERTY TYPE) 16 | if(NOT type STREQUAL "UTILITY") 17 | set(folder "${name}") 18 | endif() 19 | set_property(TARGET "${target}" PROPERTY FOLDER "${folder}Targets") 20 | endforeach() 21 | endfunction() 22 | -------------------------------------------------------------------------------- /cmake/install-config.cmake: -------------------------------------------------------------------------------- 1 | include(CMakeFindDependencyMacro) 2 | 3 | find_dependency(boost_type_index) 4 | 5 | include("${CMAKE_CURRENT_LIST_DIR}/stronkTargets.cmake") 6 | -------------------------------------------------------------------------------- /cmake/install-rules.cmake: -------------------------------------------------------------------------------- 1 | # Project is configured with no languages, so tell GNUInstallDirs the lib dir 2 | set(CMAKE_INSTALL_LIBDIR lib CACHE PATH "") 3 | 4 | include(CMakePackageConfigHelpers) 5 | include(GNUInstallDirs) 6 | 7 | if(PROJECT_IS_TOP_LEVEL) 8 | set(CMAKE_INSTALL_INCLUDEDIR "include/stronk-${PROJECT_VERSION}" CACHE PATH "") 9 | endif() 10 | 11 | # find_package() call for consumers to find this project 12 | set(package stronk) 13 | 14 | install( 15 | DIRECTORY include/ 16 | DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" 17 | COMPONENT stronk_Development 18 | ) 19 | 20 | install( 21 | TARGETS twig_stronk 22 | EXPORT stronkTargets 23 | INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" 24 | ) 25 | 26 | write_basic_package_version_file( 27 | "${package}ConfigVersion.cmake" 28 | COMPATIBILITY SameMajorVersion 29 | ARCH_INDEPENDENT 30 | ) 31 | 32 | # Allow package maintainers to freely override the path for the configs 33 | set( 34 | stronk_INSTALL_CMAKEDIR "${CMAKE_INSTALL_DATADIR}/${package}" 35 | CACHE PATH "CMake package config location relative to the install prefix" 36 | ) 37 | mark_as_advanced(stronk_INSTALL_CMAKEDIR) 38 | 39 | install( 40 | FILES cmake/install-config.cmake 41 | DESTINATION "${stronk_INSTALL_CMAKEDIR}" 42 | RENAME "${package}Config.cmake" 43 | COMPONENT stronk_Development 44 | ) 45 | 46 | install( 47 | FILES "${PROJECT_BINARY_DIR}/${package}ConfigVersion.cmake" 48 | DESTINATION "${stronk_INSTALL_CMAKEDIR}" 49 | COMPONENT stronk_Development 50 | ) 51 | 52 | install( 53 | EXPORT stronkTargets 54 | NAMESPACE twig:: 55 | DESTINATION "${stronk_INSTALL_CMAKEDIR}" 56 | COMPONENT stronk_Development 57 | ) 58 | 59 | if(PROJECT_IS_TOP_LEVEL) 60 | include(CPack) 61 | endif() 62 | -------------------------------------------------------------------------------- /cmake/lint-targets.cmake: -------------------------------------------------------------------------------- 1 | set(FORMAT_PATTERNS 2 | source/*.cpp 3 | source/*.hpp 4 | source/*.h 5 | src/*.cpp 6 | src/*.hpp 7 | src/*.h 8 | include/*.hpp 9 | include/*.h 10 | tests/*.cpp 11 | tests/*.hpp 12 | tests/*.h 13 | examples/*.cpp 14 | examples/*.hpp 15 | examples/*.h 16 | benchmarks/*.cpp 17 | benchmarks/*.hpp 18 | benchmarks/*.h 19 | CACHE STRING "; separated patterns relative to the project source dir to format" 20 | ) 21 | 22 | set(FORMAT_COMMAND 23 | clang-format-19 24 | CACHE STRING "Formatter to use" 25 | ) 26 | 27 | add_custom_target( 28 | format-check 29 | COMMAND "${CMAKE_COMMAND}" -D "FORMAT_COMMAND=${FORMAT_COMMAND}" -D "PATTERNS=${FORMAT_PATTERNS}" -P "${PROJECT_SOURCE_DIR}/cmake/lint.cmake" 30 | WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" 31 | COMMENT "Linting the code" 32 | VERBATIM 33 | ) 34 | 35 | add_custom_target( 36 | format-fix 37 | COMMAND "${CMAKE_COMMAND}" -D "FORMAT_COMMAND=${FORMAT_COMMAND}" -D "PATTERNS=${FORMAT_PATTERNS}" -D FIX=YES -P "${PROJECT_SOURCE_DIR}/cmake/lint.cmake" 38 | WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" 39 | COMMENT "Fixing the code" 40 | VERBATIM 41 | ) 42 | -------------------------------------------------------------------------------- /cmake/lint.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.28) 2 | 3 | macro (default name) 4 | if (NOT DEFINED "${name}") 5 | set("${name}" "${ARGN}") 6 | endif () 7 | endmacro () 8 | 9 | default(FORMAT_COMMAND clang-format-19) 10 | default( 11 | PATTERNS 12 | source/*.cpp 13 | source/*.h 14 | source/*.hpp 15 | src/*.cpp 16 | src/*.h 17 | src/*.hpp 18 | include/*.hpp 19 | include/*.h 20 | tests/*.cpp 21 | tests/*.h 22 | tests/*.hpp 23 | examples/*.cpp 24 | examples/*.h 25 | examples/*.hpp 26 | benchmarks/*.cpp 27 | benchmarks/*.h 28 | benchmarks/*.hpp 29 | ) 30 | default(FIX NO) 31 | 32 | set(flag --output-replacements-xml) 33 | set(args OUTPUT_VARIABLE output) 34 | 35 | if (FIX) 36 | set(flag -i) 37 | set(args "") 38 | endif () 39 | 40 | file(GLOB_RECURSE files ${PATTERNS}) 41 | set(badly_formatted "") 42 | set(output "") 43 | string(LENGTH "${CMAKE_SOURCE_DIR}/" path_prefix_length) 44 | 45 | foreach (file IN LISTS files) 46 | execute_process( 47 | COMMAND "${FORMAT_COMMAND}" --style=file "${flag}" "${file}" 48 | WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" 49 | RESULT_VARIABLE result ${args} 50 | ) 51 | 52 | if (NOT 53 | result 54 | EQUAL 55 | "0" 56 | ) 57 | message(FATAL_ERROR "'${file}': formatter returned with ${result}") 58 | endif () 59 | 60 | if (NOT FIX AND output MATCHES "\n,$,$>: -Wno-unused-variable 20 | -Wno-implicit-int-float-conversion> 21 | ) 22 | 23 | add_custom_target( 24 | "run_${NAME}" 25 | COMMAND "${NAME}" 26 | VERBATIM 27 | ) 28 | add_dependencies("run_${NAME}" "${NAME}") 29 | add_dependencies(run-examples "run_${NAME}") 30 | 31 | endfunction () 32 | 33 | add_example(unit_example) 34 | add_example(firstname_lastname_example) 35 | add_example(unit_energy_example) 36 | add_example(specializers_example) 37 | 38 | add_folders(Examples) 39 | -------------------------------------------------------------------------------- /examples/firstname_lastname_example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | struct FirstName : twig::stronk 8 | { 9 | using stronk::stronk; 10 | }; 11 | struct LastName : twig::stronk 12 | { 13 | using stronk::stronk; 14 | }; 15 | 16 | // Strong types protects you from accidentally passing the wrong argument to the wrong parameter. 17 | void print_name(const LastName& lastname, const FirstName& firstname) 18 | { 19 | // The twig::can_ostream skill overloads the `operator<<(ostream&)` for your type. 20 | std::cout << firstname << " "; 21 | // You can also access the underlying type by using the .unwrap() function. 22 | std::cout << lastname.unwrap() << std::endl; 23 | } 24 | 25 | auto main() -> int 26 | { 27 | print_name(LastName {"Doe"}, FirstName {"John"}); 28 | } 29 | -------------------------------------------------------------------------------- /examples/specializers_example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "stronk/unit.hpp" 6 | 7 | // Let's consider the following units: 8 | struct meters_unit : twig::unit> 9 | { 10 | }; 11 | 12 | struct seconds_unit : twig::unit> 13 | { 14 | }; 15 | 16 | // Let's say you want to use a custom defined stronk type for certain unit combinations. 17 | // Let's introduce our own `Speed` type: 18 | struct meters_per_second_unit : twig::unit, std::ratio<1>> 19 | { 20 | }; 21 | // Notice we are using twig::divided_dimensions_t instead of the regular tag 22 | 23 | // To make it possible for stronk to find this type we need to specialize `unit_lookup`: 24 | template<> 25 | struct twig::unit_lookup> 26 | { 27 | template // scale is to support kilo meters / second, or nano meters / second 28 | using unit_t = twig::unit_scaled_or_base_t; 29 | }; 30 | 31 | // Now the automatically generated stronk unit for seconds^2 is meters_per_second 32 | 33 | // The above of course also works for `multiplied_unit` and `unit_multiplied_resulting_unit_type` 34 | 35 | // Sometimes you might want to specialize the multiply or divide operation for the underlying value 36 | // Let's specialize `seconds^2 for double values` to use int64_t as its resulting type. 37 | 38 | template 39 | using seconds = seconds_unit::value; 40 | 41 | template<> 42 | struct twig::underlying_multiply_operation, seconds> 43 | { 44 | using res_type = int64_t; 45 | 46 | constexpr static auto multiply(const double v1, const double v2) noexcept -> res_type 47 | { 48 | return static_cast(v1 * v2); 49 | } 50 | }; 51 | // Now when multiplying two seconds, the resulting type will be seconds^2::value 52 | 53 | auto main() -> int 54 | { 55 | using speed_deduced = twig::divided_unit_t; 56 | static_assert(std::same_as); 57 | } 58 | static_assert(__LINE__ == 58UL, "update readme if this changes"); 59 | -------------------------------------------------------------------------------- /examples/unit_energy_example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | // We introduce a unit type with a default set of skills with the `stronk_default_unit` prefab 8 | struct joules_unit : twig::stronk_default_unit> 9 | { 10 | }; 11 | 12 | template 13 | using joules = joules_unit::value; 14 | 15 | void joules_and_identity_units() 16 | { 17 | auto energy = joules {30.}; 18 | energy += joules {4.} - joules {2.}; // we can add and subtract units 19 | 20 | // Multiplying and dividing with an identity_unit (such as floats and integers) does not change the type. 21 | energy *= 2.; 22 | 23 | // However an identity_unit divided by a regular unit results in a new unit type. 24 | auto one_over_joules = 1.0 / energy; 25 | static_assert(!std::same_as>); 26 | } 27 | 28 | // Let's introduce seconds as a new unit 29 | struct seconds_unit : twig::stronk_default_unit> 30 | { 31 | }; 32 | 33 | template 34 | using seconds = seconds_unit::value; 35 | 36 | // We can define ratios of a specific unit - these scaled units have the same dimension 37 | template 38 | using hours = seconds_unit::scaled_t>::value; 39 | 40 | // We can now dynamically generate a new type! 41 | using watt_unit = twig::divided_unit_t; 42 | 43 | template 44 | using watt = watt_unit::value; 45 | 46 | // or make custom names for already known types (joules) with specific scale 47 | using watt_hours_unit = decltype(watt {} * hours {})::unit_t; 48 | 49 | template 50 | using watt_hours = watt_hours_unit::value; 51 | 52 | void watt_hours_and_generating_new_units() 53 | { 54 | // Multiplying the right units together will automatically produce the new type 55 | watt_hours watt_hours_val = hours {3.} * watt {25.}; 56 | 57 | // The new type supports adding, subtracting, comparing etc by default. 58 | watt_hours_val -= watt_hours {10.} + watt_hours {2.}; 59 | 60 | // We can get back to Hours or Watt by dividing the opposite out. 61 | hours hours_val = watt_hours_val / watt {25.}; 62 | watt watt_val = watt_hours_val / hours {3.}; 63 | } 64 | 65 | // Let's introduce a type for euros, and start combining more types. 66 | struct euro_unit : twig::stronk_default_unit> 67 | { 68 | }; 69 | template 70 | using euro = euro_unit::value; 71 | 72 | template 73 | using mega_watt_hours = 74 | joules_unit::scaled_t>::value; 75 | 76 | void introducing_another_type() 77 | { 78 | // twig::make allows you to scale the input value but it does not change the resulting type 79 | mega_watt_hours one_mega_watt_hour = mega_watt_hours {1.}; 80 | // Now we can generate a new type which consists of 3 types: `Euro / (Watt * Hours)` 81 | auto euros_per_mega_watt_hour = euro {300.} / one_mega_watt_hour; 82 | 83 | // This flexibility allows us to write expessive code, while having the type system check our implementation. 84 | euro price_for_buying_5_mega_watt_hours = 85 | euros_per_mega_watt_hour * (twig::identity_value_t {1} * watt_hours {5.}); 86 | 87 | auto mega_watt_hours_per_euro = 1. / euros_per_mega_watt_hour; // `(Watt * Hours) / Euro` 88 | mega_watt_hours mega_watt_hours_affordable_for_500_euros = mega_watt_hours_per_euro * euro {500.}; 89 | } 90 | 91 | auto main() -> int 92 | { 93 | joules_and_identity_units(); 94 | watt_hours_and_generating_new_units(); 95 | introducing_another_type(); 96 | } 97 | static_assert(__LINE__ == 97UL, "update readme if this changes"); 98 | -------------------------------------------------------------------------------- /examples/unit_example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "stronk/unit.hpp" 6 | 7 | #include "stronk/skills/can_stream.hpp" 8 | 9 | namespace twig 10 | { 11 | 12 | // Distance 13 | 14 | struct meters_unit : twig::unit, can_ostream> 15 | { 16 | }; 17 | 18 | template 19 | using meters = meters_unit::value; 20 | 21 | template 22 | using kilo_meters = meters_unit::scaled_t::value; 23 | 24 | // TIME 25 | 26 | struct seconds_unit : twig::unit, can_ostream> 27 | { 28 | }; 29 | 30 | template 31 | using seconds = seconds_unit::value; 32 | 33 | template 34 | using minutes = seconds_unit::scaled_t>::value; 35 | 36 | template 37 | using hours = seconds_unit::scaled_t>::value; 38 | 39 | // MASS 40 | 41 | struct kilograms_unit : twig::unit, can_ostream> 42 | { 43 | }; 44 | 45 | template 46 | using kilograms = kilograms_unit::value; 47 | 48 | // The name of the generated type for `Distance` over `Time` is not really reader-friendly so making an alias can be 49 | // nice. 50 | using meters_per_second_unit = divided_unit_t; 51 | using acceleration_unit = divided_unit_t; 52 | using time_squared_unit = multiplied_unit_t; 53 | using force_unit = multiplied_unit_t; 54 | 55 | // Now we have it all set up 56 | void example() 57 | { 58 | auto two_hours = hours {2}; 59 | std::cout << two_hours.to>() << " should be " << 120 << std::endl; 60 | 61 | is_unit auto ten_km = kilo_meters {10.}; 62 | is_unit auto forty_minutes = minutes {40}; 63 | 64 | // Dividing different units will generate a new type (Distance/Time) 65 | is_unit auto fifteen_km_per_hour = (ten_km / forty_minutes); 66 | 67 | // And you get your original type out once there's only one type left 68 | is_unit auto distance_moved_over_2_hours_at_speed = two_hours * fifteen_km_per_hour; 69 | 70 | // units can be multiplied and divided by IdentityUnits (values without units) 71 | is_unit auto thirty_km = meters {30.} * 1000; 72 | std::cout << distance_moved_over_2_hours_at_speed.to>() << " should be " << thirty_km << std::endl; 73 | } 74 | 75 | } // namespace twig 76 | 77 | auto main() -> int 78 | { 79 | twig::example(); 80 | return 0; 81 | } 82 | -------------------------------------------------------------------------------- /include/stronk/can_decrement.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // TODO(anders.wind) remove once upgraded downstream 3 | #include "stronk/skills/can_decrement.hpp" // IWYU pragma: keep 4 | -------------------------------------------------------------------------------- /include/stronk/can_format.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // TODO(anders.wind) remove once upgraded downstream 3 | #include "stronk/skills/can_format.hpp" // IWYU pragma: keep 4 | -------------------------------------------------------------------------------- /include/stronk/can_hash.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // TODO(anders.wind) remove once upgraded downstream 3 | #include "stronk/skills/can_hash.hpp" // IWYU pragma: keep 4 | -------------------------------------------------------------------------------- /include/stronk/can_increment.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // TODO(anders.wind) remove once upgraded downstream 3 | #include "stronk/skills/can_increment.hpp" // IWYU pragma: keep 4 | -------------------------------------------------------------------------------- /include/stronk/can_stream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // TODO(anders.wind) remove once upgraded downstream 4 | #include "stronk/skills/can_stream.hpp" // IWYU pragma: keep 5 | -------------------------------------------------------------------------------- /include/stronk/extensions/absl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "stronk/extensions/absl.hpp" // IWYU pragma: keep 3 | -------------------------------------------------------------------------------- /include/stronk/extensions/absl.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include // IWYU pragma: keep 5 | 6 | namespace twig 7 | { 8 | template 9 | struct can_absl_hash 10 | { 11 | template 12 | friend auto AbslHashValue(H h, const StronkT& c) -> H 13 | { 14 | return H::combine(std::move(h), c.template unwrap()); 15 | } 16 | }; 17 | } // namespace twig 18 | -------------------------------------------------------------------------------- /include/stronk/extensions/doctest.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "stronk/extensions/doctest.hpp" // IWYU pragma: keep 3 | -------------------------------------------------------------------------------- /include/stronk/extensions/doctest.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // IWYU pragma: always_keep 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include "stronk/stronk.hpp" 9 | 10 | namespace twig 11 | { 12 | template 13 | concept has_doctest_to_string = requires(U val) { 14 | { 15 | doctest::toString(val) 16 | } -> std::same_as; 17 | }; 18 | } // namespace twig 19 | 20 | template 21 | struct doctest::StringMaker 22 | { 23 | static auto convert(const T& val) -> doctest::String 24 | { 25 | using underlying_t = typename T::underlying_type; 26 | if constexpr (twig::has_doctest_to_string) { 27 | return doctest::toString(val.template unwrap()); 28 | } else { 29 | return doctest::StringMaker::convert(val.template unwrap()); 30 | } 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /include/stronk/extensions/fmt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "stronk/extensions/fmt.hpp" // IWYU pragma: keep 3 | -------------------------------------------------------------------------------- /include/stronk/extensions/fmt.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // IWYU pragma: always_keep 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "stronk/stronk.hpp" 14 | #include "stronk/utilities/strings.hpp" 15 | 16 | namespace twig 17 | { 18 | template 19 | struct can_fmt_format_builder 20 | { 21 | template 22 | struct skill 23 | { 24 | constexpr static const auto fmt_string = FormatStringV; 25 | }; 26 | }; 27 | 28 | template 29 | struct can_fmt_format 30 | { 31 | constexpr static const auto fmt_string = stronk_details::str::string_literal("{}"); 32 | }; 33 | 34 | template 35 | concept can_special_fmt_format_like = stronk_like && requires(T v) { 36 | { 37 | static_cast(T::fmt_string) 38 | } -> std::same_as; 39 | }; 40 | 41 | } // namespace twig 42 | 43 | template 44 | struct fmt::formatter 45 | : std::conditional_t, formatter> 46 | { 47 | constexpr static auto fmt_string = static_cast(T::fmt_string); 48 | 49 | template 50 | auto format(const T& val, FormatContext& ctx) const 51 | { 52 | if constexpr (fmt_string == "{}") { 53 | return formatter::format(val.template unwrap(), ctx); 54 | } else { 55 | return formatter::format(fmt::format(FMT_COMPILE(fmt_string), val.template unwrap()), 56 | ctx); 57 | } 58 | } 59 | }; 60 | 61 | /** 62 | * @brief Allows all stronk values to be fmt formattable. 63 | * Use the can_fmt_format skill to specify the format string. 64 | */ 65 | template 66 | requires(!twig::can_special_fmt_format_like) 67 | struct fmt::formatter : formatter 68 | { 69 | template 70 | auto format(const T& val, FormatContext& ctx) const 71 | { 72 | return formatter::format(val.template unwrap(), ctx); 73 | } 74 | }; 75 | -------------------------------------------------------------------------------- /include/stronk/extensions/glaze.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "stronk/extensions/glaze.hpp" // IWYU pragma: keep 3 | -------------------------------------------------------------------------------- /include/stronk/extensions/glaze.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // IWYU pragma: always_keep 3 | 4 | #include 5 | 6 | #include "stronk/stronk.hpp" 7 | 8 | template 9 | struct glz::meta 10 | { 11 | using T = StronkT; 12 | constexpr static auto value = &T::_you_should_not_be_using_this_but_rather_unwrap; 13 | }; 14 | -------------------------------------------------------------------------------- /include/stronk/extensions/gtest.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "stronk/extensions/gtest.hpp" // IWYU pragma: keep 3 | -------------------------------------------------------------------------------- /include/stronk/extensions/gtest.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "stronk/stronk.hpp" 7 | 8 | namespace twig 9 | { 10 | 11 | template 12 | concept is_stronk_and_can_ostream = stronk_like && requires(T v, std::ostream& os) { 13 | { 14 | os << v.template unwrap() 15 | } -> std::same_as; 16 | }; 17 | 18 | template 19 | void PrintTo(const StronkT& val, std::ostream* os) // NOLINT(readability-identifier-naming) 20 | { 21 | *os << val.template unwrap(); 22 | } 23 | 24 | } // namespace twig 25 | -------------------------------------------------------------------------------- /include/stronk/extensions/nlohmann_json.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "stronk/extensions/nlohmann_json.hpp" // IWYU pragma: keep 3 | -------------------------------------------------------------------------------- /include/stronk/extensions/nlohmann_json.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // IWYU pragma: always_keep 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "stronk/stronk.hpp" 9 | 10 | template 11 | struct nlohmann::adl_serializer 12 | { 13 | static void to_json(json& j, const T& value) 14 | { 15 | j = value.template unwrap(); 16 | } 17 | 18 | static void from_json(const json& j, T& value) 19 | { 20 | j.get_to(value.template unwrap()); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /include/stronk/prefabs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // TODO(anders.wind) remove once downstream includes the right headers 3 | #include "stronk/prefabs/stronk_flag.hpp" // IWYU pragma: keep 4 | -------------------------------------------------------------------------------- /include/stronk/prefabs/stronk_flag.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "stronk/skills/can_be_used_as_flag.hpp" 4 | #include "stronk/stronk.hpp" 5 | 6 | namespace twig 7 | { 8 | 9 | template typename... Skills> 10 | using stronk_flag = stronk; 11 | 12 | } // namespace twig 13 | -------------------------------------------------------------------------------- /include/stronk/skills/can_abs.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "stronk/stronk.hpp" 5 | 6 | namespace twig 7 | { 8 | 9 | template 10 | struct can_abs 11 | { 12 | [[nodiscard]] 13 | constexpr auto abs() const noexcept -> StronkT 14 | { 15 | return StronkT {std::abs(static_cast(*this).template unwrap())}; 16 | } 17 | }; 18 | 19 | template 20 | concept can_abs_like = stronk_like && requires(T v) { 21 | { 22 | v.abs() 23 | } -> std::same_as; 24 | }; 25 | 26 | [[nodiscard]] 27 | constexpr auto abs(can_abs_like auto elem) noexcept 28 | { 29 | return elem.abs(); 30 | } 31 | 32 | } // namespace twig 33 | -------------------------------------------------------------------------------- /include/stronk/skills/can_be_used_as_flag.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | namespace twig 5 | { 6 | 7 | template 8 | struct can_be_used_as_flag 9 | { 10 | [[nodiscard]] 11 | constexpr auto is_on() const noexcept -> bool 12 | { 13 | static_assert(std::same_as); 14 | return static_cast(*this).template unwrap(); 15 | } 16 | 17 | [[nodiscard]] 18 | constexpr auto is_off() const noexcept -> bool 19 | { 20 | return !this->is_on(); 21 | } 22 | 23 | [[nodiscard]] 24 | constexpr static auto on() noexcept -> StronkT 25 | { 26 | static_assert(std::same_as); 27 | return StronkT {true}; 28 | } 29 | 30 | [[nodiscard]] 31 | constexpr static auto off() noexcept -> StronkT 32 | { 33 | static_assert(std::same_as); 34 | return StronkT {false}; 35 | } 36 | }; 37 | 38 | } // namespace twig 39 | -------------------------------------------------------------------------------- /include/stronk/skills/can_decrement.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace twig 4 | { 5 | 6 | template 7 | struct can_decrement 8 | { 9 | constexpr auto operator--() noexcept -> StronkT& 10 | { 11 | --static_cast(*this).template unwrap(); 12 | return static_cast(*this); 13 | } 14 | 15 | constexpr auto operator--(int) noexcept -> StronkT 16 | { 17 | auto copy = static_cast(*this); 18 | --static_cast(*this).template unwrap(); 19 | return copy; 20 | } 21 | }; 22 | 23 | } // namespace twig 24 | -------------------------------------------------------------------------------- /include/stronk/skills/can_divide.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /** 3 | * @brief Consider when using these skills to instead make your type a proper unit using the stronk/unit.hpp header 4 | * 5 | */ 6 | 7 | namespace twig 8 | { 9 | 10 | template 11 | struct can_divide 12 | { 13 | constexpr friend auto operator/=(StronkT& lhs, const StronkT& rhs) noexcept -> StronkT 14 | { 15 | lhs.template unwrap() /= rhs.template unwrap(); 16 | return lhs; 17 | } 18 | 19 | constexpr friend auto operator/(const StronkT& lhs, const StronkT& rhs) noexcept -> StronkT 20 | { 21 | return StronkT {lhs.template unwrap() / rhs.template unwrap()}; 22 | } 23 | }; 24 | 25 | template 26 | struct can_divide_with 27 | { 28 | template 29 | struct skill 30 | { 31 | constexpr friend auto operator/=(StronkT& lhs, const T& rhs) noexcept -> StronkT 32 | { 33 | lhs.template unwrap() /= rhs; 34 | return lhs; 35 | } 36 | 37 | constexpr friend auto operator/(const StronkT& lhs, const T& rhs) noexcept -> StronkT 38 | { 39 | return StronkT {lhs.template unwrap() / rhs}; 40 | } 41 | }; 42 | }; 43 | 44 | } // namespace twig 45 | -------------------------------------------------------------------------------- /include/stronk/skills/can_format.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // IWYU pragma: always_keep 3 | 4 | #include 5 | 6 | #include "stronk/stronk.hpp" 7 | 8 | template 9 | struct std::formatter // NOLINT(cert-dcl58-cpp) this is meant to be specialized 10 | : std::formatter 11 | { 12 | constexpr auto format(const StronkT& val, std::format_context& context) const 13 | { 14 | return std::formatter::format(val.template unwrap(), context); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /include/stronk/skills/can_hash.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace twig 8 | { 9 | 10 | template 11 | struct can_hash 12 | { 13 | using can_hash_indicator = std::true_type; 14 | }; 15 | template 16 | concept can_hash_like = std::same_as; 17 | 18 | } // namespace twig 19 | template 20 | struct std::hash // NOLINT(cert-dcl58-cpp) std::hash is exempt from this rule 21 | { 22 | [[nodiscard]] 23 | auto operator()(const T& s) const noexcept -> std::size_t 24 | { 25 | return std::hash {}(s.template unwrap()); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /include/stronk/skills/can_increment.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace twig 4 | { 5 | 6 | template 7 | struct can_increment 8 | { 9 | constexpr auto operator++() noexcept -> StronkT& 10 | { 11 | ++static_cast(*this).template unwrap(); 12 | return static_cast(*this); 13 | } 14 | 15 | constexpr auto operator++(int) noexcept -> StronkT 16 | { 17 | auto copy = static_cast(*this); 18 | ++static_cast(*this).template unwrap(); 19 | return copy; 20 | } 21 | }; 22 | 23 | } // namespace twig 24 | -------------------------------------------------------------------------------- /include/stronk/skills/can_index.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace twig 4 | { 5 | 6 | template 7 | struct can_const_index 8 | { 9 | [[nodiscard]] 10 | constexpr auto operator[](const auto& indexer) const noexcept -> const auto& 11 | { 12 | return static_cast(*this).template unwrap()[indexer]; 13 | } 14 | 15 | [[nodiscard]] 16 | constexpr auto at(const auto& indexer) const -> const auto& 17 | { 18 | return static_cast(*this).template unwrap().at(indexer); 19 | } 20 | }; 21 | 22 | template 23 | struct can_index : can_const_index 24 | { 25 | using can_const_index::operator[]; 26 | using can_const_index::at; 27 | 28 | [[nodiscard]] 29 | constexpr auto operator[](const auto& indexer) noexcept -> auto& 30 | { 31 | return static_cast(*this).template unwrap()[indexer]; 32 | } 33 | 34 | [[nodiscard]] 35 | constexpr auto at(const auto& indexer) -> auto& 36 | { 37 | return static_cast(*this).template unwrap().at(indexer); 38 | } 39 | }; 40 | 41 | } // namespace twig 42 | -------------------------------------------------------------------------------- /include/stronk/skills/can_isnan.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | #include "stronk/stronk.hpp" 7 | 8 | namespace twig 9 | { 10 | 11 | template 12 | struct can_isnan 13 | { 14 | [[nodiscard]] 15 | constexpr auto isnan() const noexcept -> bool 16 | { 17 | static_assert(std::is_floating_point_v); 18 | return std::isnan(static_cast(*this).template unwrap()); 19 | } 20 | 21 | [[nodiscard]] 22 | constexpr static auto quiet_NaN() -> StronkT // NOLINT(readability-identifier-naming) 23 | { 24 | static_assert(std::is_floating_point_v); 25 | return StronkT {std::numeric_limits::quiet_NaN()}; 26 | } 27 | 28 | [[nodiscard]] 29 | constexpr static auto signaling_NaN() -> StronkT // NOLINT(readability-identifier-naming) 30 | { 31 | static_assert(std::is_floating_point_v); 32 | return StronkT {std::numeric_limits::signaling_NaN()}; 33 | } 34 | }; 35 | 36 | template 37 | concept can_isnan_like = stronk_like && requires(T v) { 38 | { 39 | v.isnan() 40 | } -> std::same_as; 41 | }; 42 | 43 | [[nodiscard]] 44 | constexpr auto isnan(can_isnan_like auto elem) noexcept -> bool 45 | { 46 | return elem.isnan(); 47 | } 48 | } // namespace twig 49 | -------------------------------------------------------------------------------- /include/stronk/skills/can_iterate.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace twig 4 | { 5 | 6 | template 7 | struct can_const_iterate 8 | { 9 | [[nodiscard]] 10 | constexpr auto begin() const noexcept 11 | { 12 | return static_cast(*this).template unwrap().begin(); 13 | } 14 | [[nodiscard]] 15 | constexpr auto end() const noexcept 16 | { 17 | return static_cast(*this).template unwrap().end(); 18 | } 19 | 20 | [[nodiscard]] 21 | constexpr auto cbegin() const noexcept 22 | { 23 | return static_cast(*this).template unwrap().begin(); 24 | } 25 | [[nodiscard]] 26 | constexpr auto cend() const noexcept 27 | { 28 | return static_cast(*this).template unwrap().end(); 29 | } 30 | }; 31 | 32 | template 33 | struct can_iterate : can_const_iterate 34 | { 35 | using can_const_iterate::begin; 36 | using can_const_iterate::end; 37 | 38 | using can_const_iterate::cbegin; 39 | using can_const_iterate::cend; 40 | 41 | [[nodiscard]] 42 | constexpr auto begin() noexcept 43 | { 44 | return static_cast(*this).template unwrap().begin(); 45 | } 46 | [[nodiscard]] 47 | constexpr auto end() noexcept 48 | { 49 | return static_cast(*this).template unwrap().end(); 50 | } 51 | }; 52 | 53 | } // namespace twig 54 | -------------------------------------------------------------------------------- /include/stronk/skills/can_multiply.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /** 3 | * @brief Consider when using these skills to instead make your type a proper unit using the stronk/unit.hpp header 4 | * 5 | */ 6 | 7 | namespace twig 8 | { 9 | 10 | template 11 | struct can_multiply 12 | { 13 | constexpr friend auto operator*=(StronkT& lhs, const StronkT& rhs) noexcept -> StronkT 14 | { 15 | lhs.template unwrap() *= rhs.template unwrap(); 16 | return lhs; 17 | } 18 | 19 | constexpr friend auto operator*(const StronkT& lhs, const StronkT& rhs) noexcept -> StronkT 20 | { 21 | return StronkT {lhs.template unwrap() * rhs.template unwrap()}; 22 | } 23 | }; 24 | 25 | template 26 | struct can_multiply_with 27 | { 28 | template 29 | struct skill 30 | { 31 | constexpr friend auto operator*=(StronkT& lhs, const T& rhs) noexcept -> StronkT 32 | { 33 | lhs.template unwrap() *= rhs; 34 | return lhs; 35 | } 36 | 37 | constexpr friend auto operator*(const StronkT& lhs, const T& rhs) noexcept -> StronkT 38 | { 39 | return StronkT {lhs.template unwrap() * rhs}; 40 | } 41 | 42 | constexpr friend auto operator*(const T& lhs, const StronkT& rhs) noexcept -> StronkT 43 | { 44 | return StronkT {lhs * rhs.template unwrap()}; 45 | } 46 | }; 47 | }; 48 | 49 | } // namespace twig 50 | -------------------------------------------------------------------------------- /include/stronk/skills/can_stream.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace twig 5 | { 6 | 7 | template 8 | struct can_ostream 9 | { 10 | friend auto operator<<(std::ostream& os, const StronkT& elem) -> std::ostream& 11 | { 12 | return os << elem.template unwrap(); 13 | } 14 | }; 15 | 16 | template 17 | struct can_istream 18 | { 19 | friend auto operator>>(std::istream& is, StronkT& elem) -> std::istream& 20 | { 21 | return is >> elem.template unwrap(); 22 | } 23 | }; 24 | 25 | template 26 | struct can_stream 27 | : can_ostream 28 | , can_istream 29 | { 30 | }; 31 | 32 | } // namespace twig 33 | -------------------------------------------------------------------------------- /include/stronk/stronk.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // TODO(anders.wind) remove once downstream includes the right headers 3 | 4 | #include "stronk/stronk.hpp" // IWYU pragma: keep 5 | 6 | // here because we want to be backwards compatible, fix when downstream has been updated to import these directly 7 | #include "stronk/skills/can_abs.hpp" // IWYU pragma: keep 8 | #include "stronk/skills/can_be_used_as_flag.hpp" // IWYU pragma: keep 9 | #include "stronk/skills/can_divide.hpp" // IWYU pragma: keep 10 | #include "stronk/skills/can_index.hpp" // IWYU pragma: keep 11 | #include "stronk/skills/can_isnan.hpp" // IWYU pragma: keep 12 | #include "stronk/skills/can_iterate.hpp" // IWYU pragma: keep 13 | #include "stronk/skills/can_multiply.hpp" // IWYU pragma: keep 14 | -------------------------------------------------------------------------------- /include/stronk/stronk.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "stronk/utilities/equality.hpp" 8 | #include "stronk/utilities/macros.hpp" 9 | 10 | namespace twig 11 | { 12 | 13 | template typename... Skills> 14 | struct stronk : public Skills... 15 | { 16 | using self_t = stronk; 17 | using underlying_type = T; 18 | 19 | // we need the underlying value to have public visibility for stronk types to be usable as non-type template 20 | // parameters. This is to fulfill the `literal class type` requirement. 21 | // To discourage direct usage of the underlying value, we have given it a long ugly name. 22 | T _you_should_not_be_using_this_but_rather_unwrap; // NOLINT(readability-identifier-naming) 23 | 24 | constexpr stronk() noexcept(std::is_nothrow_default_constructible_v) = default; 25 | 26 | template 27 | requires(std::convertible_to 28 | && !std::same_as) 29 | STRONK_FORCEINLINE constexpr explicit stronk(ConvertConstructibleT&& value) 30 | : _you_should_not_be_using_this_but_rather_unwrap(std::forward(value)) 31 | { 32 | } 33 | 34 | template 35 | requires(std::constructible_from 36 | && sizeof...(ConvertConstructibleTs) >= 2) 37 | STRONK_FORCEINLINE constexpr explicit stronk(ConvertConstructibleTs&&... values) 38 | : _you_should_not_be_using_this_but_rather_unwrap(std::forward(values)...) 39 | { 40 | } 41 | 42 | template 43 | [[nodiscard]] 44 | constexpr auto unwrap() noexcept -> underlying_type& 45 | { 46 | static_assert(std::same_as, 47 | "To access the underlying type you need to provide the stronk type you expect to be querying. By " 48 | "doing so you will be protected from unsafe accesses if you chose to change the type"); 49 | return this->_you_should_not_be_using_this_but_rather_unwrap; 50 | } 51 | 52 | template 53 | [[nodiscard]] 54 | constexpr auto unwrap() const noexcept -> const underlying_type& 55 | { 56 | static_assert(std::same_as, 57 | "To access the underlying type you need to provide the stronk type you expect to be querying. By " 58 | "doing so you will be protected from unsafe accesses if you chose to change the type"); 59 | return this->_you_should_not_be_using_this_but_rather_unwrap; 60 | } 61 | 62 | // useful for recursively getting an underlying type - in case of stronk types of stronk types, or to ensure that 63 | // the underlying type is what you expect. 64 | template 65 | [[nodiscard]] 66 | constexpr auto unwrap_as() noexcept -> ExpectedUnderlyingT& 67 | { 68 | if constexpr (std::same_as) { 69 | return this->unwrap(); 70 | } else { 71 | return this->unwrap().template unwrap_as(); 72 | } 73 | } 74 | 75 | // useful for recursively getting an underlying type - in case of stronk types of stronk types, or to ensure that 76 | // the underlying type is what you expect. 77 | template 78 | [[nodiscard]] 79 | constexpr auto unwrap_as() const noexcept -> const ExpectedUnderlyingT& 80 | { 81 | if constexpr (std::same_as) { 82 | return this->unwrap(); 83 | } else { 84 | return this->unwrap().template unwrap_as(); 85 | } 86 | } 87 | 88 | // unwraps the type and returns a new StronkT with the result of the function 89 | template 90 | [[nodiscard]] 91 | constexpr auto transform(const FunctorT& functor) const -> Tag 92 | { 93 | return Tag {functor(this->val())}; 94 | } 95 | 96 | constexpr friend void swap(stronk& a, stronk& b) noexcept 97 | { 98 | using std::swap; 99 | swap(a._you_should_not_be_using_this_but_rather_unwrap, b._you_should_not_be_using_this_but_rather_unwrap); 100 | } 101 | 102 | protected: 103 | [[nodiscard]] 104 | constexpr auto val() noexcept -> T& 105 | { 106 | return this->_you_should_not_be_using_this_but_rather_unwrap; 107 | } 108 | [[nodiscard]] 109 | constexpr auto val() const noexcept -> const T& 110 | { 111 | return this->_you_should_not_be_using_this_but_rather_unwrap; 112 | } 113 | }; 114 | 115 | template 116 | concept stronk_like = requires(T v) { 117 | { 118 | v.template unwrap() 119 | } -> std::convertible_to; 120 | }; 121 | 122 | template 123 | struct can_negate 124 | { 125 | constexpr friend auto operator-(const StronkT& elem) noexcept -> StronkT 126 | { 127 | return StronkT {-elem.template unwrap()}; 128 | } 129 | }; 130 | 131 | template 132 | struct can_add 133 | { 134 | constexpr friend auto operator+=(StronkT& lhs, const StronkT& rhs) noexcept -> StronkT 135 | { 136 | lhs.template unwrap() += rhs.template unwrap(); 137 | return lhs; 138 | } 139 | 140 | constexpr friend auto operator+(const StronkT& lhs, const StronkT& rhs) noexcept -> StronkT 141 | { 142 | return StronkT {lhs.template unwrap() + rhs.template unwrap()}; 143 | } 144 | }; 145 | 146 | template 147 | struct can_subtract 148 | { 149 | constexpr friend auto operator-=(StronkT& lhs, const StronkT& rhs) noexcept -> StronkT 150 | { 151 | lhs.template unwrap() -= rhs.template unwrap(); 152 | return lhs; 153 | } 154 | 155 | constexpr friend auto operator-(const StronkT& lhs, const StronkT& rhs) noexcept -> StronkT 156 | { 157 | return StronkT {lhs.template unwrap() - rhs.template unwrap()}; 158 | } 159 | }; 160 | 161 | template 162 | struct can_equate 163 | { 164 | constexpr friend auto operator==(const StronkT& lhs, const StronkT& rhs) noexcept -> bool 165 | { 166 | static_assert(!std::is_floating_point_v); 167 | return lhs.template unwrap() == rhs.template unwrap(); 168 | } 169 | }; 170 | 171 | template 172 | struct can_less_than_greater_than 173 | { 174 | constexpr friend auto operator<(const StronkT& lhs, const StronkT& rhs) noexcept -> bool 175 | { 176 | return lhs.template unwrap() < rhs.template unwrap(); 177 | } 178 | constexpr friend auto operator>(const StronkT& lhs, const StronkT& rhs) noexcept -> bool 179 | { 180 | return lhs.template unwrap() > rhs.template unwrap(); 181 | } 182 | }; 183 | 184 | template 185 | struct can_less_than_greater_than_or_equal 186 | { 187 | constexpr friend auto operator<=(const StronkT& lhs, const StronkT& rhs) noexcept -> bool 188 | { 189 | return lhs.template unwrap() <= rhs.template unwrap(); 190 | } 191 | constexpr friend auto operator>=(const StronkT& lhs, const StronkT& rhs) noexcept -> bool 192 | { 193 | return lhs.template unwrap() >= rhs.template unwrap(); 194 | } 195 | }; 196 | 197 | // "Parameter type" class for 'can_equate_with_is_close_base' 198 | struct is_close_params 199 | { 200 | template 201 | constexpr static auto abs_tol() 202 | { 203 | return twig::stronk_details::default_abs_tol(); 204 | } 205 | template 206 | constexpr static auto rel_tol() 207 | { 208 | return twig::stronk_details::default_rel_tol(); 209 | } 210 | constexpr static bool nan_equals = false; 211 | }; 212 | 213 | // "Parameter type" class for 'can_equate_with_is_close_base' 214 | struct is_close_with_nan_equals_params 215 | { 216 | template 217 | constexpr static auto abs_tol() 218 | { 219 | return twig::stronk_details::default_abs_tol(); 220 | } 221 | template 222 | constexpr static auto rel_tol() 223 | { 224 | return twig::stronk_details::default_rel_tol(); 225 | } 226 | constexpr static bool nan_equals = true; 227 | }; 228 | 229 | // "Parameter type" class for 'can_equate_with_is_close_base' 230 | struct is_close_using_abs_tol_only_params 231 | { 232 | template 233 | constexpr static auto abs_tol() -> T 234 | { 235 | return twig::stronk_details::default_abs_tol(); 236 | } 237 | 238 | template 239 | constexpr static auto rel_tol() -> T 240 | { 241 | return T {0}; 242 | } 243 | constexpr static bool nan_equals = false; 244 | }; 245 | 246 | template 247 | struct can_equate_with_is_close_base 248 | { 249 | constexpr friend auto operator==(const StronkT& lhs, const StronkT& rhs) noexcept -> bool 250 | { 251 | return twig::stronk_details::almost_equals(lhs, rhs); 252 | } 253 | }; 254 | 255 | template 256 | using can_equate_with_is_close = can_equate_with_is_close_base; 257 | 258 | template 259 | using can_equate_with_is_close_nan_equals = can_equate_with_is_close_base; 260 | 261 | template 262 | using can_equate_with_is_close_abs_tol_only = 263 | can_equate_with_is_close_base; 264 | 265 | template 266 | struct default_can_equate_builder 267 | { 268 | template 269 | struct skill : can_equate 270 | { 271 | }; 272 | }; 273 | 274 | template 275 | struct default_can_equate_builder 276 | { 277 | template 278 | struct skill : can_equate_with_is_close_abs_tol_only 279 | { 280 | }; 281 | }; 282 | 283 | template 284 | struct can_equate_underlying_type_specific 285 | { 286 | constexpr friend auto operator==(const StronkT& lhs, const StronkT& rhs) noexcept -> bool 287 | { 288 | if constexpr (std::is_floating_point_v) { 289 | return twig::stronk_details::almost_equals(lhs, rhs); 290 | } else { 291 | return lhs.template unwrap() == rhs.template unwrap(); 292 | } 293 | } 294 | }; 295 | 296 | template 297 | struct can_order 298 | { 299 | // note you probably also want the == operator either from can_equate or one 300 | // of the can_equate_with_is_close 301 | 302 | constexpr friend auto operator<=>(const StronkT& lhs, const StronkT& rhs) noexcept 303 | { 304 | return lhs.template unwrap() <=> rhs.template unwrap(); 305 | } 306 | }; 307 | 308 | template 309 | struct can_size 310 | { 311 | [[nodiscard]] 312 | constexpr auto size() const noexcept -> std::size_t 313 | { 314 | return static_cast(*this).template unwrap().size(); 315 | } 316 | [[nodiscard]] 317 | constexpr auto empty() const noexcept -> bool 318 | { 319 | return this->size() == static_cast(0); 320 | } 321 | }; 322 | 323 | } // namespace twig 324 | -------------------------------------------------------------------------------- /include/stronk/unit.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "stronk/unit.hpp" // IWYU pragma: keep 4 | -------------------------------------------------------------------------------- /include/stronk/unit.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include "stronk/stronk.hpp" 11 | 12 | namespace twig 13 | { 14 | 15 | // Concepts 16 | template 17 | concept unit_like = requires { 18 | typename T::dimensions_t; 19 | typename T::template value; 20 | }; 21 | 22 | template 23 | concept unit_value_like = ::twig::stronk_like && requires { typename T::unit_t; }; 24 | 25 | template 26 | concept is_unit = std::same_as; 27 | 28 | template 29 | concept scale_like = requires { 30 | T::num; 31 | T::den; 32 | }; 33 | 34 | template 35 | concept canonical_scale_like = std::same_as /*ratio is fully reduced*/ && scale_like; 36 | 37 | // Implementations 38 | 39 | // Tag should either be the unit itself, or a dimensions_like type defining the dimensions of the unit. 40 | template typename... SkillTs> 41 | struct unit 42 | { 43 | using dimensions_t = std::conditional_t, Tag, twig::create_dimensions_t>>; 44 | using scale_t = ScaleT; 45 | 46 | unit() = delete; // Do not construct this type 47 | 48 | // we recreate ratio to make sure we have the gcd version of the scale. 49 | template 50 | using scaled_t = unit; 51 | 52 | template 53 | struct value : ::twig::stronk, UnderlyingT, SkillTs...> 54 | { 55 | using unit_t = unit; 56 | using base_t = ::twig::stronk, UnderlyingT, SkillTs...>; 57 | using base_t::base_t; 58 | 59 | // can static_cast to same unit with different underlying type 60 | template 61 | requires(!std::same_as) 62 | constexpr explicit operator value() const 63 | { 64 | return value {static_cast(this->val())}; 65 | } 66 | 67 | // utility function for static casting to same unit with different underlying type 68 | template 69 | constexpr auto cast() const -> scaled_t::template value 70 | { 71 | return static_cast>(*this); 72 | } 73 | 74 | /** 75 | * @brief Convert the value to another scale 76 | * 77 | * @tparam NewUnitValueT The new unit value type to convert to with same dimensions and underlying type 78 | * @return a new unit with same dimensions and underlying type, but with the specified scale 79 | */ 80 | template 81 | requires(std::same_as 82 | && std::same_as) 83 | constexpr auto to() const -> NewUnitValueT 84 | { 85 | using new_scale_t = typename NewUnitValueT::unit_t::scale_t; 86 | using converter = std::ratio_divide; 87 | using result_value_t = scaled_t::template value; 88 | return result_value_t {static_cast(this->val() * converter::num / converter::den)}; 89 | } 90 | }; 91 | }; 92 | 93 | template<> 94 | struct unit<::twig::create_dimensions_t<>, std::ratio<1>> 95 | { 96 | using dimensions_t = ::twig::create_dimensions_t<>; 97 | using scale_t = std::ratio<1>; 98 | using unit_t = unit; 99 | 100 | unit() = delete; // Do not construct this type 101 | 102 | template 103 | using value = UnderlyingT; 104 | 105 | template 106 | using scaled_t = unit<::twig::create_dimensions_t<>, OtherScaleT>; 107 | }; 108 | using identity_unit = unit<::twig::create_dimensions_t<>, std::ratio<1>>; 109 | 110 | template 111 | using identity_unit_t = unit<::twig::create_dimensions_t<>, ScaleT>; 112 | 113 | template 114 | using identity_value_t = identity_unit_t::template value; 115 | 116 | template typename... Skills> 117 | using stronk_default_unit = 118 | unit; 119 | 120 | template 121 | using unit_value_t = typename UnitT::template value; 122 | 123 | template 124 | using unit_scaled_value_t = typename UnitT::template scaled_t::template value; 125 | 126 | template 127 | using unit_scaled_or_base_t = 128 | std::conditional_t>, UnitT, typename UnitT::template scaled_t>; 129 | 130 | /** 131 | * @brief Lookup which specific type is the type for the given Dimensions. 132 | */ 133 | template<::twig::dimensions_like DimensionsT> 134 | struct unit_lookup 135 | { 136 | template 137 | using unit_t = stronk_default_unit; 138 | }; 139 | 140 | template<> 141 | struct unit_lookup<::twig::create_dimensions_t<>> 142 | { 143 | template 144 | using unit_t = identity_unit::scaled_t; 145 | }; 146 | 147 | template<::twig::dimensions_like DimensionsT> 148 | requires(DimensionsT::is_pure()) 149 | struct unit_lookup 150 | { 151 | template 152 | using unit_t = unit_scaled_or_base_t; 153 | }; 154 | 155 | // ================== 156 | // Multiply 157 | // ================== 158 | 159 | template 160 | using multiplied_dimensions_t = typename A::dimensions_t::template multiply_t; 161 | 162 | template 163 | using multiplied_unit_t = typename unit_lookup>::template unit_t< 164 | std::ratio_multiply>; 165 | 166 | // You can specialize this struct if you want another underlying multiply operation 167 | template 168 | struct underlying_multiply_operation 169 | { 170 | using res_type = 171 | decltype(std::declval() * std::declval()); 172 | 173 | STRONK_FORCEINLINE 174 | constexpr static auto multiply(const typename T1::underlying_type& v1, 175 | const typename T2::underlying_type& v2) noexcept -> res_type 176 | { 177 | return v1 * v2; 178 | } 179 | }; 180 | 181 | template 182 | STRONK_FORCEINLINE constexpr auto operator*(const A& a, const B& b) noexcept 183 | { 184 | auto res = underlying_multiply_operation::multiply(a.template unwrap(), b.template unwrap()); 185 | 186 | using resulting_unit = multiplied_unit_t; 187 | using underlying_t = decltype(res); 188 | 189 | using value_t = typename resulting_unit::template value; 190 | return value_t {res}; 191 | } 192 | 193 | template 194 | requires(!unit_value_like) 195 | constexpr auto operator*(const T& a, const B& b) noexcept -> B 196 | { 197 | return B {a * b.template unwrap()}; 198 | } 199 | 200 | template 201 | requires(!unit_value_like) 202 | constexpr auto operator*(const A& a, const T& b) noexcept -> A 203 | { 204 | return A {a.template unwrap() * b}; 205 | } 206 | 207 | template 208 | requires(!unit_value_like) 209 | constexpr auto operator*=(A& a, const T& b) noexcept -> A& 210 | { 211 | a.template unwrap() *= b; 212 | return a; 213 | } 214 | 215 | // ================== 216 | // Divide 217 | // ================== 218 | 219 | template 220 | using divided_dimensions_t = typename A::dimensions_t::template divide_t; 221 | 222 | template 223 | using divided_unit_t = typename unit_lookup>::template unit_t< 224 | std::ratio_divide>; 225 | 226 | // You can specialize this struct if you want another underlying divide operation 227 | template 228 | struct underlying_divide_operation 229 | { 230 | using res_type = 231 | decltype(std::declval() / std::declval()); 232 | 233 | STRONK_FORCEINLINE 234 | constexpr static auto divide(const typename T1::underlying_type& v1, 235 | const typename T2::underlying_type& v2) noexcept -> res_type 236 | { 237 | return v1 / v2; 238 | } 239 | }; 240 | 241 | template 242 | STRONK_FORCEINLINE constexpr auto operator/(const A& a, const B& b) noexcept 243 | { 244 | auto res = underlying_divide_operation::divide(a.template unwrap(), b.template unwrap()); 245 | 246 | using resulting_unit = divided_unit_t; 247 | using underlying_t = decltype(res); 248 | 249 | using value_t = typename resulting_unit::template value; 250 | return value_t {res}; 251 | } 252 | 253 | template 254 | requires(!unit_value_like) 255 | constexpr auto operator/(const T& a, const B& b) noexcept 256 | { 257 | auto res = a / b.template unwrap(); 258 | using resulting_unit = divided_unit_t; 259 | using underlying_t = decltype(res); 260 | 261 | using value_t = resulting_unit::template value; 262 | return value_t {res}; 263 | } 264 | 265 | template 266 | requires(!unit_value_like) 267 | constexpr auto operator/(const A& a, const T& b) noexcept -> A 268 | { 269 | return A {a.template unwrap() / b}; 270 | } 271 | 272 | template 273 | requires(!unit_value_like) 274 | constexpr auto operator/=(A& a, const T& b) noexcept -> A& 275 | { 276 | a.template unwrap() /= b; 277 | return a; 278 | } 279 | 280 | template 281 | constexpr auto make(UnderlyingT&& value) 282 | { 283 | return unit_value_t {std::forward(value)}; 284 | } 285 | 286 | template 287 | constexpr auto make(UnderlyingT&& value) 288 | { 289 | static_assert(UnitT::dimensions_t::size() == 1, 290 | "this function semantically does not seem right with multiple units"); 291 | static_assert(UnitT::dimensions_t::first_t::rank == 1, "this function is ambiguous for none rank 1"); 292 | 293 | return unit_scaled_value_t {std::forward(value)}; 294 | } 295 | 296 | } // namespace twig 297 | -------------------------------------------------------------------------------- /include/stronk/utilities/constexpr_helpers.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace twig::stronk_details 5 | { 6 | 7 | template 8 | struct not_implemented_type : std::false_type 9 | { 10 | }; 11 | 12 | namespace variadic 13 | { 14 | 15 | template 16 | struct first_type_of 17 | { 18 | using type = T; 19 | }; 20 | 21 | template 22 | using first_type_of_t = typename first_type_of::type; 23 | 24 | } // namespace variadic 25 | 26 | } // namespace twig::stronk_details 27 | -------------------------------------------------------------------------------- /include/stronk/utilities/dimensions.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace twig 10 | { 11 | 12 | template 13 | concept dimension_like = requires(T) { 14 | typename T::unit_t; 15 | T::rank; 16 | }; 17 | 18 | template 19 | struct dimension 20 | { 21 | constexpr static auto rank = RankV; 22 | using unit_t = UnitT; 23 | 24 | template 25 | requires std::same_as 26 | using add_t = dimension; 27 | 28 | template 29 | requires std::same_as 30 | using subtract_t = dimension; 31 | 32 | using negate_t = dimension; 33 | }; 34 | 35 | namespace details 36 | { 37 | template 38 | struct dimensions; 39 | 40 | using empty_dimensions = dimensions<>; 41 | 42 | template 43 | struct dimensions_merge; 44 | 45 | // Empty Case implementation 46 | template<> 47 | struct dimensions_merge<> 48 | { 49 | template 50 | [[nodiscard]] 51 | constexpr static auto merge() 52 | { 53 | using res_t = typename AccumulatedDimensionListT::template concat_t; 54 | return res_t(); 55 | } 56 | 57 | template 58 | using merge_t = decltype(merge()); 59 | }; 60 | 61 | template 62 | struct dimensions_merge 63 | { 64 | template 65 | [[nodiscard]] 66 | constexpr static auto merge() 67 | { 68 | using res_t = typename AccumulatedDimensionListT::template concat_t; 69 | return res_t(); 70 | } 71 | 72 | template 73 | [[nodiscard]] 74 | constexpr static auto merge() 75 | { 76 | static_assert(A::rank != 0, "Cannot merge dimensions with rank 0"); 77 | static_assert(B::rank != 0, "Cannot merge dimensions with rank 0"); 78 | 79 | using boost::typeindex::ctti_type_index; 80 | constexpr auto a_equals_b = std::is_same_v; 81 | constexpr auto a_before_b = 82 | ctti_type_index::type_id().before(ctti_type_index::type_id()); 83 | 84 | if constexpr (a_equals_b) { 85 | using new_dim = typename A::template add_t; 86 | if constexpr (new_dim::rank == 0) { 87 | // The combination of the two dimensions results in a dimension with rank 0, so we can remove it 88 | return dimensions_merge::template merge(); 89 | } else { 90 | using new_merged_list = typename AccumulatedDimensionListT::template push_back_t; 91 | return dimensions_merge::template merge(); 92 | } 93 | } else if constexpr (a_before_b) { 94 | using new_merged_list = typename AccumulatedDimensionListT::template push_back_t; 95 | return dimensions_merge::template merge(); 96 | } else { 97 | using new_merged_list = typename AccumulatedDimensionListT::template push_back_t; 98 | return dimensions_merge::template merge(); 99 | } 100 | } 101 | 102 | template 103 | using merge_t = decltype(merge()); 104 | }; 105 | 106 | // Use create_dimensions_t to instantiate this type 107 | template 108 | struct dimensions 109 | { 110 | [[nodiscard]] 111 | constexpr static auto size() -> std::size_t 112 | { 113 | return sizeof...(Ts); 114 | } 115 | 116 | [[nodiscard]] 117 | constexpr static auto empty() -> bool 118 | { 119 | return size() == 0; 120 | } 121 | 122 | [[nodiscard]] 123 | constexpr static auto is_pure() -> bool 124 | { 125 | return size() == 1 && first_t::rank == 1; 126 | } 127 | 128 | using first_t = stronk_details::variadic::first_type_of_t>; // void if empty 129 | 130 | template 131 | using push_back_t = dimensions; 132 | 133 | template 134 | using concat_t = dimensions; 135 | 136 | using negate_t = dimensions; 137 | 138 | template 139 | static auto multiply([[maybe_unused]] dimensions dims) 140 | { 141 | return dimensions_merge::template merge, Bs...>(); 142 | } 143 | template 144 | using multiply_t = decltype(multiply(OtherDimensionsT {})); 145 | 146 | template 147 | static auto divide([[maybe_unused]] dimensions dims) 148 | { 149 | using negated_b = typename dimensions::negate_t; 150 | return multiply(negated_b {}); 151 | } 152 | template 153 | using divide_t = decltype(divide(OtherDimensionsT {})); 154 | }; 155 | 156 | template 157 | auto create(dimensions dim) 158 | { 159 | return dim; 160 | } 161 | 162 | template 163 | auto create(dimensions dims, [[maybe_unused]] Dim dim_for_type_deduction, DimTs... rest) 164 | { 165 | return create(dims.multiply(details::dimensions {}), rest...); 166 | } 167 | 168 | } // namespace details 169 | 170 | using empty_dimensions = details::empty_dimensions; 171 | template 172 | concept dimensions_like = requires(T) { 173 | typename T::first_t; 174 | T::size(); 175 | T::empty(); 176 | typename T::negate_t; 177 | typename T::template multiply_t; 178 | typename T::template divide_t; 179 | }; 180 | 181 | // Ensures order and uniqueness of dimensions 182 | template 183 | auto create_dimensions(DimTs... dims) 184 | { 185 | return details::create(details::empty_dimensions {}, dims...); 186 | } 187 | 188 | template 189 | using create_dimensions_t = decltype(create_dimensions(DimTs {}...)); 190 | 191 | } // namespace twig 192 | -------------------------------------------------------------------------------- /include/stronk/utilities/equality.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | namespace twig::stronk_details 8 | { 9 | 10 | template 11 | inline constexpr auto default_abs_tol = []() -> T 12 | { 13 | if constexpr (std::is_same_v) { 14 | return T {1e-5F}; 15 | } else if constexpr (std::is_same_v) { 16 | return T {1e-8}; 17 | } else { 18 | static_assert(not_implemented_type()); 19 | } 20 | }; 21 | 22 | template 23 | inline constexpr auto default_rel_tol = []() -> T 24 | { 25 | if constexpr (std::is_same_v) { 26 | return T {1e-3F}; 27 | } else if constexpr (std::is_same_v) { 28 | return T {1e-5}; 29 | } else { 30 | static_assert(not_implemented_type()); 31 | } 32 | }; 33 | 34 | /** 35 | * @brief create a comparator lambda for floating points with given absolute and relative tolerances 36 | * 37 | */ 38 | template 39 | constexpr auto is_close(T abs_tol, T rel_tol, bool nan_equals = false) 40 | { 41 | return [abs_tol, rel_tol, nan_equals](const T& a, const T& b) -> bool 42 | { 43 | // Taken from https://numpy.org/devdocs/reference/generated/numpy.allclose.html 44 | auto val_equals = std::abs(a - b) <= (abs_tol + rel_tol * std::abs(b)); 45 | auto both_nan = nan_equals && std::isnan(a) && std::isnan(b); 46 | return both_nan || val_equals; 47 | }; 48 | }; 49 | 50 | /** 51 | * @brief compare two floating point values with the given abs_tol or rel_tol; 52 | * 53 | */ 54 | template 55 | constexpr auto is_close(const T& a, const T& b, T abs_tol, T rel_tol, bool nan_equals = false) -> bool 56 | { 57 | return is_close(abs_tol, rel_tol, nan_equals)(a, b); 58 | }; 59 | 60 | template 61 | constexpr auto almost_equals(const StronkT& lhs, const StronkT& rhs) noexcept -> bool 62 | { 63 | static_assert(std::is_floating_point_v); 64 | using type = typename StronkT::underlying_type; 65 | auto inner_val_1 = lhs.template unwrap(); 66 | auto inner_val_2 = rhs.template unwrap(); 67 | 68 | constexpr auto abs_tol = CloseParamsT::template abs_tol(); 69 | constexpr auto rel_tol = CloseParamsT::template rel_tol(); 70 | return twig::stronk_details::is_close(abs_tol, rel_tol, CloseParamsT::nan_equals)(inner_val_1, inner_val_2); 71 | } 72 | 73 | } // namespace twig::stronk_details 74 | -------------------------------------------------------------------------------- /include/stronk/utilities/macros.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if defined(_MSC_VER) 4 | # define STRONK_FORCEINLINE __forceinline // NOLINT 5 | #elif defined(__clang__) 6 | # define STRONK_FORCEINLINE __attribute__((always_inline)) // NOLINT 7 | #elif defined(__GNUC__) 8 | # define STRONK_FORCEINLINE __attribute__((always_inline)) // NOLINT 9 | #else 10 | # define STRONK_FORCEINLINE // NOLINT 11 | #endif 12 | -------------------------------------------------------------------------------- /include/stronk/utilities/strings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace twig::stronk_details::str 7 | { 8 | 9 | // A string literal, useful for templating on compile time known strings. 10 | template 11 | struct string_literal 12 | { 13 | std::array value {}; 14 | 15 | consteval explicit(false) string_literal(const char (&str)[N]) noexcept // NOLINT 16 | { 17 | for (auto i = 0; i < N; i++) { 18 | value[i] = str[i]; 19 | } 20 | } 21 | 22 | constexpr explicit operator std::string_view() const noexcept 23 | { 24 | return std::string_view {this->value.data(), N - 1}; 25 | } 26 | 27 | constexpr auto operator==(std::string_view other) const noexcept -> bool 28 | { 29 | return static_cast(*this) == other; 30 | } 31 | }; 32 | 33 | } // namespace twig::stronk_details::str 34 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=twig-energy_stronk 2 | sonar.organization=twig-energy 3 | 4 | # This is the name and version displayed in the SonarCloud UI. 5 | #sonar.projectName=stronk 6 | #sonar.projectVersion=1.0 7 | 8 | # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. 9 | #sonar.sources=. 10 | 11 | # Encoding of the source code. Default is default system encoding 12 | #sonar.sourceEncoding=UTF-8 13 | 14 | sonar.sources=. 15 | sonar.exclusions=llvm/**/*,version.py 16 | sonar.tests=tests 17 | sonar.test.inclusions=tests/**/*.cpp,tests/**/*.h,tests/**/*.hpp 18 | sonar.cfamily.llvm-cov.reportPath=build/ccov/all-merged_coverage.txt 19 | 20 | # dimensions is purely consteval 21 | sonar.coverage.exclusions=include/stronk/utilities/dimensions.hpp,examples/**/*.cpp,tools/* 22 | -------------------------------------------------------------------------------- /tests/.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | InheritParentConfig: "true" 3 | Checks: "\ 4 | -bugprone-easily-swappable-parameters\ 5 | ,-cppcoreguidelines-avoid-magic-numbers\ 6 | ,-readability-function-cognitive-complexity\ 7 | ,-readability-magic-numbers\ 8 | ,-readability-identifier-naming\ 9 | ,-google-readability-avoid-underscore-in-googletest-name\ 10 | ,-modernize-use-ranges\ 11 | " 12 | CheckOptions: 13 | - key: "readability-identifier-length.MinimumVariableNameLength" 14 | value: "1" 15 | - key: "readability-identifier-length.MinimumParameterNameLength" 16 | value: "1" 17 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.28) 2 | 3 | project(stronkTests LANGUAGES CXX) 4 | 5 | include(../cmake/project-is-top-level.cmake) 6 | include(../cmake/folders.cmake) 7 | 8 | # ---- Dependencies ---- 9 | if (PROJECT_IS_TOP_LEVEL) 10 | find_package(stronk REQUIRED) 11 | enable_testing() 12 | endif () 13 | 14 | find_package(GTest CONFIG REQUIRED) 15 | find_package(fmt CONFIG REQUIRED) 16 | find_package(nlohmann_json CONFIG REQUIRED) 17 | find_package(glaze CONFIG REQUIRED) 18 | find_package(absl CONFIG REQUIRED) 19 | find_package(doctest CONFIG REQUIRED) 20 | 21 | # ---- Tests ---- 22 | add_executable( 23 | stronk_test 24 | src/extensions/absl_tests.cpp 25 | src/extensions/doctest_tests.cpp 26 | src/extensions/fmt_tests.cpp 27 | src/extensions/glaze_tests.cpp 28 | src/extensions/gtest_tests.cpp 29 | src/extensions/nlohmann_json_tests.cpp 30 | src/prefabs/stronk_flag_tests.cpp 31 | src/skills/can_abs_tests.cpp 32 | src/skills/can_decrement_tests.cpp 33 | src/skills/can_divide_tests.cpp 34 | src/skills/can_format_tests.cpp 35 | src/skills/can_hash_tests.cpp 36 | src/skills/can_increment_tests.cpp 37 | src/skills/can_index_tests.cpp 38 | src/skills/can_isnan_tests.cpp 39 | src/skills/can_iterate_tests.cpp 40 | src/skills/can_multiply_tests.cpp 41 | src/skills/can_stream_tests.cpp 42 | src/specializers_tests.cpp 43 | src/stronk_tests.cpp 44 | src/unit_tests.cpp 45 | src/utilities/dimensions_tests.cpp 46 | ) 47 | 48 | target_link_libraries( 49 | stronk_test 50 | PRIVATE absl::hash 51 | doctest::doctest 52 | fmt::fmt 53 | glaze::glaze 54 | GTest::gtest_main 55 | nlohmann_json::nlohmann_json 56 | twig::stronk 57 | ) 58 | target_compile_features(stronk_test PRIVATE cxx_std_20) 59 | target_compile_options( 60 | stronk_test 61 | PRIVATE # right now we don't know how to conversions inside units nicely 62 | $<$,$,$>: 63 | -Wno-implicit-int-float-conversion 64 | -Wno-sign-conversion 65 | -Wno-implicit-int-conversion> 66 | ) 67 | 68 | add_test(NAME stronk_test COMMAND stronk_test "--gtest_color=yes" "--gtest_output=xml:") 69 | 70 | # ---- End-of-file commands ---- 71 | add_folders(Tests) 72 | 73 | # ---- Code coverage ---- 74 | if (ENABLE_COVERAGE) 75 | target_code_coverage( 76 | stronk_test 77 | ALL 78 | EXCLUDE 79 | src 80 | ) 81 | endif () 82 | -------------------------------------------------------------------------------- /tests/src/extensions/absl_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "stronk/extensions/absl.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include "stronk/stronk.hpp" 7 | 8 | namespace twig 9 | { 10 | 11 | struct a_can_absl_hash_type : stronk 12 | { 13 | using stronk::stronk; 14 | }; 15 | 16 | TEST(can_absl_hash, can_absl_hash_adds_the_absl_hash_function) // NOLINT 17 | { 18 | for (auto i = -10; i < 10; i++) { 19 | EXPECT_EQ(absl::HashOf(a_can_absl_hash_type(i)), absl::HashOf(i)); 20 | } 21 | } 22 | 23 | } // namespace twig 24 | -------------------------------------------------------------------------------- /tests/src/extensions/doctest_tests.cpp: -------------------------------------------------------------------------------- 1 | #define DOCTEST_CONFIG_IMPLEMENT 2 | 3 | #include "stronk/extensions/doctest.hpp" 4 | 5 | #include 6 | #include 7 | 8 | #include "stronk/stronk.hpp" 9 | 10 | namespace twig 11 | { 12 | 13 | struct a_type_to_doctest_print : stronk 14 | { 15 | using stronk::stronk; 16 | }; 17 | 18 | struct a_recursive_type_to_doctest_print : stronk 19 | { 20 | using stronk::stronk; 21 | }; 22 | 23 | TEST(can_doctest_extend, when_given_a_stronk_type_doctest_can_convert_the_underlying_string) // NOLINT 24 | { 25 | EXPECT_EQ(doctest::StringMaker::convert(a_type_to_doctest_print {5}), "5"); 26 | EXPECT_EQ(doctest::StringMaker::convert( 27 | a_recursive_type_to_doctest_print {a_type_to_doctest_print {42}}), 28 | "42"); 29 | } 30 | 31 | } // namespace twig 32 | -------------------------------------------------------------------------------- /tests/src/extensions/fmt_tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #if !defined(__GNUC__) || defined(__clang__) || (__GNUC__ >= 12) 3 | 4 | # include 5 | # include 6 | 7 | # include "stronk/extensions/fmt.hpp" 8 | # include "stronk/stronk.hpp" 9 | 10 | namespace twig 11 | { 12 | 13 | struct a_formattable_type : stronk::skill> 14 | { 15 | using stronk::stronk; 16 | }; 17 | 18 | static_assert(a_formattable_type::fmt_string == "a_formattable_type({})"); 19 | static_assert(can_special_fmt_format_like); 20 | 21 | TEST(can_fmt_format_builder, format_string_is_correctly_applied_via_can_fmt_format_builder) // NOLINT 22 | { 23 | EXPECT_EQ(fmt::format("{}", a_formattable_type {5}), "a_formattable_type(5)"); 24 | EXPECT_EQ(fmt::format("{}", a_formattable_type {-12}), "a_formattable_type(-12)"); 25 | } 26 | 27 | struct a_float_formattable_type : stronk::skill> 28 | { 29 | using stronk::stronk; 30 | }; 31 | 32 | TEST(can_fmt_format_builder, type_format_string_can_have_format_specifiers_applied) // NOLINT 33 | { 34 | EXPECT_EQ(fmt::format("{}", a_float_formattable_type {42.0F}), "42.0000"); 35 | EXPECT_EQ(fmt::format("{:*^30}", a_float_formattable_type {42.0F}), "***********42.0000************"); 36 | } 37 | 38 | struct a_default_formattable_type : stronk 39 | { 40 | using stronk::stronk; 41 | }; 42 | 43 | TEST(can_fmt_format, format_string_is_correctly_applied_via_can_fmt_format) // NOLINT 44 | { 45 | EXPECT_EQ(fmt::format("{}", a_default_formattable_type {42}), "42"); 46 | EXPECT_EQ(fmt::format("{}", a_default_formattable_type {-1}), "-1"); 47 | } 48 | 49 | struct a_default_float_formattable_type : stronk 50 | { 51 | using stronk::stronk; 52 | }; 53 | 54 | TEST(can_fmt_format, format_string_can_have_format_specifiers_applied_if_underlying_type_supports_it) // NOLINT 55 | { 56 | EXPECT_EQ(fmt::format("{:.3f}", a_default_float_formattable_type {42.0F}), "42.000"); 57 | EXPECT_EQ(fmt::format("{:*^30}", a_default_formattable_type {1}), "**************1***************"); 58 | } 59 | 60 | } // namespace twig 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /tests/src/extensions/glaze_tests.cpp: -------------------------------------------------------------------------------- 1 | #if !defined(__GNUC__) || defined(__clang__) || (__GNUC__ >= 12) 2 | 3 | # include 4 | 5 | # include "stronk/extensions/glaze.hpp" 6 | 7 | # include 8 | # include 9 | # include 10 | # include 11 | 12 | # include "stronk/stronk.hpp" 13 | 14 | namespace twig 15 | { 16 | 17 | struct an_int_can_glaze_de_and_serialize : stronk 18 | { 19 | using stronk::stronk; 20 | }; 21 | 22 | struct an_int_can_glaze_de_and_serialize_wrapper 23 | { 24 | an_int_can_glaze_de_and_serialize a_number; 25 | 26 | auto operator==(const an_int_can_glaze_de_and_serialize_wrapper& other) const -> bool = default; 27 | 28 | struct glaze 29 | { 30 | constexpr static auto value = glz::object(&an_int_can_glaze_de_and_serialize_wrapper::a_number); 31 | }; 32 | }; 33 | 34 | TEST(can_glaze_de_and_serialize, when_serializing_stronk_integer_then_can_deserialize_to_same_value) 35 | { 36 | const auto val = an_int_can_glaze_de_and_serialize_wrapper {.a_number = an_int_can_glaze_de_and_serialize {42}}; 37 | 38 | auto json_str_opt = glz::write_json(val); 39 | ASSERT_TRUE(json_str_opt.has_value()); 40 | EXPECT_EQ(R"({"a_number":42})", json_str_opt.value()); 41 | auto val_opt = glz::read_json(json_str_opt.value()); 42 | EXPECT_TRUE(val_opt.has_value()); 43 | EXPECT_EQ(val, val_opt.value()); 44 | } 45 | 46 | struct a_string_can_glaze_de_and_serialize : stronk 47 | { 48 | using stronk::stronk; 49 | }; 50 | 51 | struct a_string_can_glaze_de_and_serialize_wrapper 52 | { 53 | a_string_can_glaze_de_and_serialize a_string; 54 | 55 | auto operator==(const a_string_can_glaze_de_and_serialize_wrapper& other) const -> bool = default; 56 | 57 | struct glaze 58 | { 59 | constexpr static auto value = glz::object(&a_string_can_glaze_de_and_serialize_wrapper::a_string); 60 | }; 61 | }; 62 | 63 | TEST(can_glaze_de_and_serialize, when_serializing_stronk_string_then_can_deserialize_to_same_value) 64 | { 65 | const auto val = 66 | a_string_can_glaze_de_and_serialize_wrapper {.a_string = a_string_can_glaze_de_and_serialize {"hello"}}; 67 | auto json_str_opt = glz::write_json(val); 68 | ASSERT_TRUE(json_str_opt.has_value()); 69 | EXPECT_EQ(R"({"a_string":"hello"})", json_str_opt.value()); 70 | EXPECT_EQ(val, glz::read_json(json_str_opt.value()).value()); 71 | } 72 | 73 | struct a_class_with_two_can_glaze_members 74 | { 75 | an_int_can_glaze_de_and_serialize val1; 76 | a_string_can_glaze_de_and_serialize val2; 77 | 78 | auto operator==(const a_class_with_two_can_glaze_members& other) const -> bool = default; 79 | 80 | struct glaze 81 | { 82 | constexpr static auto value = 83 | glz::object(&a_class_with_two_can_glaze_members::val1, &a_class_with_two_can_glaze_members::val2); 84 | }; 85 | }; 86 | 87 | TEST(can_glaze_de_and_serialize, when_serializing_a_type_with_multiple_members_it_roundtrips) 88 | { 89 | const auto val = a_class_with_two_can_glaze_members { 90 | .val1 = an_int_can_glaze_de_and_serialize {24}, 91 | .val2 = a_string_can_glaze_de_and_serialize {"world"}, 92 | }; 93 | auto json_str_opt = glz::write_json(val); 94 | ASSERT_TRUE(json_str_opt.has_value()); 95 | EXPECT_EQ(R"({"val1":24,"val2":"world"})", json_str_opt.value()); 96 | EXPECT_EQ(val, glz::read_json(json_str_opt.value()).value()); 97 | } 98 | 99 | } // namespace twig 100 | 101 | #endif 102 | -------------------------------------------------------------------------------- /tests/src/extensions/gtest_tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "stronk/extensions/gtest.hpp" 5 | 6 | #include 7 | 8 | #include "stronk/stronk.hpp" 9 | 10 | namespace twig 11 | { 12 | 13 | struct a_can_gtest_print_type : stronk 14 | { 15 | using stronk::stronk; 16 | }; 17 | 18 | TEST(can_gtest_print, streaming_to_ostream_prints_the_value) // NOLINT 19 | { 20 | auto val = a_can_gtest_print_type {5}; 21 | auto sstream = std::stringstream(); 22 | PrintTo(val, &sstream); 23 | 24 | EXPECT_EQ(sstream.str(), "5"); 25 | } 26 | 27 | struct a_cannot_gtest_print_type : stronk> 28 | { 29 | using stronk::stronk; 30 | }; 31 | static_assert(is_stronk_and_can_ostream); 32 | static_assert(!is_stronk_and_can_ostream); 33 | 34 | } // namespace twig 35 | -------------------------------------------------------------------------------- /tests/src/extensions/nlohmann_json_tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "stronk/extensions/nlohmann_json.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "stronk/stronk.hpp" 10 | 11 | namespace twig 12 | { 13 | 14 | struct a_can_nlohmann_json : stronk 15 | { 16 | using stronk::stronk; 17 | }; 18 | 19 | TEST(can_nlohmann_json, when_serializing_stronk_integer_then_can_deserialize_to_same_value) 20 | { 21 | const auto val = a_can_nlohmann_json {42}; 22 | auto json_val = nlohmann::json {}; 23 | json_val["number"] = val; 24 | EXPECT_EQ(R"({"number":42})", json_val.dump()); 25 | EXPECT_EQ(val, json_val.at("number").get()); 26 | } 27 | 28 | struct a_string_can_nlohmann_json : stronk 29 | { 30 | using stronk::stronk; 31 | }; 32 | 33 | TEST(can_nlohmann_json, when_serializing_stronk_string_then_can_deserialize_to_same_value) 34 | { 35 | const auto val = a_string_can_nlohmann_json {"hello"}; 36 | auto json_val = nlohmann::json {}; 37 | json_val["string"] = val; 38 | EXPECT_EQ(R"({"string":"hello"})", json_val.dump()); 39 | EXPECT_EQ(val, json_val.at("string").get()); 40 | } 41 | 42 | } // namespace twig 43 | -------------------------------------------------------------------------------- /tests/src/prefabs/stronk_flag_tests.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "stronk/prefabs/stronk_flag.hpp" 3 | 4 | #include 5 | 6 | #include "stronk/stronk.hpp" 7 | 8 | namespace twig 9 | { 10 | 11 | struct a_flag_type : stronk_flag 12 | { 13 | using stronk::stronk; 14 | }; 15 | 16 | static_assert(a_flag_type::on().is_on()); 17 | static_assert(!a_flag_type::on().is_off()); 18 | static_assert(!a_flag_type::off().is_on()); 19 | static_assert(a_flag_type::off().is_off()); 20 | 21 | TEST(flag, flag_values_corrosponds_to_bool_values) 22 | { 23 | EXPECT_FALSE(a_flag_type {false}.is_on()); 24 | EXPECT_TRUE(a_flag_type {false}.is_off()); 25 | EXPECT_TRUE(a_flag_type {true}.is_on()); 26 | EXPECT_FALSE(a_flag_type {true}.is_off()); 27 | 28 | EXPECT_FALSE(a_flag_type::off().is_on()); 29 | EXPECT_TRUE(a_flag_type::off().is_off()); 30 | 31 | EXPECT_TRUE(a_flag_type::on().is_on()); 32 | EXPECT_FALSE(a_flag_type::on().is_off()); 33 | } 34 | 35 | } // namespace twig 36 | -------------------------------------------------------------------------------- /tests/src/skills/can_abs_tests.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include 4 | 5 | #include "stronk/skills/can_abs.hpp" 6 | 7 | #include 8 | 9 | #include "stronk/stronk.hpp" 10 | 11 | namespace twig 12 | { 13 | 14 | struct an_abs_min_max_type : stronk 15 | { 16 | using stronk::stronk; 17 | }; 18 | 19 | TEST(can_abs, abs_is_applied_correctly) 20 | { 21 | EXPECT_EQ(twig::abs(an_abs_min_max_type {10}), an_abs_min_max_type {10}); 22 | EXPECT_EQ(twig::abs(an_abs_min_max_type {-10}), an_abs_min_max_type {10}); 23 | 24 | EXPECT_EQ(an_abs_min_max_type {10}.abs(), an_abs_min_max_type {10}); 25 | EXPECT_EQ(an_abs_min_max_type {-10}.abs(), an_abs_min_max_type {10}); 26 | } 27 | 28 | TEST(can_abs, abs_behaves_similar_to_integer_abs) 29 | { 30 | for (auto i = -16; i < 16; i++) { 31 | EXPECT_EQ(an_abs_min_max_type {std::abs(i)}, twig::abs(an_abs_min_max_type {i})); 32 | } 33 | } 34 | 35 | } // namespace twig 36 | -------------------------------------------------------------------------------- /tests/src/skills/can_decrement_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "stronk/skills/can_decrement.hpp" 2 | 3 | #include 4 | 5 | #include "stronk/stronk.hpp" 6 | 7 | namespace twig 8 | { 9 | 10 | struct an_int_decrement_test_type : stronk 11 | { 12 | using stronk::stronk; 13 | }; 14 | 15 | TEST(can_decrement, can_decrement_stronk_integer) 16 | { 17 | auto a = an_int_decrement_test_type {1}; 18 | 19 | EXPECT_EQ(--a, an_int_decrement_test_type {0}); 20 | EXPECT_EQ(a--, an_int_decrement_test_type {0}); 21 | EXPECT_EQ(a, an_int_decrement_test_type {-1}); 22 | } 23 | 24 | } // namespace twig 25 | -------------------------------------------------------------------------------- /tests/src/skills/can_divide_tests.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "stronk/skills/can_divide.hpp" 3 | 4 | #include 5 | 6 | #include "stronk/stronk.hpp" 7 | 8 | namespace twig 9 | { 10 | struct a_dividing_type : stronk 11 | { 12 | using stronk::stronk; 13 | }; 14 | 15 | TEST(can_divide, can_divide_numbers_correctly) 16 | { 17 | auto divideable1 = a_dividing_type {8}; 18 | auto divideable2 = a_dividing_type {2}; 19 | EXPECT_EQ(divideable1 / divideable2, a_dividing_type {4}); 20 | divideable1 /= divideable2; 21 | 22 | EXPECT_EQ(divideable1 / divideable2, a_dividing_type {2}); 23 | } 24 | 25 | TEST(can_divide, dividing_behaves_similar_to_integers) 26 | { 27 | for (auto i = -16; i < 16; i++) { 28 | for (auto j = -16; j < 16; j++) { 29 | if (j == 0) { 30 | continue; 31 | } 32 | EXPECT_EQ(a_dividing_type {i / j}, a_dividing_type {i} / a_dividing_type {j}); 33 | } 34 | } 35 | } 36 | 37 | struct a_can_divide_with_int_and_double_type 38 | : stronk::skill, 41 | can_divide_with::skill, 42 | can_equate_underlying_type_specific> 43 | { 44 | using stronk::stronk; 45 | }; 46 | 47 | TEST(can_divide_with, can_divide_with_integers_and_other_types) 48 | { 49 | auto val = a_can_divide_with_int_and_double_type {200}; 50 | val /= 10; 51 | val = val / 10; 52 | EXPECT_EQ(val, a_can_divide_with_int_and_double_type {2}) << val.unwrap(); 53 | val /= 0.1; 54 | val = val / 0.1; 55 | EXPECT_EQ(val, a_can_divide_with_int_and_double_type {200}); 56 | } 57 | 58 | } // namespace twig 59 | -------------------------------------------------------------------------------- /tests/src/skills/can_format_tests.cpp: -------------------------------------------------------------------------------- 1 | #if __has_include() 2 | # include 3 | 4 | # include "stronk/skills/can_format.hpp" 5 | 6 | # include 7 | 8 | # include "stronk/stronk.hpp" 9 | 10 | namespace twig 11 | { 12 | 13 | struct a_default_formattable_type : stronk 14 | { 15 | using stronk::stronk; 16 | }; 17 | 18 | TEST(can_format, format_string_is_correctly_applied_via_can_format) // NOLINT 19 | { 20 | EXPECT_EQ(std::format("{}", a_default_formattable_type {42}), "42"); 21 | EXPECT_EQ(std::format("{}", a_default_formattable_type {-1}), "-1"); 22 | } 23 | 24 | struct a_default_float_formattable_type : stronk 25 | { 26 | using stronk::stronk; 27 | }; 28 | 29 | TEST(can_format, format_string_can_have_format_specifiers_applied_if_underlying_type_supports_it) // NOLINT 30 | { 31 | EXPECT_EQ(std::format("{:.3f}", a_default_float_formattable_type {42.0F}), "42.000"); 32 | EXPECT_EQ(std::format("{:*^30}", a_default_formattable_type {1}), "**************1***************"); 33 | } 34 | 35 | } // namespace twig 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /tests/src/skills/can_hash_tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "stronk/skills/can_hash.hpp" 5 | 6 | #include 7 | 8 | #include "stronk/stronk.hpp" 9 | 10 | namespace twig 11 | { 12 | 13 | struct a_hashable_type : stronk 14 | { 15 | using stronk::stronk; 16 | }; 17 | 18 | TEST(can_hash, can_hash_overloads_std_hash) // NOLINT 19 | { 20 | for (auto i = -10; i < 10; i++) { 21 | auto hash = std::hash {}(a_hashable_type {i}); 22 | auto expected_val = std::hash {}(i); 23 | EXPECT_EQ(hash, expected_val); 24 | } 25 | } 26 | 27 | } // namespace twig 28 | -------------------------------------------------------------------------------- /tests/src/skills/can_increment_tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "stronk/skills/can_increment.hpp" 4 | 5 | #include 6 | 7 | #include "stronk/stronk.hpp" 8 | 9 | namespace twig 10 | { 11 | 12 | struct an_int_increment_test_type : stronk 13 | { 14 | using stronk::stronk; 15 | }; 16 | 17 | TEST(can_increment, can_increment_stronk_integer) 18 | { 19 | auto a = an_int_increment_test_type {-1}; 20 | 21 | EXPECT_EQ(++a, an_int_increment_test_type {0}); 22 | EXPECT_EQ(a++, an_int_increment_test_type {0}); 23 | EXPECT_EQ(a, an_int_increment_test_type {1}); 24 | } 25 | 26 | struct a_uint64_t_increment_test_type : stronk 27 | { 28 | using stronk::stronk; 29 | }; 30 | 31 | TEST(can_increment, can_increment_stronk_uint64_t) 32 | { 33 | auto a = a_uint64_t_increment_test_type {0}; 34 | 35 | EXPECT_EQ(++a, a_uint64_t_increment_test_type {1}); 36 | EXPECT_EQ(a++, a_uint64_t_increment_test_type {1}); 37 | EXPECT_EQ(a, a_uint64_t_increment_test_type {2}); 38 | } 39 | 40 | } // namespace twig 41 | -------------------------------------------------------------------------------- /tests/src/skills/can_index_tests.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | 6 | #include "stronk/skills/can_index.hpp" 7 | 8 | #include 9 | 10 | #include "stronk/stronk.hpp" 11 | 12 | namespace twig 13 | { 14 | 15 | struct a_can_index_vector_type : stronk, can_index> 16 | { 17 | using stronk::stronk; 18 | }; 19 | 20 | TEST(can_const_index, can_const_index_works_for_vectors) 21 | { 22 | for (size_t i = 0; i < 16; i++) { 23 | const auto vector = [&i]() 24 | { 25 | auto tmp = std::vector(i); 26 | std::iota(tmp.begin(), tmp.end(), -8); 27 | return a_can_index_vector_type(tmp); 28 | }(); 29 | auto curr = -8; 30 | for (size_t j = 0; j < i; j++) { 31 | EXPECT_EQ(vector.at(j), curr); 32 | EXPECT_EQ(vector[j], curr); 33 | curr++; 34 | } 35 | } 36 | } 37 | 38 | TEST(can_index, can_index_works_for_vectors) 39 | { 40 | for (size_t i = 0; i < 16; i++) { 41 | auto vector = [&i]() 42 | { 43 | auto tmp = std::vector(i); 44 | std::iota(tmp.begin(), tmp.end(), -8); 45 | return a_can_index_vector_type(tmp); 46 | }(); 47 | auto curr = -8; 48 | for (size_t j = 0; j < i; j++) { 49 | vector.at(j)++; 50 | vector[j]++; 51 | EXPECT_EQ(vector.at(j), curr + 2); 52 | EXPECT_EQ(vector[j], curr + 2); 53 | curr++; 54 | } 55 | } 56 | } 57 | 58 | } // namespace twig 59 | -------------------------------------------------------------------------------- /tests/src/skills/can_isnan_tests.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include 4 | 5 | #include "stronk/skills/can_isnan.hpp" 6 | 7 | #include 8 | 9 | #include "stronk/stronk.hpp" 10 | 11 | namespace twig 12 | { 13 | 14 | struct an_isnan_type : stronk 15 | { 16 | using stronk::stronk; 17 | }; 18 | 19 | TEST(can_isnan, isnan_works) 20 | { 21 | EXPECT_FALSE(twig::isnan(an_isnan_type {0.F})); 22 | EXPECT_FALSE(twig::isnan(an_isnan_type {1.F})); 23 | EXPECT_FALSE(twig::isnan(an_isnan_type {-1.1F})); 24 | EXPECT_TRUE(twig::isnan(an_isnan_type {std::numeric_limits::quiet_NaN()})); 25 | EXPECT_TRUE(twig::isnan(an_isnan_type {-std::numeric_limits::quiet_NaN()})); 26 | EXPECT_TRUE(twig::isnan(an_isnan_type {std::numeric_limits::signaling_NaN()})); 27 | EXPECT_TRUE(twig::isnan(an_isnan_type {-std::numeric_limits::signaling_NaN()})); 28 | } 29 | 30 | TEST(can_isnan, quiet_NaN_isnan) 31 | { 32 | EXPECT_TRUE(twig::isnan(an_isnan_type::quiet_NaN())); 33 | } 34 | 35 | TEST(can_isnan, signaling_NaN_isnan) 36 | { 37 | EXPECT_TRUE(twig::isnan(an_isnan_type::signaling_NaN())); 38 | } 39 | 40 | } // namespace twig 41 | -------------------------------------------------------------------------------- /tests/src/skills/can_iterate_tests.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | 6 | #include "stronk/skills/can_iterate.hpp" 7 | 8 | #include 9 | 10 | #include "stronk/stronk.hpp" 11 | 12 | namespace twig 13 | { 14 | 15 | struct a_can_iterate_vector_type : stronk, can_iterate> 16 | { 17 | using stronk::stronk; 18 | }; 19 | 20 | TEST(can_const_iterate, can_const_iterate_works_for_vectors) 21 | { 22 | for (size_t i = 0; i < 16; i++) { 23 | const auto vector = [&i]() 24 | { 25 | auto tmp = std::vector(i); 26 | std::iota(tmp.begin(), tmp.end(), -8); 27 | return a_can_iterate_vector_type(tmp); 28 | }(); 29 | auto curr = -8; 30 | for (const auto& val : vector) { 31 | EXPECT_EQ(val, curr); 32 | curr++; 33 | } 34 | } 35 | } 36 | 37 | TEST(can_iterate, can_iterate_works_for_vectors) 38 | { 39 | for (size_t i = 0; i < 16; i++) { 40 | auto vector = [&i]() 41 | { 42 | auto tmp = std::vector(i); 43 | std::iota(tmp.begin(), tmp.end(), -8); 44 | return a_can_iterate_vector_type(tmp); 45 | }(); 46 | auto curr = -8; 47 | for (auto& val : vector) { 48 | val++; 49 | EXPECT_EQ(val, curr + 1); 50 | curr++; 51 | } 52 | } 53 | } 54 | 55 | } // namespace twig 56 | -------------------------------------------------------------------------------- /tests/src/skills/can_multiply_tests.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "stronk/skills/can_multiply.hpp" 3 | 4 | #include 5 | 6 | #include "stronk/stronk.hpp" 7 | 8 | namespace twig 9 | { 10 | 11 | struct a_multiplying_type : stronk 12 | { 13 | using stronk::stronk; 14 | }; 15 | 16 | TEST(can_multiply, can_multiply_numbers_correctly) 17 | { 18 | auto val_1 = a_multiplying_type {5}; 19 | auto val_2 = a_multiplying_type {2}; 20 | EXPECT_EQ(val_1 * val_2, a_multiplying_type {10}); 21 | val_1 *= val_2; 22 | 23 | EXPECT_EQ(val_1 * val_2, a_multiplying_type {20}); 24 | } 25 | 26 | TEST(can_multiply, multiplying_behaves_similar_to_integers) 27 | { 28 | for (auto i = -16; i < 16; i++) { 29 | for (auto j = -16; j < 16; j++) { 30 | EXPECT_EQ(a_multiplying_type {i * j}, a_multiplying_type {i} * a_multiplying_type {j}); 31 | } 32 | } 33 | } 34 | 35 | struct a_can_multiply_with_int_and_double_type 36 | : stronk::skill, 39 | can_multiply_with::skill, 40 | can_equate_underlying_type_specific> 41 | { 42 | using stronk::stronk; 43 | }; 44 | 45 | TEST(can_multiply_with, can_multiply_with_integers_and_other_types) 46 | { 47 | auto val = a_can_multiply_with_int_and_double_type {2}; 48 | val *= 10; 49 | val = 10 * val; 50 | val = val * 10; 51 | EXPECT_EQ(val, a_can_multiply_with_int_and_double_type {2000}); 52 | val *= 0.1; 53 | val = 0.1 * val; 54 | val = val * 0.1; 55 | EXPECT_EQ(val, a_can_multiply_with_int_and_double_type {2}); 56 | } 57 | 58 | } // namespace twig 59 | -------------------------------------------------------------------------------- /tests/src/skills/can_stream_tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "stronk/skills/can_stream.hpp" 4 | 5 | #include 6 | 7 | #include "stronk/stronk.hpp" 8 | 9 | namespace twig 10 | { 11 | 12 | struct an_ostreamable_type : stronk 13 | { 14 | using stronk::stronk; 15 | }; 16 | 17 | TEST(can_ostream, streaming_to_ostream_prints_the_value) // NOLINT 18 | { 19 | auto formattable = an_ostreamable_type {5}; 20 | auto sstream = std::stringstream(); 21 | sstream << formattable; 22 | 23 | EXPECT_EQ(sstream.str(), "5"); 24 | } 25 | 26 | struct an_istreamable_type : stronk 27 | { 28 | using stronk::stronk; 29 | }; 30 | 31 | TEST(can_ostream, streaming_from_istream_overrides_the_value) // NOLINT 32 | { 33 | auto val = an_istreamable_type {5}; 34 | auto sstream = std::stringstream("7"); 35 | sstream >> val; 36 | 37 | EXPECT_EQ(val.unwrap(), 7); 38 | } 39 | 40 | } // namespace twig 41 | -------------------------------------------------------------------------------- /tests/src/specializers_tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "stronk/unit.hpp" 10 | 11 | namespace twig::tests 12 | { 13 | 14 | struct specializer_type_a : unit> 15 | { 16 | }; 17 | struct specializer_type_b : unit> 18 | { 19 | }; 20 | 21 | struct specializer_type_c : unit> 22 | { 23 | }; 24 | 25 | struct specializer_type_d : unit, std::ratio<1>> 26 | { 27 | }; 28 | 29 | } // namespace twig::tests 30 | 31 | namespace twig 32 | { 33 | template<> 34 | struct underlying_multiply_operation, tests::specializer_type_b::value> 35 | { 36 | using res_type = int64_t; 37 | 38 | STRONK_FORCEINLINE 39 | constexpr static auto multiply(const typename tests::specializer_type_a::value::underlying_type& v1, 40 | const typename tests::specializer_type_b::value::underlying_type& v2) noexcept 41 | -> res_type 42 | { 43 | return (static_cast(v1) * static_cast(v2)) + 1LL; 44 | } 45 | }; 46 | 47 | template<> 48 | struct underlying_divide_operation, tests::specializer_type_b::value> 49 | { 50 | using res_type = int64_t; 51 | 52 | STRONK_FORCEINLINE 53 | constexpr static auto divide(const typename tests::specializer_type_a::value::underlying_type& v1, 54 | const typename tests::specializer_type_b::value::underlying_type& v2) noexcept 55 | -> res_type 56 | { 57 | return (static_cast(v1) / static_cast(v2)) + 1LL; 58 | } 59 | }; 60 | 61 | template<> 62 | struct unit_lookup> 63 | { 64 | template 65 | using unit_t = unit_scaled_or_base_t; 66 | }; 67 | 68 | using specializer_type_a_squared = 69 | decltype(tests::specializer_type_a::value {} * tests::specializer_type_a::value {}); 70 | using specializer_type_a_times_b = 71 | decltype(tests::specializer_type_a::value {} * tests::specializer_type_b::value {}); 72 | using specializer_type_a_divided_by_b = 73 | decltype(tests::specializer_type_a::value {} / tests::specializer_type_b::value {}); 74 | 75 | } // namespace twig 76 | 77 | namespace twig::tests 78 | { 79 | 80 | TEST(underlying_multiply_operation, the_multiplying_function_is_overloaded) // NOLINT 81 | { 82 | using res_type = decltype(tests::specializer_type_a::value {} * tests::specializer_type_b::value {}); 83 | 84 | // we have specialized it to return int64_t and add one to the result 85 | static_assert(std::is_same_v); 86 | EXPECT_EQ(tests::specializer_type_a::value {10} * tests::specializer_type_b::value {20}, 87 | res_type {200 + 1}); 88 | } 89 | 90 | TEST(underlying_divide_operation, the_divide_function_is_overloaded) // NOLINT 91 | { 92 | using res_type = decltype(tests::specializer_type_a::value {} / tests::specializer_type_b::value {}); 93 | 94 | // we have specialized it to return int64_t and add one to the result 95 | static_assert(std::is_same_v); 96 | EXPECT_EQ(tests::specializer_type_a::value {120} / tests::specializer_type_b::value {2}, 97 | res_type {60 + 1}); 98 | } 99 | 100 | // clang-format off 101 | 102 | static_assert(std::same_as, decltype(specializer_type_a::value {} * specializer_type_c::value {})>); 103 | static_assert(std::same_as::unit_t>::value, specializer_type_a_divided_by_b>); 104 | static_assert(std::same_as::unit_t>::value, specializer_type_a_times_b>); 105 | static_assert(std::same_as::dimensions_t>::unit_t>, specializer_type_d>); 106 | static_assert(std::same_as::unit_t>, specializer_type_d>); 107 | // clang-format on 108 | 109 | } // namespace twig::tests 110 | -------------------------------------------------------------------------------- /tests/src/utilities/dimensions_tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace twig::details 6 | { 7 | 8 | struct distance 9 | { 10 | }; 11 | 12 | struct time 13 | { 14 | }; 15 | 16 | using Distance = dimensions>; 17 | using Time = dimensions>; 18 | using Speed = dimensions, dimension>; 19 | 20 | static_assert(std::same_as, empty_dimensions>); 21 | static_assert(std::same_as>, Distance>); 22 | static_assert(std::same_as, dimension>, Speed>); 23 | static_assert(std::same_as, dimension>, 24 | create_dimensions_t, dimension>>); 25 | static_assert(std::same_as, dimension>, 26 | create_dimensions_t>>); 27 | 28 | // multiply 29 | static_assert(std::same_as, empty_dimensions>); 30 | static_assert(std::same_as, empty_dimensions>); 31 | static_assert(std::same_as, dimensions>>); 32 | static_assert(std::same_as::multiply_t, dimensions>>); 33 | static_assert(std::same_as, dimensions, dimension>>); 34 | static_assert(std::same_as, Time::multiply_t>); 35 | static_assert(std::same_as, Speed>); 36 | static_assert(std::same_as, Speed>); 37 | static_assert(std::same_as::multiply_t, 38 | dimensions, dimension>>); 39 | static_assert(std::same_as::multiply_t, dimensions>>); 40 | static_assert(std::same_as, dimensions, dimension>>); 41 | static_assert(std::same_as, empty_dimensions>); 42 | 43 | // Divide 44 | static_assert(std::same_as, empty_dimensions>); 45 | static_assert(std::same_as, empty_dimensions>); 46 | static_assert(std::same_as::divide_t, dimensions>>); 47 | static_assert(std::same_as, empty_dimensions>); 48 | static_assert(std::same_as, Speed>); 49 | static_assert(std::same_as, dimensions, dimension>>); 50 | 51 | } // namespace twig::details 52 | -------------------------------------------------------------------------------- /tools/check_include_what_you_use.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # find recursively which folder starting from build, contains the compile_commands.json file 4 | export BUILD_FOLDER=$(find build -name compile_commands.json -printf "%h\n" | head -n 1) 5 | 6 | git diff --diff-filter=d --name-only origin/main | grep -E "\.h$" | xargs -r -t -n 1 -P 8 -- clang-tidy-19 --fix --checks="-*,misc-include-cleaner" -p $BUILD_FOLDER 7 | git diff --diff-filter=d --name-only origin/main | grep -E "\.hpp$" | xargs -r -t -n 1 -P 8 -- clang-tidy-19 --fix --checks="-*,misc-include-cleaner" -p $BUILD_FOLDER 8 | git diff --diff-filter=d --name-only origin/main | grep -E "\.cpp$" | xargs -r -t -n 1 -P 8 -- clang-tidy-19 --fix --checks="-*,misc-include-cleaner" -p $BUILD_FOLDER 9 | 10 | # Format after fixing 11 | git diff --diff-filter=d --name-only origin/main | grep -E "\.h$" | xargs -I{} -t -n 1 -P 8 sh -c 'cmake -D FIX=YES -D PATTERNS="{}" -D FORMAT_COMMAND=clang-format-19 -P cmake/lint.cmake' 12 | git diff --diff-filter=d --name-only origin/main | grep -E "\.hpp$" | xargs -I{} -t -n 1 -P 8 sh -c 'cmake -D FIX=YES -D PATTERNS="{}" -D FORMAT_COMMAND=clang-format-19 -P cmake/lint.cmake' 13 | git diff --diff-filter=d --name-only origin/main | grep -E "\.cpp$" | xargs -I{} -t -n 1 -P 8 sh -c 'cmake -D FIX=YES -D PATTERNS="{}" -D FORMAT_COMMAND=clang-format-19 -P cmake/lint.cmake' 14 | 15 | echo "Checking diff after clang-tidy-19" 16 | git --no-pager diff --quiet || exit 1 17 | -------------------------------------------------------------------------------- /tools/check_install_packages_match.py: -------------------------------------------------------------------------------- 1 | """ 2 | Check that the packages defined by find_package matches the ones in cmake/install-config.cmake 3 | These are used by downstream dependencies and therefore helps users install the library. 4 | """ 5 | 6 | 7 | def extract_components( 8 | dependency_line: str, dependecy_to_components: dict[str, list[str]] 9 | ) -> None: 10 | words = dependency_line[ 11 | dependency_line.find("(") + 1 : dependency_line.rfind(")") 12 | ].split(" ") 13 | keywords = ["REQUIRED", "CONFIG"] 14 | dependency = words[0] 15 | in_component_list = False 16 | for word in words[1:]: 17 | word = word.strip() 18 | if word == "COMPONENTS": 19 | in_component_list = True 20 | continue 21 | 22 | if in_component_list: 23 | if word in keywords: 24 | break 25 | 26 | dependecy_to_components[dependency].append(word) 27 | 28 | 29 | # %% 30 | def main() -> None: 31 | """ 32 | Check that the packages defined by find_package matches the ones in cmake/install-config.cmake 33 | """ 34 | dependency_str_lines: list[str] = [] 35 | 36 | with open("CMakeLists.txt", "r", encoding="utf-8") as f: 37 | for line in f: 38 | if line.startswith("find_package("): 39 | dependency_str_lines.append(line) 40 | 41 | dependencies = [line.split("(")[1].split(" ")[0] for line in dependency_str_lines] 42 | dependency_components = {x: [] for x in dependencies} 43 | for dependency_line in dependency_str_lines: 44 | extract_components(dependency_line, dependency_components) 45 | 46 | install_dependency_str_lines = [] 47 | with open("cmake/install-config.cmake", "r", encoding="utf-8") as f: 48 | for line in f: 49 | if line.startswith("find_dependency("): 50 | install_dependency_str_lines.append(line) 51 | 52 | install_dependencies = [ 53 | line.split("(")[1].split(")")[0].split(" ")[0] 54 | for line in install_dependency_str_lines 55 | ] 56 | install_dependency_components = {x: [] for x in install_dependencies} 57 | for dependency_line in install_dependency_str_lines: 58 | extract_components(dependency_line, install_dependency_components) 59 | 60 | assert set(install_dependencies) == set(dependencies), ( 61 | f"Dependencies in find_package and install-config.cmake do not match.\n" 62 | f"find_package: {dependencies}\n" 63 | f"install-config.cmake: {install_dependencies}" 64 | ) 65 | 66 | assert install_dependency_components == dependency_components, ( 67 | f"Component Dependencies in find_package and install-config.cmake do not match.\n" 68 | f"find_package: {dependency_components}\n" 69 | f"install-config.cmake: {install_dependency_components}" 70 | ) 71 | 72 | print( 73 | "Dependencies in find_package and install-config.cmake match." 74 | f"find_package: {dependencies}\n" 75 | f"install-config.cmake: {install_dependencies}" 76 | ) 77 | 78 | 79 | if __name__ == "__main__": 80 | main() 81 | -------------------------------------------------------------------------------- /tools/embed_code.py: -------------------------------------------------------------------------------- 1 | """ 2 | Parse markdown document and embed the referenced code into code blocks 3 | 4 | Example syntax for code blocks: 5 | ```cpp:file=./examples/unit_energy_example.cpp:line_start=0:line_end=29 6 | ``` 7 | """ 8 | 9 | from dataclasses import dataclass 10 | from io import TextIOWrapper 11 | import sys 12 | from pathlib import Path 13 | from typing import Dict, List, Optional 14 | 15 | 16 | def remove_leading_spaces(code_string: str) -> str: 17 | lines = code_string.splitlines() 18 | result = "" 19 | min_leading = sys.maxsize 20 | for line in lines: 21 | if len(line) != 0: 22 | min_leading = min(min_leading, len(line) - len(line.lstrip())) 23 | 24 | for line in lines: 25 | result += line[min_leading:] + "\n" 26 | 27 | return result 28 | 29 | 30 | def parse_code_file(file_handle: TextIOWrapper, line_start=0, line_end=None) -> str: 31 | code = "" 32 | for line in file_handle.readlines()[line_start:line_end]: 33 | code += line 34 | return remove_leading_spaces(code) 35 | 36 | 37 | @dataclass(unsafe_hash=True) 38 | class Section: 39 | language: str 40 | file: Path 41 | line_start: int = 0 42 | line_end: Optional[int] = None 43 | 44 | def __post_init__(self): 45 | self.file = Path(self.file) 46 | self.line_start = int(self.line_start) 47 | self.line_end = int(self.line_end) if self.line_end else None 48 | 49 | @staticmethod 50 | def is_valid_section(code_section_start_line: str): 51 | stripped = code_section_start_line.strip() 52 | stripped = stripped[stripped.find("```") + 3 :] 53 | return len(stripped.split(":")) >= 2 and "file=" in stripped 54 | 55 | @staticmethod 56 | def parse_section(code_section_start_line: str): 57 | stripped = code_section_start_line.strip() 58 | stripped = stripped[stripped.find("```") + 3 :] 59 | properties = stripped.split(":") 60 | keyword_properties = {x.split("=")[0]: x.split("=")[1] for x in properties[1:]} 61 | return Section(language=properties[0], **keyword_properties) 62 | 63 | 64 | def find_code_sections(lines: List[str]): 65 | section = [] 66 | for number, line in enumerate(lines): 67 | if line.strip().startswith("```"): 68 | section.append(number) 69 | if len(section) == 2: 70 | if Section.is_valid_section(lines[section[0]]): 71 | yield (section[0], section[1]) 72 | section.clear() 73 | 74 | 75 | def parse_markdown(file: Path) -> List[Section]: 76 | res = [] 77 | with open(file) as fp: 78 | lines = fp.readlines() 79 | 80 | sections = list(find_code_sections(lines)) 81 | 82 | for section_start, _ in sections: 83 | section = Section.parse_section(lines[section_start]) 84 | assert section.file.exists(), ( 85 | f"Failed to parse section: {lines[section_start]}: '{section.file.absolute()}' did not exist" 86 | ) 87 | res.append(section) 88 | return res 89 | 90 | 91 | def load_code_sections(snippets: List[Section]) -> Dict[Section, str]: 92 | res = {} 93 | for snippet in snippets: 94 | with open(snippet.file) as fp: 95 | res[snippet] = parse_code_file(fp, snippet.line_start, snippet.line_end) 96 | return res 97 | 98 | 99 | def embed_data_in_docs( 100 | files_to_embed_into: Path, files_to_code: Dict[Section, str] 101 | ) -> str: 102 | with open(files_to_embed_into) as fp: 103 | lines = fp.readlines() 104 | 105 | res = "" 106 | last_index = 0 107 | for section_start, section_end in find_code_sections(lines): 108 | for line in lines[last_index : section_start + 1]: 109 | res += line 110 | section = Section.parse_section(lines[section_start]) 111 | assert section in files_to_code, files_to_code 112 | res += files_to_code[section] 113 | res += "```\n" 114 | last_index = section_end + 1 115 | 116 | # Add the remaining of the file 117 | for line in lines[last_index:]: 118 | res += line 119 | return res 120 | 121 | 122 | def output_result(file_to_embed_into: Path, result_str: str, inline: bool): 123 | if inline: 124 | with open(file_to_embed_into, "w") as fp: 125 | fp.write(result_str) 126 | else: 127 | print(result_str) 128 | 129 | 130 | def parse_arguments(): 131 | import argparse 132 | 133 | parser = argparse.ArgumentParser(description="Embed code into readme") 134 | parser.add_argument("-f", "--file", type=Path) 135 | parser.add_argument("-i", "--inline", action="store_true") 136 | return parser.parse_args() 137 | 138 | 139 | def run(): 140 | try: 141 | args = parse_arguments() 142 | assert args.file.exists() 143 | sections_to_load = parse_markdown(args.file) 144 | files_to_code = load_code_sections(sections_to_load) 145 | new_file_str = embed_data_in_docs(args.file, files_to_code) 146 | output_result(args.file, new_file_str, args.inline) 147 | except Exception as e: 148 | print(e) 149 | return 1 150 | return 0 151 | 152 | 153 | if __name__ == "__main__": 154 | exit(run()) 155 | -------------------------------------------------------------------------------- /tools/run_clangd_tidy_on_changed.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # find recursively which folder starting from build, contains the compile_commands.json file 4 | export BUILD_FOLDER=$(find build -name compile_commands.json -printf "%h\n" | head -n 1) 5 | 6 | # you can add --enable-check-profile to see which checks are taking the most time 7 | 8 | # if given argument "all" run on all files, otherwise only on diffs 9 | if [ "$1" != "all" ]; then 10 | git diff --diff-filter=d --name-only origin/main | grep -E "\.(h|hpp|cpp)$" | xargs -r -t -- clangd-tidy -p $BUILD_FOLDER --clangd-executable=clangd-19 --github --tqdm 11 | else 12 | find include source test example -type f | grep -E "\.(h|hpp|cpp)$" | xargs -r -t -- clangd-tidy -p $BUILD_FOLDER --clangd-executable=clangd-19 --github --tqdm 13 | fi 14 | -------------------------------------------------------------------------------- /vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stronk", 3 | "homepage": "https://github.com/twig-energy/stronk", 4 | "description": "An easy to customize, strong type library with built in support for unit-like behavior", 5 | "license": "MIT", 6 | "dependencies": [ 7 | "boost-type-index" 8 | ], 9 | "default-features": [], 10 | "features": { 11 | "fmt": { 12 | "description": "Dependencies for fmt extension", 13 | "dependencies": [ 14 | "fmt" 15 | ] 16 | }, 17 | "abseil": { 18 | "description": "Dependencies for abseil extension", 19 | "dependencies": [ 20 | { 21 | "name": "abseil", 22 | "features": [ 23 | "cxx17" 24 | ] 25 | } 26 | ] 27 | }, 28 | "doctest": { 29 | "description": "Dependencies for doctest extension", 30 | "dependencies": [ 31 | "doctest" 32 | ] 33 | }, 34 | "nlohmann-json": { 35 | "description": "Dependencies for nlohmann::json extension", 36 | "dependencies": [ 37 | "nlohmann-json" 38 | ] 39 | }, 40 | "glaze": { 41 | "description": "Dependencies for glaze extension", 42 | "dependencies": [ 43 | "glaze" 44 | ] 45 | }, 46 | "test": { 47 | "description": "Dependencies for testing", 48 | "dependencies": [ 49 | "gtest" 50 | ] 51 | }, 52 | "benchmark": { 53 | "description": "Dependencies for benchmarking", 54 | "dependencies": [ 55 | "benchmark" 56 | ] 57 | } 58 | }, 59 | "builtin-baseline": "d5ec528843d29e3a52d745a64b469f810b2cedbf" 60 | } 61 | -------------------------------------------------------------------------------- /version.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.11.0" 2 | --------------------------------------------------------------------------------