diff --git a/.github/workflows/ci-clang-tidy.yml b/.github/workflows/ci-clang-tidy.yml new file mode 100644 index 000000000..9f4e6a5e3 --- /dev/null +++ b/.github/workflows/ci-clang-tidy.yml @@ -0,0 +1,138 @@ +# The MIT License (MIT) +# +# Copyright (c) 2018 Mateusz Pusz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +name: clang-tidy CI + +on: + push: + paths-ignore: + - "docs/**" + pull_request: + paths-ignore: + - "docs/**" + +jobs: + build: + name: "${{ matrix.formatting }} ${{ matrix.contracts }} C++${{ matrix.std }} ${{ matrix.config.name }} ${{ matrix.build_type }}" + runs-on: ${{ matrix.config.os }} + strategy: + fail-fast: false + matrix: + formatting: ["std::format", "fmtlib"] + contracts: ["none", "gsl-lite", "ms-gsl"] + std: [20, 23] + config: + - { + name: "Clang-18", + os: ubuntu-24.04, + compiler: + { + type: CLANG, + version: 18, + cc: "clang-18", + cxx: "clang++-18", + }, + lib: "libc++", + cxx_modules: "False", + std_format_support: "True", + conan-config: "", + } + build_type: ["Release", "Debug"] + + env: + CC: ${{ matrix.config.compiler.cc }} + CXX: ${{ matrix.config.compiler.cxx }} + + steps: + - uses: actions/checkout@v4 + - run: echo "cache_id=$(/bin/date -u "+%Y%m%d")" >> $GITHUB_ENV + - name: Cache Conan data + uses: actions/cache@v4 + if: always() + env: + cache-name: cache-conan-data + with: + path: ~/.conan2/p + key: clang-tidy-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }}-${{ env.cache_id }} + restore-keys: | + clang-tidy-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }}- + clang-tidy-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}- + clang-tidy-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}- + clang-tidy-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}- + clang-tidy-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}-${{ matrix.config.compiler.type }}- + clang-tidy-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}- + clang-tidy-${{ matrix.config.os }}-${{ matrix.formatting }}- + clang-tidy-${{ matrix.config.os }}- + - uses: hendrikmuhs/ccache-action@v1.2 + if: runner.os == 'Linux' + with: + key: ${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }} + max-size: 50M + - name: Install Clang + if: matrix.config.compiler.type == 'CLANG' + shell: bash + working-directory: ${{ env.HOME }} + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh ${{ matrix.config.compiler.version }} + sudo apt install -y clang-tools-${{ matrix.config.compiler.version }} + - name: Install Libc++ + if: matrix.config.compiler.type == 'CLANG' && matrix.config.lib == 'libc++' + shell: bash + run: | + sudo apt install -y libc++-${{ matrix.config.compiler.version }}-dev libc++abi-${{ matrix.config.compiler.version }}-dev libunwind-${{ matrix.config.compiler.version }}-dev + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Install Ninja + shell: bash + run: | + pip install -U ninja + - name: Install Conan + shell: bash + run: | + pip install -U conan + - name: Configure Conan + shell: bash + run: | + conan profile detect --force + if [[ "${{ matrix.config.compiler.type }}" == "CLANG" ]]; then + sed -i.backup '/^\[settings\]$/,/^\[/ s/^compiler.libcxx=.*/compiler.libcxx=${{ matrix.config.lib }}/' ~/.conan2/profiles/default + fi + sed -i.backup '/^\[settings\]$/,/^\[/ s/^compiler.cppstd=.*/compiler.cppstd=${{ matrix.std }}/' ~/.conan2/profiles/default + sed -i.backup '/^\[settings\]$/,/^\[/ s/^build_type=.*/build_type=${{ matrix.build_type }}/' ~/.conan2/profiles/default + conan profile show -pr default + - run: echo "std_format=$([ "${{ matrix.formatting }}" == "std::format" ] && echo "True" || echo "False")" >> $GITHUB_ENV + - name: Run clang-tidy + shell: bash + run: | + conan build . -b missing -c tools.cmake.cmaketoolchain:generator="Ninja Multi-Config" \ + -c user.mp-units.build:all=True -c user.mp-units.analyze:clang-tidy=True \ + -o cxx_modules=${{ matrix.config.cxx_modules }} -o std_format=${{ env.std_format }} -o contracts=${{ matrix.contracts }} ${{ matrix.config.conan-config }} + - name: Clean Conan cache before backup + shell: bash + run: | + conan remove *#~latest --confirm + conan remove *:*#~latest --confirm + conan cache clean "*" -s -b -d diff --git a/.github/workflows/ci-conan.yml b/.github/workflows/ci-conan.yml index b6802f358..44bdf8e4b 100644 --- a/.github/workflows/ci-conan.yml +++ b/.github/workflows/ci-conan.yml @@ -35,12 +35,13 @@ env: jobs: build: - name: "${{ matrix.formatting }} C++${{ matrix.std }} ${{ matrix.config.name }} ${{ matrix.build_type }}" + name: "${{ matrix.formatting }} ${{ matrix.contracts }} C++${{ matrix.std }} ${{ matrix.config.name }} ${{ matrix.build_type }}" runs-on: ${{ matrix.config.os }} strategy: fail-fast: false matrix: formatting: ["std::format", "fmtlib"] + contracts: ["none", "gsl-lite", "ms-gsl"] std: [20, 23] config: # - { @@ -57,7 +58,7 @@ jobs: # } - { name: "GCC-12", - os: ubuntu-22.04, + os: ubuntu-24.04, compiler: { type: GCC, @@ -71,7 +72,7 @@ jobs: } - { name: "GCC-13", - os: ubuntu-22.04, + os: ubuntu-24.04, compiler: { type: GCC, @@ -83,6 +84,20 @@ jobs: std_format_support: "True", conan-config: "", } + - { + name: "GCC-14", + os: ubuntu-24.04, + compiler: + { + type: GCC, + version: 14, + cc: "gcc-14", + cxx: "g++-14", + }, + cxx_modules: "False", + std_format_support: "True", + conan-config: "", + } - { name: "Clang-16", os: ubuntu-22.04, @@ -100,7 +115,7 @@ jobs: } - { name: "Clang-17", - os: ubuntu-22.04, + os: ubuntu-24.04, compiler: { type: CLANG, @@ -115,7 +130,7 @@ jobs: } - { name: "Clang-18", - os: ubuntu-22.04, + os: ubuntu-24.04, compiler: { type: CLANG, @@ -161,19 +176,20 @@ jobs: cache-name: cache-conan-data with: path: ~/.conan2/p - key: conan-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }}-${{ env.cache_id }} + key: conan-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }}-${{ env.cache_id }} restore-keys: | - conan-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }}- - conan-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}- - conan-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}- - conan-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}- - conan-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}- + conan-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }}- + conan-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}- + conan-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}- + conan-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}- + conan-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}-${{ matrix.config.compiler.type }}- + conan-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}- conan-${{ matrix.config.os }}-${{ matrix.formatting }}- conan-${{ matrix.config.os }}- - uses: hendrikmuhs/ccache-action@v1.2 if: runner.os == 'Linux' with: - key: ${{ matrix.config.os }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }} + key: ${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }} max-size: 50M - name: Install gcc-13 if: matrix.config.compiler.type == 'GCC' && matrix.config.compiler.version == '13' @@ -202,7 +218,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.8" + python-version: 3.x - name: Install Ninja shell: bash run: | @@ -227,7 +243,7 @@ jobs: run: | conan create . --user mpusz --channel ${CHANNEL} --lockfile-out=package.lock \ -b mp-units/* -b missing -c tools.cmake.cmaketoolchain:generator="Ninja Multi-Config" \ - -c user.mp-units.build:all=True -o cxx_modules=${{ matrix.config.cxx_modules }} -o std_format=${{ env.std_format }} ${{ matrix.config.conan-config }} + -c user.mp-units.build:all=True -o cxx_modules=${{ matrix.config.cxx_modules }} -o std_format=${{ env.std_format }} -o contracts=${{ matrix.contracts }} ${{ matrix.config.conan-config }} - name: Obtain package reference id: get-package-ref shell: bash @@ -244,8 +260,8 @@ jobs: shell: bash run: | conan remove mp-units --confirm - conan remove *#!latest --confirm - conan remove *:*#!latest --confirm + conan remove *#~latest --confirm + conan remove *:*#~latest --confirm conan cache clean "*" -s -b -d outputs: package_ref: ${{ steps.get-package-ref.outputs.PACKAGE_REF }} @@ -254,13 +270,13 @@ jobs: if: github.ref == 'refs/heads/master' || (github.ref_type == 'tag' && startsWith(github.ref_name, 'v')) needs: build name: Promote Conan package - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.8" + python-version: 3.x - name: Install Conan shell: bash run: | diff --git a/.github/workflows/ci-formatting.yml b/.github/workflows/ci-formatting.yml index ad053fb75..086fe625a 100644 --- a/.github/workflows/ci-formatting.yml +++ b/.github/workflows/ci-formatting.yml @@ -26,13 +26,13 @@ on: [push, pull_request] jobs: check: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - - name: Set up Python 3.8 + - name: Set up Python 3.12 uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: 3.x - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/ci-freestanding.yml b/.github/workflows/ci-freestanding.yml new file mode 100644 index 000000000..407949d39 --- /dev/null +++ b/.github/workflows/ci-freestanding.yml @@ -0,0 +1,156 @@ +# The MIT License (MIT) +# +# Copyright (c) 2018 Mateusz Pusz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +name: Freestanding CI + +on: + push: + paths-ignore: + - "docs/**" + pull_request: + paths-ignore: + - "docs/**" + +jobs: + build: + name: "${{ matrix.formatting }} ${{ matrix.contracts }} C++${{ matrix.std }} ${{ matrix.config.name }} ${{ matrix.build_type }}" + runs-on: ${{ matrix.config.os }} + strategy: + fail-fast: false + matrix: + formatting: ["std::format", "fmtlib"] + contracts: ["none"] + std: [23] + config: + - { + name: "GCC-14", + os: ubuntu-24.04, + compiler: + { + type: GCC, + version: 14, + cc: "gcc-14", + cxx: "g++-14", + }, + cxx_modules: "False", + std_format_support: "True", + conan-config: "", + } + - { + name: "Clang-18", + os: ubuntu-24.04, + compiler: + { + type: CLANG, + version: 18, + cc: "clang-18", + cxx: "clang++-18", + }, + lib: "libc++", + cxx_modules: "True", + std_format_support: "True", + conan-config: "", + } + build_type: ["Release", "Debug"] + # TODO For some reason Clang-18 Debug with -ffreestanding does not pass CMakeTestCXXCompiler + exclude: + - build_type: "Debug" + config: { name: "Clang-18" } + + env: + CC: ${{ matrix.config.compiler.cc }} + CXX: ${{ matrix.config.compiler.cxx }} + + steps: + - uses: actions/checkout@v4 + - run: echo "cache_id=$(/bin/date -u "+%Y%m%d")" >> $GITHUB_ENV + - name: Cache Conan data + uses: actions/cache@v4 + if: always() + env: + cache-name: cache-conan-data + with: + path: ~/.conan2/p + key: freestanding-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }}-${{ env.cache_id }} + restore-keys: | + freestanding-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }}- + freestanding-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}- + freestanding-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}- + freestanding-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}- + freestanding-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}-${{ matrix.config.compiler.type }}- + freestanding-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}- + freestanding-${{ matrix.config.os }}-${{ matrix.formatting }}- + freestanding-${{ matrix.config.os }}- + - uses: hendrikmuhs/ccache-action@v1.2 + if: runner.os == 'Linux' + with: + key: ${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }} + max-size: 50M + - name: Install Clang + if: matrix.config.compiler.type == 'CLANG' + shell: bash + working-directory: ${{ env.HOME }} + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh ${{ matrix.config.compiler.version }} + sudo apt install -y clang-tools-${{ matrix.config.compiler.version }} + - name: Install Libc++ + if: matrix.config.compiler.type == 'CLANG' && matrix.config.lib == 'libc++' + shell: bash + run: | + sudo apt install -y libc++-${{ matrix.config.compiler.version }}-dev libc++abi-${{ matrix.config.compiler.version }}-dev libunwind-${{ matrix.config.compiler.version }}-dev + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Install Ninja + shell: bash + run: | + pip install -U ninja + - name: Install Conan + shell: bash + run: | + pip install -U conan + - name: Configure Conan + shell: bash + run: | + conan profile detect --force + if [[ "${{ matrix.config.compiler.type }}" == "CLANG" ]]; then + sed -i.backup '/^\[settings\]$/,/^\[/ s/^compiler.libcxx=.*/compiler.libcxx=${{ matrix.config.lib }}/' ~/.conan2/profiles/default + fi + sed -i.backup '/^\[settings\]$/,/^\[/ s/^compiler.cppstd=.*/compiler.cppstd=${{ matrix.std }}/' ~/.conan2/profiles/default + sed -i.backup '/^\[settings\]$/,/^\[/ s/^build_type=.*/build_type=${{ matrix.build_type }}/' ~/.conan2/profiles/default + conan profile show -pr default + - run: echo "std_format=$([ "${{ matrix.formatting }}" == "std::format" ] && echo "True" || echo "False")" >> $GITHUB_ENV + - name: Build freestanding + shell: bash + run: | + conan build . -b missing -c tools.cmake.cmaketoolchain:generator="Ninja Multi-Config" \ + -c user.mp-units.build:all=True -c tools.build:cxxflags="['-ffreestanding']" \ + -o cxx_modules=${{ matrix.config.cxx_modules }} -o std_format=${{ env.std_format }} -o contracts=${{ matrix.contracts }} -o freestanding=True ${{ matrix.config.conan-config }} + - name: Clean Conan cache before backup + shell: bash + run: | + conan remove *#~latest --confirm + conan remove *:*#~latest --confirm + conan cache clean "*" -s -b -d diff --git a/.github/workflows/ci-test-package-cmake.yml b/.github/workflows/ci-test-package-cmake.yml index e822d030e..024ad0acd 100644 --- a/.github/workflows/ci-test-package-cmake.yml +++ b/.github/workflows/ci-test-package-cmake.yml @@ -36,12 +36,13 @@ on: jobs: test_package: - name: "${{ matrix.formatting }} C++${{ matrix.std }} ${{ matrix.config.name }} ${{ matrix.build_type }}" + name: "${{ matrix.formatting }} ${{ matrix.contracts }} C++${{ matrix.std }} ${{ matrix.config.name }} ${{ matrix.build_type }}" runs-on: ${{ matrix.config.os }} strategy: fail-fast: false matrix: formatting: ["std::format", "fmtlib"] + contracts: ["none", "gsl-lite", "ms-gsl"] std: [20, 23] config: # - { @@ -56,7 +57,7 @@ jobs: # } - { name: "GCC-12", - os: ubuntu-22.04, + os: ubuntu-24.04, compiler: { type: GCC, @@ -69,7 +70,7 @@ jobs: } - { name: "GCC-13", - os: ubuntu-22.04, + os: ubuntu-24.04, compiler: { type: GCC, @@ -80,6 +81,19 @@ jobs: cxx_modules: "False", std_format_support: "True", } + - { + name: "GCC-14", + os: ubuntu-24.04, + compiler: + { + type: GCC, + version: 14, + cc: "gcc-14", + cxx: "g++-14", + }, + cxx_modules: "False", + std_format_support: "True" + } - { name: "Clang-16", os: ubuntu-22.04, @@ -96,7 +110,7 @@ jobs: } - { name: "Clang-17", - os: ubuntu-22.04, + os: ubuntu-24.04, compiler: { type: CLANG, @@ -110,7 +124,7 @@ jobs: } - { name: "Clang-18", - os: ubuntu-22.04, + os: ubuntu-24.04, compiler: { type: CLANG, @@ -124,7 +138,7 @@ jobs: } - { name: "Apple Clang 15", - os: macos-13, + os: macos-14, compiler: { type: APPLE_CLANG, @@ -147,7 +161,7 @@ jobs: steps: - name: Downcase 'build_type' id: build_type - uses: ASzc/change-string-case-action@v5 + uses: ASzc/change-string-case-action@v6 with: string: ${{ matrix.build_type }} - uses: actions/checkout@v4 @@ -159,13 +173,14 @@ jobs: cache-name: cache-conan-data with: path: ~/.conan2/p - key: cmake-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }}-${{ env.cache_id }} + key: cmake-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }}-${{ env.cache_id }} restore-keys: | - cmake-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }}- - cmake-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}- - cmake-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}- - cmake-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}- - cmake-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}- + cmake-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }}- + cmake-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}- + cmake-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}- + cmake-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}- + cmake-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}-${{ matrix.config.compiler.type }}- + cmake-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.contracts }}- cmake-${{ matrix.config.os }}-${{ matrix.formatting }}- cmake-${{ matrix.config.os }}- - name: Install gcc-13 @@ -195,7 +210,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.8" + python-version: 3.x - name: Install Ninja shell: bash run: | @@ -220,7 +235,7 @@ jobs: shell: bash run: | conan install . -b missing -c tools.cmake.cmaketoolchain:generator="Ninja Multi-Config" -c user.mp-units.build:all=False \ - -o cxx_modules=${{ matrix.config.cxx_modules }} -o std_format=${{ env.std_format }} + -o cxx_modules=${{ matrix.config.cxx_modules }} -o std_format=${{ env.std_format }} -o contracts=${{ matrix.contracts }} - name: Provide dependencies for the build shell: bash working-directory: src @@ -291,3 +306,9 @@ jobs: working-directory: test_package/build/install/${{ matrix.build_type }} run: | ./test_package + - name: Clean Conan cache before backup + shell: bash + run: | + conan remove *#~latest --confirm + conan remove *:*#~latest --confirm + conan cache clean "*" -s -b -d diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d9fdc3c7c..072cb7e9e 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -59,7 +59,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -74,7 +74,7 @@ jobs: # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild if: matrix.language != 'cpp' - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -97,7 +97,7 @@ jobs: if: matrix.language == 'cpp' uses: actions/setup-python@v5 with: - python-version: "3.8" + python-version: 3.x - name: Conan build if: matrix.language == 'cpp' run: | @@ -108,6 +108,6 @@ jobs: conan build .. -s compiler.cppstd=20 -c user.mp-units.build:all=True -o std_format=False -b missing - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: category: "/language:${{matrix.language}}" diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fa00699f..8a5ae64eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,15 +8,18 @@ - (!) feat: formatting grammar improved and units formatting support added - (!) feat: `has_unit_symbol` support removed - (!) feat: ABI concerns resolved with introduction of u8 strings for symbols +- (!) feat: API-related Conan, CMake, and preprocessor options redesigned +- (!) feat: :boom: `core.h` removed - feat: implicit point origins support added - feat: unit default point origin support added - feat: `fma`, `isfinite`, `isinf`, and `isnan` math function added by [@NAThompson](https://github.com/NAThompson) +- feat: `fma` for quantity points added - feat: `quantity_point` support added for `quantity_cast` and `value_cast` - feat: `value_cast` added - feat: `value_cast(q)`, `value_cast(qp)` and `value_cast(qp)` added by [@burnpanck](https://github.com/burnpanck) - feat: `interconvertible(QuantitySpec, QuantitySpec)` added - feat: `qp.quantity_from_zero()` added -- feat: `underlying_type` type trait added +- feat: `value_type` type trait added - feat: do not print space between a number and `percent` or `per_mille` - feat: `ppm` parts per million added by [@nebkat](https://github.com/nebkat) - feat: `atan2` 2-argument arctangent added by [@nebkat](https://github.com/nebkat) @@ -26,6 +29,10 @@ - feat: unit text output support added - feat: formatting error messages improved - feat: improve types readability by eliminating extraneous `()` in references, prefixes, and `kind_of` +- feat: dimension text output added +- feat: some light and radiation ISQ quantities added +- feat: New formatting specification implemented +- feat: allow configuring GSL library use - (!) refactor: `zero_Fahrenheit` renamed to `zeroth_degree_Fahrenheit` - (!) refactor: SI-related trigonometric functions moved to the `si` subnamespace - (!) refactor: `math.h` header file broke up to smaller pieces @@ -33,15 +40,28 @@ - (!) refactor: `ReferenceOf` does not take a dimension anymore - (!) refactor: 'o' replaced with '1' as a modifier for `unit_symbol_solidus::one_denominator` - (!) refactor: `get_kind()` now returns `kind_of` +- (!) refactor: FMT macros moved to `compat_macros.h` +- (!) refactor: `fixed_string` refactored to reflect the latest changes to [P3094R2](https://wg21.link/P3094R2) +- (!) refactor: `basic_symbol_text` renamed to `symbol_text` +- (!) refactor: `ratio` hidden as an implementation detail behind `mag_ratio` +- (!) refactor: `framework.h` introduced +- (!) refactor: type list tools made an implementation detail of the library +- (!) refactor: header files with the entire system definitions moved up in the directory tree - refactor: math functions constraints refactored - refactor: `si_quantities.h` added to improve compile-times - refactor: `validate_ascii_string` refactored to `is_basic_literal_character_set` +- refactor: `underlying_type` split to `wrapped_type` and `value_type` and used in code +- refactor: code refactored to comply with clang-tidy +- refactor: remove dependency on `` header and switch to use an iterator-based `copy` algorithm +- refactor: `terminate` replaced with `abort` and a header file added - fix: `QuantityLike` conversions required `Q::rep` instead of using one provided by `quantity_like_traits` - fix: `QuantitySpec[Unit]` replaced with `make_reference` in `value_cast` - fix: `ice_point` is now defined with the integral offset from `absolute_zero` - fix: performance regression in `sudo_cast` fixed - fix: explicit object parameter support fixed - fix: missing `version` header file added to `hacks.h` +- fix: `quantity_cast` to accept lvalue references (thanks [@burnpanck](https://github.com/burnpanck)) +- fix: `value_cast` with lvalue references to `quantity_point` (thanks [@burnpanck](https://github.com/burnpanck)) - docs: project blog and first posts added - docs: project documentation layout refactored - docs: "Interoperability with Other Libraries" chapter added @@ -56,16 +76,32 @@ - docs: mkdocs social plugin enabled - docs: project logo and custom color scheme added - docs: minimum compiler requirements updated +- docs: unit symbols admonition extended in the "Quick Start" chapter - docs: Cairo dependency described in the MkDocs section +- docs: "hello units" example updated with dimensions output +- docs: "Text Output" chapter updated with the recent formatting changes +- docs: formatting grammar language changed to EBNF +- docs: "Project structure" chapter expanded - (!) build: Conan and CMake options refactored -- (!) build: `MP_UNITS_AS_SYSTEM_HEADERS` support removed +- (!) build: `MP_UNITS_AS_SYSTEM_HEADERS` renamed to `MP_UNITS_BUILD_AS_SYSTEM_HEADERS` +- (!) build: `MP_UNITS_BUILD_LA` and `MP_UNITS_IWYU` CMake options now have `_DEV_` in the name - build: gsl-lite updated to 0.41.0 - build: catch2 updated to 3.5.1 - build: fmt updated to 10.2.1 - build: gitpod environment updated - build: `check_cxx_feature_supported` added +- build: IWYU path handling fixed +- build: IWYU enabled on GCC +- build: `CMAKE_EXPORT_COMPILE_COMMANDS` flag enabled for the developer's build - build(conan): `generate()` now set `cache_variables` - build(conan): `can_run` check added before running tests +- ci: Conan and CMake CI now use different cache names +- ci: gcc-14 added + +### 2.1.1 May 16, 2024 { id="2.1.1" } + +- fix: unit tests compilation on gcc-14 fixed +- fix: explicit `this` parameter support fixed ### 2.1.0 December 9, 2023 { id="2.1.0" } diff --git a/CITATION.cff b/CITATION.cff index 00692afb2..650a157be 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -36,13 +36,13 @@ contact: given-names: Mateusz family-names: Pusz -repository-code: "https://github.com/mpusz/units" -url: "https://mpusz.github.io/units" +repository-code: "https://github.com/mpusz/mp-units" +url: "https://mpusz.github.io/mp-units" repository-artifact: "https://conan.io/center/mp-units" -version: 0.7.0 -date-released: "2021-05-11" +version: 2.1.1 +date-released: "2024-05-16" identifiers: - - description: "The GitHub release URL of tag 0.7.0" + - description: "The GitHub release URL of tag 2.1.1" type: url - value: "https://github.com/mpusz/units/releases/tag/v0.7.0" + value: "https://github.com/mpusz/mp-units/releases/tag/v2.1.1" diff --git a/CMakeLists.txt b/CMakeLists.txt index 88713d7f7..17624eb88 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,8 +67,10 @@ endif() # add project code add_subdirectory(src) -# add usage example -add_subdirectory(example) +if(NOT ${projectPrefix}API_FREESTANDING) + # add usage example + add_subdirectory(example) +endif() # add unit tests enable_testing() diff --git a/README.md b/README.md index 313f6065a..bb3a7cc27 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ [![GitHub license](https://img.shields.io/github/license/mpusz/mp-units?cacheSeconds=3600&color=informational&label=License)](./LICENSE.md) -[![GitHub license](https://img.shields.io/badge/C%2B%2B-20-blue)](https://en.cppreference.com/w/cpp/compiler_support#cpp20) +[![GitHub license](https://img.shields.io/badge/C%2B%2B-20%2F23-blue)](https://en.cppreference.com/w/cpp/compiler_support#cpp20) -[![Conan CI](https://img.shields.io/github/actions/workflow/status/mpusz/mp-units/ci-conan.yml?branch=master&label=Conan%20CI)](https://github.com/mpusz/mp-units/actions?query=workflow%3A%22Conan%20CI%22+branch%3Amaster) -[![CMake CI](https://img.shields.io/github/actions/workflow/status/mpusz/mp-units/ci-test-package-cmake.yml?branch=master&label=CMake%20CI)](https://github.com/mpusz/mp-units/actions?query=workflow%3A%22CMake+Test+Package+CI%22+branch%3Amaster) -[![Formatting CI](https://img.shields.io/github/actions/workflow/status/mpusz/mp-units/ci-formatting.yml?branch=master&label=Formatting%20CI)](https://github.com/mpusz/mp-units/actions?query=workflow%3A%22Formatting%20CI%22+branch%3Amaster) +[![Conan CI](https://img.shields.io/github/actions/workflow/status/mpusz/mp-units/ci-conan.yml?branch=master&label=Conan%20CI)](https://github.com/mpusz/mp-units/actions/workflows/ci-conan.yml) +[![CMake CI](https://img.shields.io/github/actions/workflow/status/mpusz/mp-units/ci-test-package-cmake.yml?branch=master&label=CMake%20CI)](https://github.com/mpusz/mp-units/actions/workflows/ci-test-package-cmake.yml) +[![clang-tidy CI](https://img.shields.io/github/actions/workflow/status/mpusz/mp-units/ci-clang-tidy.yml?branch=master&label=clang-tidy%20CI)](https://github.com/mpusz/mp-units/actions/workflows/ci-clang-tidy.yml) +[![Freestanding CI](https://img.shields.io/github/actions/workflow/status/mpusz/mp-units/ci-freestanding.yml?branch=master&label=Freestanding%20CI)](https://github.com/mpusz/mp-units/actions/workflows/ci-freestanding.yml) +[![Formatting CI](https://img.shields.io/github/actions/workflow/status/mpusz/mp-units/ci-formatting.yml?branch=master&label=Formatting%20CI)](https://github.com/mpusz/mp-units/actions/workflows/ci-formatting.yml) [![GitHub Workflow Documentation](https://img.shields.io/github/actions/workflow/status/mpusz/mp-units/documentation.yml?branch=master&label=Documentation)](https://github.com/mpusz/mp-units/actions?query=workflow%3ADocumentation+branch%3Amaster) [![Conan stable](https://img.shields.io/conan/v/mp-units?label=ConanCenter&color=blue)](https://conan.io/center/mp-units) @@ -130,4 +132,4 @@ int main() } ``` -_Try it on the [Compiler Explorer](https://godbolt.org/z/fsdovTcYh)._ +_Try it on the [Compiler Explorer](https://godbolt.org/z/nhqhT8Mzb)._ diff --git a/conanfile.py b/conanfile.py index dc57a1d09..0d2cb39b1 100644 --- a/conanfile.py +++ b/conanfile.py @@ -65,12 +65,16 @@ class MPUnitsConan(ConanFile): "std_format": ["auto", True, False], "string_view_ret": ["auto", True, False], "no_crtp": ["auto", True, False], + "contracts": ["none", "gsl-lite", "ms-gsl"], + "freestanding": [True, False], } default_options = { "cxx_modules": "auto", "std_format": "auto", "string_view_ret": "auto", "no_crtp": "auto", + "contracts": "gsl-lite", + "freestanding": "False", } tool_requires = "cmake/[>=3.29]" implements = "auto_header_only" @@ -191,6 +195,10 @@ def _use_fmtlib(self): def _skip_la(self): return bool(self.conf.get("user.mp-units.build:skip_la", default=False)) + @property + def _run_clang_tidy(self): + return bool(self.conf.get("user.mp-units.analyze:clang-tidy", default=False)) + def set_version(self): content = load(self, os.path.join(self.recipe_folder, "src/CMakeLists.txt")) version = re.search( @@ -199,13 +207,17 @@ def set_version(self): self.version = version.strip() def requirements(self): - self.requires("gsl-lite/0.41.0") - if self._use_fmtlib: + if self.options.contracts == "gsl-lite": + self.requires("gsl-lite/0.41.0") + elif self.options.contracts == "ms-gsl": + self.requires("ms-gsl/4.0.0") + if self._use_fmtlib and not self.options.freestanding: self.requires("fmt/10.2.1") def build_requirements(self): if self._build_all: - self.test_requires("catch2/3.5.1") + if not self.options.freestanding: + self.test_requires("catch2/3.5.1") if not self._skip_la: self.test_requires("wg21-linear_algebra/0.7.3") @@ -214,16 +226,23 @@ def validate(self): for key, value in self._option_feature_map.items(): if self.options.get_safe(key) == True: self._check_feature_supported(key, value) + if self.options.freestanding and self.options.contracts != "none": + raise ConanInvalidConfiguration( + "'contracts' should be set to 'none' for a freestanding build" + ) def layout(self): cmake_layout(self) def generate(self): tc = CMakeToolchain(self) + tc.absolute_paths = True # only needed for CMake CI if self._build_all: tc.cache_variables["CMAKE_EXPORT_COMPILE_COMMANDS"] = True tc.cache_variables["CMAKE_VERIFY_INTERFACE_HEADER_SETS"] = True tc.cache_variables["MP_UNITS_DEV_BUILD_LA"] = not self._skip_la + if self._run_clang_tidy: + tc.cache_variables["MP_UNITS_DEV_CLANG_TIDY"] = True if self._build_cxx_modules: tc.cache_variables["CMAKE_CXX_SCAN_FOR_MODULES"] = True tc.cache_variables["MP_UNITS_BUILD_CXX_MODULES"] = str( @@ -236,6 +255,10 @@ def generate(self): self.options.string_view_ret ).upper() tc.cache_variables["MP_UNITS_API_NO_CRTP"] = str(self.options.no_crtp).upper() + tc.cache_variables["MP_UNITS_API_CONTRACTS"] = str( + self.options.contracts + ).upper() + tc.cache_variables["MP_UNITS_API_FREESTANDING"] = self.options.freestanding tc.generate() deps = CMakeDeps(self) deps.generate() @@ -262,8 +285,12 @@ def package(self): def package_info(self): compiler = self.settings.compiler - self.cpp_info.components["core"].requires = ["gsl-lite::gsl-lite"] - if self._use_fmtlib: + self.cpp_info.components["core"] + if self.options.contracts == "gsl-lite": + self.cpp_info.components["core"].requires = ["gsl-lite::gsl-lite"] + elif self.options.contracts == "ms-gsl": + self.cpp_info.components["core"].requires = ["ms-gsl::ms-gsl"] + if self._use_fmtlib and not self.options.freestanding: self.cpp_info.components["core"].requires.append("fmt::fmt") if compiler == "msvc": self.cpp_info.components["core"].cxxflags = ["/utf-8"] diff --git a/docs/blog/posts/2.2.0-released.md b/docs/blog/posts/2.2.0-released.md index 2ea97e737..936840a15 100644 --- a/docs/blog/posts/2.2.0-released.md +++ b/docs/blog/posts/2.2.0-released.md @@ -31,12 +31,12 @@ the C++ modules' support is still really limited. To benefit from C++ modules, we need at least: - - CMake 3.28.1 + - CMake 3.29.3 - Ninja 1.11 - clang-17 -In the upcoming months, hopefully, the situation will improve with the gcc-14 release and -bug fixes in MSVC. +In the upcoming months, hopefully, the situation will improve with the bug fixes in +CMake, gcc-14, and MSVC. !!! note @@ -64,6 +64,11 @@ flowchart TD The easiest way to use them is just to `import mp_units;` at the beginning of your translation unit (see the [Quick Start](../../getting_started/quick_start.md) chapter for some usage examples). +!!! note + + C++20 modules support still have some issues when imported from the installed CMake target. + See the following [GitLab issue for more details](https://gitlab.kitware.com/cmake/cmake/-/issues/25909#note_1525377). + In this release, we also highly limited the number of CMake targets (:boom: **breaking change** :boom:). Now, they correspond exactly to the C++ modules they provide. This means that many smaller partial targets were removed. We also merged text output targets with the core library's definition. @@ -93,9 +98,9 @@ In version 2.2, the following headers have a new location or contents: Benefiting from this opportunity, we also cleaned up core and systems definitions (:boom: **breaking change** :boom:). -Regarding the library's core, we exposed only one header `framework.h` that exposes all of -the library's framework so the user does not have to enumerate files like `unit.h`, `quantity.h`, -and `quantity_point.h` anymore. Those headers are not gone, they were put to +Regarding the library's core, we removed `core.h` and exposed only one header `framework.h` that +provides all of the library's framework so the user does not have to enumerate files like `unit.h`, +`quantity.h`, and `quantity_point.h` anymore. Those headers are not gone, they were put to the `mp-units/framework` subheader. So they are still there if you really need them. Regarding the changes in systems definitions, we moved the wrapper header files with the entire @@ -161,6 +166,21 @@ Additionally, some CMake options were renamed to better express the impact on ou - `MP_UNITS_DEV_*` - options primarily useful for the project developers or people who want to compile our unit tests and examples. + +## Configurable contracts checking + +Before this release, the library always depended on [gsl-lite](https://github.com/gsl-lite/gsl-lite) +to perform runtime contract and asserts checking. In this release we introduced new options +to control if contract checking should be based on [gsl-lite](https://github.com/gsl-lite/gsl-lite), +[ms-gsl](https://github.com/microsoft/GSL), or maybe completely disabled. + + +## Freestanding support + +From this release it is possible to configure the library in the +[freestanding](https://en.cppreference.com/w/cpp/freestanding) mode. This limits the functionality +and interface of the library to be compatible with the freestanding implementations. + !!! info To learn more, please refer to the [Build options](../../getting_started/installation_and_usage.md#build-options) @@ -191,7 +211,7 @@ origins. For example: ``` As we can see above, the new design allows -[direct-initialization](https://en.cppreference.com/w/cpp/language/direct_initialization) of a +[direct-initializing](https://en.cppreference.com/w/cpp/language/direct_initialization) `quantity_point` class template from a `quantity`, but only if the former one is defined in terms of the implicit point origin. Otherwise, an explicit origin still always has to be provided during initialization. @@ -199,7 +219,7 @@ initialization. Also, we introduced the possibility of specifying a default point origin in the unit definition. With that, we could provide proper temperature scales without forcing the user to always use the origins explicitly. Also, a new member function, `.quantity_from_zero(),` was introduced -that always returns the quantity from the unit's specific point origin or from the absolute +that always returns the quantity from the unit's specific point origin or from the implicit point origin otherwise. === "Now" @@ -244,20 +264,30 @@ named with its corresponding unit and with the `si::zeroth_degree_Celsius` about potential ABI issues when different translation units are compiled with different ordinary literal encodings. Those issues were resolved with a change to units definitions (:boom: **breaking change** :boom:). It affects only units that specify both Unicode and ASCII -symbols. The new design requires the Unicode symbol to be provided as a UTF-8 literal: +symbols. The new design requires the Unicode symbol to be provided as a UTF-8 literal. + +This also means that the `basic_symbol_text` has fixed character types for both symbols. This +is why it was renamed to `symbol_text` (:boom: **breaking change** :boom:). === "Now" ```cpp - inline constexpr struct ohm : named_unit<{u8"Ω", "ohm"}, volt / ampere> {} ohm; + inline constexpr struct ohm : named_unit {} ohm; ``` === "Before" ```cpp - inline constexpr struct ohm : named_unit<{"Ω", "ohm"}, volt / ampere> {} ohm; + inline constexpr struct ohm : named_unit {} ohm; ``` +!!! note + + On C++20-compliant compilers it should be enough to type the following in the unit's definition: + + ```cpp + inline constexpr struct ohm : named_unit<{u8"Ω", "ohm"}, volt / ampere> {} ohm; + ``` ## Improved text output @@ -333,7 +363,7 @@ Example 1 (clang): 61 | concept QuantityOf = Quantity && QuantitySpecOf, QS>; | ^ note: because 'implicitly_convertible(kind_of_ > >{}, struct speed{{{}}})' evaluated to false - 147 | QuantitySpec && QuantitySpec> && implicitly_convertible(T{}, QS) && + 147 | QuantitySpec && QuantitySpec && implicitly_convertible(T{}, QS) && | ^ 1 error generated. Compiler returned: 1 @@ -356,7 +386,7 @@ Example 1 (clang): 61 | concept QuantityOf = Quantity && QuantitySpecOf, QS>; | ^ note: because 'implicitly_convertible(kind_of_ >{{}, {{}}}>{}, struct speed{{{}}})' evaluated to false - 147 | QuantitySpec && QuantitySpec> && implicitly_convertible(T{}, QS) && + 147 | QuantitySpec && QuantitySpec && implicitly_convertible(T{}, QS) && | ^ 1 error generated. Compiler returned: 1 diff --git a/docs/getting_started/cpp_compiler_support.md b/docs/getting_started/cpp_compiler_support.md index 73f1c6c6c..f5fd6fab2 100644 --- a/docs/getting_started/cpp_compiler_support.md +++ b/docs/getting_started/cpp_compiler_support.md @@ -13,13 +13,13 @@ The table below provides the minimum compiler version required to compile the code using a specific C++ feature: -| C++ Feature | C++ version | gcc | clang | apple-clang | MSVC | -|-----------------------------------------------------------|:-----------:|:---:|:-----:|:-----------:|:----:| -| **Minimum support** | 20 | 12 | 16 | 15 | None | -| **`std::format`** | 20 | 13 | 17 | None | None | -| **C++ modules** | 20 | 14 | 17 | None | None | -| **Static `constexpr` variables in `constexpr` functions** | 23 | 13 | 17 | None | None | -| **Explicit `this` parameter** | 23 | 14 | 18 | None | None | +| C++ Feature | C++ version | gcc | clang | apple-clang | MSVC | +|-----------------------------------------------------------|:-----------:|:----:|:-----:|:-----------:|:----:| +| **Minimum support** | 20 | 12 | 16 | 15 | None | +| **`std::format`** | 20 | 13 | 17 | None | None | +| **C++ modules** | 20 | None | 17 | None | None | +| **Static `constexpr` variables in `constexpr` functions** | 23 | 13 | 17 | None | None | +| **Explicit `this` parameter** | 23 | 14 | 18 | None | None | !!! important @@ -29,7 +29,7 @@ C++ feature: ## `std::format` -- Provides [powerful text formatting capabilities](../users_guide/framework_basics/text_output.md#stdformat) +- Provides [powerful text formatting capabilities](../users_guide/framework_basics/text_output.md#text-formatting) for C++. - An alternative [fmtlib](https://github.com/fmtlib/fmt) library can be used instead if - the C++ language feature is not supported, diff --git a/docs/getting_started/faq.md b/docs/getting_started/faq.md index 38373806d..efc919770 100644 --- a/docs/getting_started/faq.md +++ b/docs/getting_started/faq.md @@ -176,7 +176,7 @@ we have to obey the rules and be consistent with ISO specifications. !!! note We do understand engineering reality and the constraints of some environments. This is why the library - has the option of [ASCII-only Quantity Symbols](../users_guide/framework_basics/text_output.md#unit-symbol-formatting). + has the option of [ASCII-only Quantity Symbols](../users_guide/framework_basics/text_output.md#unit_symbol_formatting). ## Why don't we have CMake options to disable the building of tests and examples? diff --git a/docs/getting_started/installation_and_usage.md b/docs/getting_started/installation_and_usage.md index 8d2d1f489..175bf545a 100644 --- a/docs/getting_started/installation_and_usage.md +++ b/docs/getting_started/installation_and_usage.md @@ -18,8 +18,8 @@ projects: - in case this library becomes part of the C++ standard, it will have no external dependencies but until then, it depends on the following: - - [gsl-lite](https://github.com/gsl-lite/gsl-lite) to verify runtime contracts with - the `gsl_Expects` macro, + - [gsl-lite](https://github.com/gsl-lite/gsl-lite) or [ms-gsl](https://github.com/microsoft/GSL) + to verify runtime contracts (if contract checking is enabled), - [{fmt}](https://github.com/fmtlib/fmt) to provide text formatting of quantities (if `std::format` is not supported yet on a specific compiler). @@ -56,7 +56,7 @@ projects: handle the dependencies. To learn more about the rationale, please check our - [FAQ](faq.md#why-dont-we-have-cmake-options-to-disable-building-of-tests-and-examples). + [FAQ](faq.md#why-dont-we-have-cmake-options-to-disable-the-building-of-tests-and-examples). ### Modules @@ -229,7 +229,7 @@ tools.build:compiler_executables={"c": "gcc-12", "cpp": "g++-12"} ### Conan options -[cxx_modules](#cxx_modules){ #cxx_modules } +[`cxx_modules`](#cxx_modules){ #cxx_modules } : [:octicons-tag-24: 2.2.0][conan C++ modules support] · :octicons-milestone-24: `auto`/`True`/`False` (Default: `auto`) @@ -237,7 +237,7 @@ tools.build:compiler_executables={"c": "gcc-12", "cpp": "g++-12"} [conan C++ modules support]: https://github.com/mpusz/mp-units/releases/tag/v2.2.0 -[std_format](#std_format){ #std_format } +[`std_format`](#std_format){ #std_format } : [:octicons-tag-24: 2.2.0][conan std::format support] · :octicons-milestone-24: `auto`/`True`/`False` (Default: `auto`) @@ -247,7 +247,7 @@ tools.build:compiler_executables={"c": "gcc-12", "cpp": "g++-12"} [conan std::format support]: https://github.com/mpusz/mp-units/releases/tag/v2.2.0 -[string_view_ret](#string_view_ret){ #string_view_ret } +[`string_view_ret`](#string_view_ret){ #string_view_ret } : [:octicons-tag-24: 2.2.0][conan returning string_view] · :octicons-milestone-24: `auto`/`True`/`False` (Default: `auto`) @@ -259,7 +259,7 @@ tools.build:compiler_executables={"c": "gcc-12", "cpp": "g++-12"} [conan returning string_view]: https://github.com/mpusz/mp-units/releases/tag/v2.2.0 -[no_crtp](#no_crtp){ #no_crtp } +[`no_crtp`](#no_crtp){ #no_crtp } : [:octicons-tag-24: 2.2.0][conan no crtp support] · :octicons-milestone-24: `auto`/`True`/`False` (Default: `auto`) @@ -268,6 +268,24 @@ tools.build:compiler_executables={"c": "gcc-12", "cpp": "g++-12"} [conan no crtp support]: https://github.com/mpusz/mp-units/releases/tag/v2.2.0 +[`contracts`](#contracts){ #contracts } + +: [:octicons-tag-24: 2.2.0][conan contracts] · :octicons-milestone-24: `none`/`gsl-lite`/`ms-gsl` (Default: `gsl-lite`) + + Enables checking of preconditions and additional asserts in the code. + + [conan contracts]: https://github.com/mpusz/mp-units/releases/tag/v2.2.0 + +[`freestanding`](#freestanding){ #freestanding } + +: [:octicons-tag-24: 2.2.0][conan freestanding] · :octicons-milestone-24: `True`/`False` (Default: `False`) + + Configures the library in the [freestanding](https://en.cppreference.com/w/cpp/freestanding) + mode. When enabled, the library's source code should build with the compiler's + [`-ffreestanding`](https://gcc.gnu.org/onlinedocs/gcc/C-Dialect-Options.html) compilation option + without any issues. + + [conan freestanding]: https://github.com/mpusz/mp-units/releases/tag/v2.2.0 ### Conan configuration properties @@ -276,7 +294,7 @@ tools.build:compiler_executables={"c": "gcc-12", "cpp": "g++-12"} : [:octicons-tag-24: 2.2.0][conan build all support] · :octicons-milestone-24: `True`/`False` (Default: `False`) Enables compilation of all the source code, including tests and examples. To support this, it requires some additional Conan build dependencies described in - [Repository Structure and Dependencies](#repository-structure-and-dependencies). + [Repository directory tree and dependencies](#repository-directory-tree-and-dependencies). It also runs unit tests during Conan build (unless [`tools.build:skip_test`](https://docs.conan.io/2/reference/commands/config.html?highlight=tools.build:skip_test#conan-config-list) configuration property is set to `True`). @@ -296,6 +314,14 @@ tools.build:compiler_executables={"c": "gcc-12", "cpp": "g++-12"} [conan skip la support]: https://github.com/mpusz/mp-units/releases/tag/v2.2.0 +[`user.mp-units.analyze:clang-tidy`](#user.mp-units.analyze-clang-tidy){ #user.mp-units.analyze-clang-tidy } + +: [:octicons-tag-24: 2.2.0][conan clang-tidy support] · :octicons-milestone-24: `True`/`False` (Default: `False`) + + Enables clang-tidy analysis. + + [conan clang-tidy support]: https://github.com/mpusz/mp-units/releases/tag/v2.2.0 + ### CMake options [`MP_UNITS_BUILD_AS_SYSTEM_HEADERS`](#MP_UNITS_BUILD_AS_SYSTEM_HEADERS){ #MP_UNITS_BUILD_AS_SYSTEM_HEADERS } @@ -345,24 +371,50 @@ tools.build:compiler_executables={"c": "gcc-12", "cpp": "g++-12"} [cmake no crtp support]: https://github.com/mpusz/mp-units/releases/tag/v2.2.0 +[`MP_UNITS_API_CONTRACTS`](#MP_UNITS_API_CONTRACTS){ #MP_UNITS_API_CONTRACTS } + +: [:octicons-tag-24: 2.2.0][cmake contracts] · :octicons-milestone-24: `NONE`/`GSL-LITE`/`MS-GSL` (Default: `GSL-LITE`) + + Enables checking of preconditions and additional asserts in the code. + + [cmake contracts]: https://github.com/mpusz/mp-units/releases/tag/v2.2.0 + +[`MP_UNITS_API_FREESTANDING`](#MP_UNITS_API_FREESTANDING){ #MP_UNITS_API_FREESTANDING } + +: [:octicons-tag-24: 2.2.0][cmake freestanding] · :octicons-milestone-24: `ON`/`OFF` (Default: `OFF`) + + Configures the library in the [freestanding](https://en.cppreference.com/w/cpp/freestanding) + mode. When enabled, the library's source code should build with the compiler's + [`-ffreestanding`](https://gcc.gnu.org/onlinedocs/gcc/C-Dialect-Options.html) compilation option + without any issues. + + [cmake freestanding]: https://github.com/mpusz/mp-units/releases/tag/v2.2.0 + #### Options for mp-units project developers [`MP_UNITS_DEV_BUILD_LA`](#MP_UNITS_DEV_BUILD_LA){ #MP_UNITS_DEV_BUILD_LA } -: [:octicons-tag-24: 2.0.0][build la support] · :octicons-milestone-24: `ON`/`OFF` (Default: `ON`) +: [:octicons-tag-24: 2.2.0][cmake build la support] · :octicons-milestone-24: `ON`/`OFF` (Default: `ON`) Enables building code depending on the linear algebra library. - [build la support]: https://github.com/mpusz/mp-units/releases/tag/v2.0.0 - + [cmake build la support]: https://github.com/mpusz/mp-units/releases/tag/v2.2.0 [`MP_UNITS_DEV_IWYU`](#MP_UNITS_DEV_IWYU){ #MP_UNITS_DEV_IWYU } -: [:octicons-tag-24: 2.0.0][iwyu support] · :octicons-milestone-24: `ON`/`OFF` (Default: `OFF`) +: [:octicons-tag-24: 2.2.0][cmake iwyu support] · :octicons-milestone-24: `ON`/`OFF` (Default: `OFF`) + + Enables include-what-you-use analysis. + + [cmake iwyu support]: https://github.com/mpusz/mp-units/releases/tag/v2.2.0 + +[`MP_UNITS_DEV_CLANG_TIDY`](#MP_UNITS_DEV_CLANG_TIDY){ #MP_UNITS_DEV_CLANG_TIDY } + +: [:octicons-tag-24: 2.2.0][cmake clang-tidy support] · :octicons-milestone-24: `ON`/`OFF` (Default: `OFF`) - Enables `include-what-you-use` when compiling with a clang compiler. + Enables clang-tidy analysis. - [iwyu support]: https://github.com/mpusz/mp-units/releases/tag/v2.0.0 + [cmake clang-tidy support]: https://github.com/mpusz/mp-units/releases/tag/v2.2.0 ## CMake with presets support diff --git a/docs/getting_started/introduction.md b/docs/getting_started/introduction.md index fadce399b..7efe47cfb 100644 --- a/docs/getting_started/introduction.md +++ b/docs/getting_started/introduction.md @@ -80,6 +80,6 @@ To achieve this goal, several techniques are applied: [Highly adjustable text-output formatting]: ../users_guide/framework_basics/text_output.md [Each entity can be defined with a single line of code]: ../users_guide/framework_basics/interface_introduction.md#new-style-of-definitions -[User can easily extend the systems with custom dimensions, quantities, and units]: ../users_guide/use_cases/extending_the_library.md#new-style-of-definitions +[User can easily extend the systems with custom dimensions, quantities, and units]: ../users_guide/use_cases/extending_the_library.md [freestanding]: https://en.cppreference.com/w/cpp/freestanding diff --git a/docs/getting_started/look_and_feel.md b/docs/getting_started/look_and_feel.md index b87d54702..a8bfb8244 100644 --- a/docs/getting_started/look_and_feel.md +++ b/docs/getting_started/look_and_feel.md @@ -150,7 +150,7 @@ performed without sacrificing accuracy. Please see the below example for a quick } ``` -!!! example "[Try it on Compiler Explorer](https://godbolt.org/z/fsdovTcYh)" +!!! example "[Try it on Compiler Explorer](https://godbolt.org/z/nhqhT8Mzb)" !!! note diff --git a/docs/users_guide/framework_basics/concepts.md b/docs/users_guide/framework_basics/concepts.md index 5faab7302..494675a6a 100644 --- a/docs/users_guide/framework_basics/concepts.md +++ b/docs/users_guide/framework_basics/concepts.md @@ -95,7 +95,7 @@ and when `T` is implicitly convertible to `V`. and is satisfied by: - All units derived from a `named_unit` class template instantiated with a unique symbol identifier - and a [`QuantitySpec`](#quantityspec) of a [quantity kind](../../appendix/glossary.md#kind). + and a [`QuantitySpec`](#QuantitySpec) of a [quantity kind](../../appendix/glossary.md#kind). - All units being a result of [unit equations](../../appendix/glossary.md#unit-equation) on other associated units. diff --git a/docs/users_guide/framework_basics/design_overview.md b/docs/users_guide/framework_basics/design_overview.md index ce7f44413..da38cd955 100644 --- a/docs/users_guide/framework_basics/design_overview.md +++ b/docs/users_guide/framework_basics/design_overview.md @@ -231,8 +231,8 @@ For example: A unit can be defined by the user in one of the following ways: ```cpp -template struct kilo_ : prefixed_unit<"k", mag_power<10, 3>, U> {}; -template inline constexpr kilo_ kilo; +template struct kilo_ : prefixed_unit<"k", mag_power<10, 3>, U{}> {}; +template inline constexpr kilo_ kilo; inline constexpr struct second : named_unit<"s", kind_of> {} second; inline constexpr struct minute : named_unit<"min", mag<60> * second> {} minute; diff --git a/docs/users_guide/framework_basics/systems_of_units.md b/docs/users_guide/framework_basics/systems_of_units.md index 8f3410236..3d263c112 100644 --- a/docs/users_guide/framework_basics/systems_of_units.md +++ b/docs/users_guide/framework_basics/systems_of_units.md @@ -137,8 +137,8 @@ unit magnitude in a more flexible way. Each prefix is implemented similarly to the following: ```cpp -template struct quecto_ : prefixed_unit<"q", mag_power<10, -30>, U> {}; -template inline constexpr quecto_ quecto; +template struct quecto_ : prefixed_unit<"q", mag_power<10, -30>, U{}> {}; +template inline constexpr quecto_ quecto; ``` and then a [PrefixableUnit](concepts.md#PrefixableUnit) can be prefixed in the following @@ -153,8 +153,8 @@ efficiently represent any rational magnitude. For example, IEC 80000 prefixes us IT industry can be implemented as: ```cpp -template struct yobi_ : prefixed_unit<"Yi", mag_power<2, 80>, U> {}; -template inline constexpr yobi_ yobi; +template struct yobi_ : prefixed_unit<"Yi", mag_power<2, 80>, U{}> {}; +template inline constexpr yobi_ yobi; ``` ## Scaled units diff --git a/docs/users_guide/framework_basics/text_output.md b/docs/users_guide/framework_basics/text_output.md index 124cf5d35..de8c96eaa 100644 --- a/docs/users_guide/framework_basics/text_output.md +++ b/docs/users_guide/framework_basics/text_output.md @@ -62,14 +62,14 @@ and units of derived quantities. === "Prefixes" ```cpp - template struct micro_ : prefixed_unit<{u8"µ", "u"}, mag_power<10, -6>, U> {}; - template struct milli_ : prefixed_unit<"m", mag_power<10, -3>, U> {}; - template struct centi_ : prefixed_unit<"c", mag_power<10, -2>, U> {}; - template struct deci_ : prefixed_unit<"d", mag_power<10, -1>, U> {}; - template struct deca_ : prefixed_unit<"da", mag_power<10, 1>, U> {}; - template struct hecto_ : prefixed_unit<"h", mag_power<10, 2>, U> {}; - template struct kilo_ : prefixed_unit<"k", mag_power<10, 3>, U> {}; - template struct mega_ : prefixed_unit<"M", mag_power<10, 6>, U> {}; + template struct micro_ : prefixed_unit<{u8"µ", "u"}, mag_power<10, -6>, U{}> {}; + template struct milli_ : prefixed_unit<"m", mag_power<10, -3>, U{}> {}; + template struct centi_ : prefixed_unit<"c", mag_power<10, -2>, U{}> {}; + template struct deci_ : prefixed_unit<"d", mag_power<10, -1>, U{}> {}; + template struct deca_ : prefixed_unit<"da", mag_power<10, 1>, U{}> {}; + template struct hecto_ : prefixed_unit<"h", mag_power<10, 2>, U{}> {}; + template struct kilo_ : prefixed_unit<"k", mag_power<10, 3>, U{}> {}; + template struct mega_ : prefixed_unit<"M", mag_power<10, 6>, U{}> {}; ``` === "Constants" @@ -241,7 +241,7 @@ static_assert(unit_symbol<{.solidus = unit_symbol_solidus::never, `std::string_view` is returned only when C++23 is available. Otherwise, an instance of a `basic_fixed_string` is being returned. See more in the - [C++ compiler support](../../getting_started/installation_and_usage.md#static-constexpr-variables-in-constexpr-functions) + [C++ compiler support](../../getting_started/cpp_compiler_support.md#static-constexpr-variables-in-constexpr-functions) chapter. #### `unit_symbol_to()` diff --git a/docs/users_guide/framework_basics/the_affine_space.md b/docs/users_guide/framework_basics/the_affine_space.md index 62c00efb9..c21bceea9 100644 --- a/docs/users_guide/framework_basics/the_affine_space.md +++ b/docs/users_guide/framework_basics/the_affine_space.md @@ -219,7 +219,7 @@ the origin and the _displacement vector_ measured from it to the point we create [Why can't I create a quantity by passing a number to a constructor?](../../getting_started/faq.md#why-cant-i-create-a-quantity-by-passing-a-number-to-a-constructor) chapter. -Similarly to [creation of a quantity](../../getting_started/quick_start.md#creating-a-quantity), +Similarly to [creation of a quantity](../../getting_started/quick_start.md#quantities), if someone does not like the operator-based syntax to create a `quantity_point`, the same results can be achieved with a two-parameter constructor: @@ -403,7 +403,7 @@ inline constexpr struct zeroth_degree_Celsius : decltype(ice_point) {} zeroth_de namespace usc { inline constexpr struct zeroth_degree_Fahrenheit : - relative_point_origin * si::degree_Celsius)> {} zeroth_degree_Fahrenheit; + relative_point_origin * si::degree_Celsius)}> {} zeroth_degree_Fahrenheit; } ``` diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 9e08103d1..5325cd8d2 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -23,21 +23,37 @@ cmake_minimum_required(VERSION 3.5) # find dependencies -if(NOT TARGET gsl::gsl-lite) - find_package(gsl-lite REQUIRED) -endif() - if(${projectPrefix}BUILD_CXX_MODULES) add_library(example_utils INTERFACE) target_compile_features(example_utils INTERFACE cxx_std_20) target_compile_definitions(example_utils INTERFACE ${projectPrefix}MODULES) target_include_directories(example_utils INTERFACE include) - target_link_libraries(example_utils INTERFACE gsl::gsl-lite) endif() add_library(example_utils-headers INTERFACE) target_include_directories(example_utils-headers INTERFACE include) -target_link_libraries(example_utils-headers INTERFACE gsl::gsl-lite) + +if(${projectPrefix}API_CONTRACTS STREQUAL "NONE") + target_compile_definitions(mp-units-core INTERFACE ${projectPrefix}API_CONTRACTS=0) +elseif(${projectPrefix}API_CONTRACTS STREQUAL "GSL-LITE") + if(NOT TARGET gsl::gsl-lite) + find_package(gsl-lite REQUIRED) + endif() + if(${projectPrefix}BUILD_CXX_MODULES) + target_link_libraries(example_utils INTERFACE gsl::gsl-lite) + endif() + target_link_libraries(example_utils-headers INTERFACE gsl::gsl-lite) + target_compile_definitions(mp-units-core INTERFACE ${projectPrefix}API_CONTRACTS=2) +elseif(${projectPrefix}API_CONTRACTS STREQUAL "MS-GSL") + if(NOT TARGET Microsoft.GSL::GSL) + find_package(Microsoft.GSL REQUIRED) + endif() + if(${projectPrefix}BUILD_CXX_MODULES) + target_link_libraries(example_utils INTERFACE Microsoft.GSL::GSL) + endif() + target_link_libraries(example_utils-headers INTERFACE Microsoft.GSL::GSL) + target_compile_definitions(mp-units-core INTERFACE ${projectPrefix}API_CONTRACTS=3) +endif() # # add_example(target ...) @@ -72,4 +88,4 @@ add_example(total_energy) add_example(unmanned_aerial_vehicle example_utils) add_subdirectory(glide_computer_lib) -# add_subdirectory(kalman_filter) +add_subdirectory(kalman_filter) diff --git a/example/glide_computer.cpp b/example/glide_computer.cpp index 533002a1b..4da868721 100644 --- a/example/glide_computer.cpp +++ b/example/glide_computer.cpp @@ -90,8 +90,6 @@ void print(const R& gliders) for (const auto& p : g.polar) { const auto ratio = glide_ratio(g.polar[0]).force_in(one); std::cout << MP_UNITS_STD_FMT::format(" * {::N[.4]} @ {::N[.1]} -> {::N[.1]} ({::N[.1]})\n", p.climb, p.v, ratio, - // TODO is it possible to make ADL work below (we need another set of trig - // functions for strong angle in a different namespace) si::asin(1 / ratio).force_in(si::degree)); } std::cout << "\n"; @@ -168,8 +166,6 @@ void example() const auto weather_conditions = get_weather_conditions(); const task t = {waypoints[0], waypoints[1], waypoints[0]}; const aircraft_tow tow = {400 * m, 1.6 * m / s}; - // TODO use C++20 date library when available - // set `start_time` to 11:00 am today const timestamp start_time(std::chrono::system_clock::now()); print(sfty); diff --git a/example/include/validated_type.h b/example/include/validated_type.h index 8b05aa8f6..9a429e26b 100644 --- a/example/include/validated_type.h +++ b/example/include/validated_type.h @@ -22,7 +22,6 @@ #pragma once -#include #include #include #include // IWYU pragma: export @@ -50,13 +49,13 @@ class validated_type { requires std::copyable : value_(value) { - gsl_Expects(validate(value_)); + MP_UNITS_EXPECTS(validate(value_)); } constexpr explicit validated_type(T&& value) noexcept(std::is_nothrow_move_constructible_v) : value_(std::move(value)) { - gsl_Expects(validate(value_)); + MP_UNITS_EXPECTS(validate(value_)); } constexpr validated_type(const T& value, validated_tag) noexcept(std::is_nothrow_copy_constructible_v) diff --git a/example/kalman_filter/kalman.h b/example/kalman_filter/kalman.h index 9ae5b4621..866590650 100644 --- a/example/kalman_filter/kalman.h +++ b/example/kalman_filter/kalman.h @@ -23,21 +23,23 @@ #pragma once #include +#include +#include #include #ifdef MP_UNITS_MODULES import mp_units; #else +#include #include +#include +#include #include -#include -#include -#include +#include #endif namespace kalman { -template -concept QuantityOrQuantityPoint = mp_units::Quantity || mp_units::QuantityPoint; +namespace detail { template inline constexpr bool are_time_derivatives = false; @@ -49,192 +51,262 @@ template = (D1 / D2 == mp_units::isq::dim_time) && are_time_derivatives; -// state -template - requires(sizeof...(QQPs) > 0) && (sizeof...(QQPs) <= 3) && are_time_derivatives -struct state { - std::tuple variables_; - constexpr state(QQPs... qqps) : variables_(std::move(qqps)...) {} +} // namespace detail + +// system state +template + requires(sizeof...(QPs) > 0) && (sizeof...(QPs) <= 3) && detail::are_time_derivatives +class system_state { + std::tuple variables_; +public: + constexpr explicit system_state(QPs... qps) : variables_(std::move(qps)...) {} + + template + [[nodiscard]] friend constexpr auto& get(system_state& s) + { + return get(s.variables_); + } + + template + [[nodiscard]] friend constexpr const auto& get(const system_state& s) + { + return get(s.variables_); + } }; template -concept State = mp_units::is_specialization_of; - -template -constexpr auto& get(state& s) -{ - return get(s.variables_); -} +concept SystemState = mp_units::is_specialization_of; -template -constexpr const auto& get(const state& s) -{ - return get(s.variables_); -} -// estimation -template -struct estimation { +// system state estimation +template + requires requires { typename system_state; } +class system_state_estimate { +public: + using state_type = system_state; + using standard_deviation_type = QP::quantity_type; + using variance_type = + mp_units::quantity(standard_deviation_type::reference), typename standard_deviation_type::rep>; private: - static constexpr auto uncertainty_ref = QQP::reference * QQP::reference; - using uncertainty_type = mp_units::quantity; + state_type state_; + variance_type variance_; public: - kalman::state state; // TODO extend kalman functions to work with this variadic parameter list - uncertainty_type uncertainty; + constexpr system_state_estimate(state_type state, standard_deviation_type standard_deviation) : + state_(state), variance_(pow<2>(standard_deviation)) + { + } + constexpr system_state_estimate(state_type state, variance_type variance) : state_(state), variance_(variance) {} + [[nodiscard]] constexpr const state_type& state() const { return state_; } + [[nodiscard]] constexpr const variance_type& variance() const { return variance_; } + [[nodiscard]] constexpr standard_deviation_type standard_deviation() const { return sqrt(variance_); } }; -template -estimation(state, U) -> estimation; - // kalman gain -template -constexpr mp_units::quantity kalman_gain(Q estimate_uncertainty, - Q measurement_uncertainty) +template + requires requires { mp_units::common_reference(Q1::reference, Q2::reference); } +[[nodiscard]] constexpr mp_units::quantity kalman_gain( + Q1 variance_in_estimate, Q2 variance_in_measurement) { - return estimate_uncertainty / (estimate_uncertainty + measurement_uncertainty); + return variance_in_estimate / (variance_in_estimate + variance_in_measurement); } // state update -template K> - requires(implicitly_convertible(QM::quantity_spec, Q::quantity_spec)) -constexpr state state_update(const state& predicted, QM measured, K gain) +template K> + requires(implicitly_convertible(QM::quantity_spec, QP::quantity_spec)) +[[nodiscard]] constexpr system_state state_update(const system_state& predicted, QM measured, K gain) { - return {get<0>(predicted) + gain * (measured - get<0>(predicted))}; + return system_state{get<0>(predicted) + gain * (measured - get<0>(predicted))}; } -template K, +template K, mp_units::QuantityOf T> - requires(implicitly_convertible(QM::quantity_spec, Q1::quantity_spec)) -constexpr state state_update(const state& predicted, QM measured, std::array gain, T interval) + requires(implicitly_convertible(QM::quantity_spec, QP1::quantity_spec)) +[[nodiscard]] constexpr system_state state_update(const system_state& predicted, QM measured, + std::array gain, T interval) { - const auto q1 = get<0>(predicted) + get<0>(gain) * (measured - get<0>(predicted)); - const auto q2 = get<1>(predicted) + get<1>(gain) * (measured - get<0>(predicted)) / interval; - return {q1, q2}; + const auto qp1 = fma(get<0>(gain), measured - get<0>(predicted), get<0>(predicted)); + const auto qp2 = fma(get<1>(gain), (measured - get<0>(predicted)) / interval, get<1>(predicted)); + return system_state{qp1, qp2}; } -template K, mp_units::QuantityOf T> - requires(implicitly_convertible(QM::quantity_spec, Q1::quantity_spec)) -constexpr state state_update(const state& predicted, QM measured, std::array gain, - T interval) + requires(implicitly_convertible(QM::quantity_spec, QP1::quantity_spec)) +[[nodiscard]] constexpr system_state state_update(const system_state& predicted, + QM measured, std::array gain, T interval) { - const auto q1 = get<0>(predicted) + get<0>(gain) * (measured - get<0>(predicted)); - const auto q2 = get<1>(predicted) + get<1>(gain) * (measured - get<0>(predicted)) / interval; - const auto q3 = get<2>(predicted) + get<2>(gain) * (measured - get<0>(predicted)) / (interval * interval / 2); - return {q1, q2, q3}; + const auto qp1 = fma(get<0>(gain), measured - get<0>(predicted), get<0>(predicted)); + const auto qp2 = fma(get<1>(gain), (measured - get<0>(predicted)) / interval, get<1>(predicted)); + const auto qp3 = fma(get<2>(gain), (measured - get<0>(predicted)) / (interval * interval / 2), get<2>(predicted)); + return system_state{qp1, qp2, qp3}; } // covariance update template K> -constexpr Q covariance_update(Q uncertainty, K gain) +[[nodiscard]] constexpr Q covariance_update(Q uncertainty, K gain) { return (1 * mp_units::one - gain) * uncertainty; } +template K> +[[nodiscard]] constexpr system_state_estimate state_estimate_update( + const system_state_estimate& previous, QP measurement, K gain) +{ + return {state_update(previous.state(), measurement, gain), covariance_update(previous.variance(), gain)}; +}; + + // state extrapolation -template T> -constexpr state state_extrapolation(const state& estimated, T interval) +template T> +[[nodiscard]] constexpr system_state state_extrapolation(const system_state& estimated, T interval) { - const auto q1 = get<0>(estimated) + get<1>(estimated) * interval; - const auto q2 = get<1>(estimated); - return {q1, q2}; + auto to_quantity = [](const auto& qp) { return qp.quantity_ref_from(qp.point_origin); }; + const auto qp1 = fma(to_quantity(get<1>(estimated)), interval, get<0>(estimated)); + const auto qp2 = get<1>(estimated); + return system_state{qp1, qp2}; } -template T> -constexpr state state_extrapolation(const state& estimated, T interval) +template T> +[[nodiscard]] constexpr system_state state_extrapolation(const system_state& estimated, + T interval) { - const auto q1 = get<0>(estimated) + get<1>(estimated) * interval + get<2>(estimated) * pow<2>(interval) / 2; - const auto q2 = get<1>(estimated) + get<2>(estimated) * interval; - const auto q3 = get<2>(estimated); - return {q1, q2, q3}; + auto to_quantity = [](const auto& qp) { return qp.quantity_ref_from(qp.point_origin); }; + const auto qp1 = to_quantity(get<2>(estimated)) * pow<2>(interval) / 2 + + fma(to_quantity(get<1>(estimated)), interval, get<0>(estimated)); + const auto qp2 = fma(to_quantity(get<2>(estimated)), interval, get<1>(estimated)); + const auto qp3 = get<2>(estimated); + return system_state{qp1, qp2, qp3}; } // covariance extrapolation -template -constexpr Q covariance_extrapolation(Q uncertainty, Q process_noise_variance) +template + requires requires { mp_units::common_reference(Q1::reference, Q2::reference); } +[[nodiscard]] constexpr mp_units::Quantity auto covariance_extrapolation(Q1 uncertainty, Q2 process_noise_variance) { return uncertainty + process_noise_variance; } } // namespace kalman -template -struct MP_UNITS_STD_FMT::formatter> { - constexpr auto parse(format_parse_context& ctx) +template +struct MP_UNITS_STD_FMT::formatter, Char> : + MP_UNITS_STD_FMT::formatter::quantity_type> { + template + auto format(const mp_units::quantity_point& qp, FormatContext& ctx) const -> decltype(ctx.out()) { - mp_units::detail::dynamic_specs_handler handler(specs, ctx); - return mp_units::detail::parse_format_specs(ctx.begin(), ctx.end(), handler); + return MP_UNITS_STD_FMT::formatter::quantity_type>::format( + qp.quantity_ref_from(qp.point_origin), ctx); } +}; - template - auto format(const kalman::state& s, FormatContext& ctx) +template +class MP_UNITS_STD_FMT::formatter, Char> { + using format_specs = mp_units::detail::fill_align_width_format_specs; + format_specs specs_{}; + std::array, sizeof...(QPs)> format_str_; + std::tuple...> formatters_{}; + + template + constexpr const Char* parse_default_spec(const Char* begin, const Char* end, Formatter& f, std::string& format_str) { - std::string value_buffer; - auto to_value_buffer = std::back_inserter(value_buffer); - if (specs.precision != -1) { - if constexpr (sizeof...(Qs) == 1) - MP_UNITS_STD_FMT::format_to(to_value_buffer, "{1:%.{0}Q %q}", specs.precision, kalman::get<0>(s)); - else if constexpr (sizeof...(Qs) == 2) - MP_UNITS_STD_FMT::format_to(to_value_buffer, "{{ {1:%.{0}Q %q}, {2:%.{0}Q %q} }}", specs.precision, - kalman::get<0>(s), kalman::get<1>(s)); - else - MP_UNITS_STD_FMT::format_to(to_value_buffer, "{{ {1:%.{0}Q %q}, {2:%.{0}Q %q}, {3:%.{0}Q %q} }}", - specs.precision, kalman::get<0>(s), kalman::get<1>(s), kalman::get<2>(s)); - } else { - if constexpr (sizeof...(Qs) == 1) - MP_UNITS_STD_FMT::format_to(to_value_buffer, "{}", kalman::get<0>(s)); - else if constexpr (sizeof...(Qs) == 2) - MP_UNITS_STD_FMT::format_to(to_value_buffer, "{{ {}, {} }}", kalman::get<0>(s), kalman::get<1>(s)); - else - MP_UNITS_STD_FMT::format_to(to_value_buffer, "{{ {}, {}, {} }}", kalman::get<0>(s), kalman::get<1>(s), - kalman::get<2>(s)); + if (begin == end || *begin++ != '[') + throw MP_UNITS_STD_FMT::format_error("`default-spec` should contain a `[` character"); + auto it = begin; + for (int nested_brackets = 0; it != end && !(*it == ']' && nested_brackets == 0); it++) { + if (*it == '[') ++nested_brackets; + if (*it == ']') { + if (nested_brackets == 0) throw MP_UNITS_STD_FMT::format_error("unmatched ']' in format string"); + --nested_brackets; + } } + format_str = "{:" + std::string(begin, it) + '}'; + if (it == end) throw MP_UNITS_STD_FMT::format_error("unmatched '[' in format string"); + MP_UNITS_STD_FMT::basic_format_parse_context ctx(std::string_view(begin, it)); + auto ptr = f.parse(ctx); + if (ptr != it) throw MP_UNITS_STD_FMT::format_error("invalid subentity format '" + std::string(begin, it) + "'"); + return ++it; // skip `]` + } + + template + [[nodiscard]] constexpr const Char* parse_default_spec(const Char* begin, const Char* end, size_t idx, + std::index_sequence) + { + auto parse = [&](bool flag, auto& f, std::basic_string& str) { + return flag ? parse_default_spec(begin, end, f, str) : begin; + }; + return std::max({parse(idx == Is, std::get(formatters_), format_str_[Is])...}); + } - std::string global_format_buffer; - mp_units::detail::quantity_global_format_specs global_specs = {specs.fill, specs.align, specs.width}; - mp_units::detail::format_global_buffer(std::back_inserter(global_format_buffer), global_specs); + [[nodiscard]] constexpr const Char* parse_defaults_specs(const Char* begin, const Char* end) + { + if (begin == end || *begin == '}') return begin; + if (*begin++ != ':') throw MP_UNITS_STD_FMT::format_error("`defaults-specs` should start with a `:`"); + do { + auto c = *begin++; + if (c < '0' || c >= static_cast('0' + sizeof...(QPs))) + throw MP_UNITS_STD_FMT::format_error(std::string("unknown `subentity-id` token '") + c + "'"); + const size_t idx = static_cast(c - '0'); + begin = parse_default_spec(begin, end, idx, std::index_sequence_for{}); + } while (begin != end && *begin != '}'); + return begin; + } - return MP_UNITS_STD_FMT::vformat_to(ctx.out(), global_format_buffer, - MP_UNITS_STD_FMT::make_format_args(value_buffer)); + template + OutputIt format_system_state(OutputIt out, const kalman::system_state& s, FormatContext& ctx, + std::index_sequence) const + { + const std::locale locale = MP_UNITS_FMT_LOCALE(ctx.locale()); + auto f = [&](const std::basic_string& str, const mp_units::QuantityPoint auto& qp) { + return MP_UNITS_STD_FMT::vformat_to(out, locale, str, MP_UNITS_STD_FMT::make_format_args(qp)); + }; + return f(get(format_str_), get(s)); } -private: - mp_units::detail::dynamic_format_specs specs; -}; -template -struct MP_UNITS_STD_FMT::formatter> { - constexpr auto parse(format_parse_context& ctx) + template + requires(sizeof...(Rest) > 0) + OutputIt format_system_state(OutputIt out, const kalman::system_state& s, FormatContext& ctx, + std::index_sequence) const { - mp_units::detail::dynamic_specs_handler handler(specs, ctx); - return mp_units::detail::parse_format_specs(ctx.begin(), ctx.end(), handler); + const std::locale locale = MP_UNITS_FMT_LOCALE(ctx.locale()); + auto f = [&](const std::basic_string& str, const mp_units::QuantityPoint auto& qp) { + return MP_UNITS_STD_FMT::vformat_to(out, locale, str, MP_UNITS_STD_FMT::make_format_args(qp)); + }; + return f(get(format_str_), get(s)), ((*out++ = ' ', f(get(format_str_), get(s))), ...); + } + +public: + constexpr formatter() + { + for (auto& str : format_str_) str = "{}"; + } + + constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) + { + auto begin = ctx.begin(), end = ctx.end(); + + begin = parse_fill_align_width(ctx, begin, end, specs_, mp_units::detail::fmt_align::right); + if (begin == end) return begin; + + return parse_defaults_specs(begin, end); } template - auto format(kalman::estimation e, FormatContext& ctx) + auto format(const kalman::system_state& s, FormatContext& ctx) const -> decltype(ctx.out()) { - mp_units::Quantity auto q = [](const Q& t) { - if constexpr (mp_units::Quantity) - return t; - else - return t.quantity_ref_from(t.point_origin); - }(kalman::get<0>(e.state)); - - std::string value_buffer; - auto to_value_buffer = std::back_inserter(value_buffer); - if (specs.precision != -1) { - MP_UNITS_STD_FMT::format_to(to_value_buffer, "{0:%.{2}Q} ± {1:%.{2}Q} {0:%q}", q, sqrt(e.uncertainty), - specs.precision); - } else { - MP_UNITS_STD_FMT::format_to(to_value_buffer, "{0:%Q} ± {1:%Q} {0:%q}", q, sqrt(e.uncertainty)); - } + auto specs = specs_; + mp_units::detail::handle_dynamic_spec(specs.width, specs.width_ref, ctx); - std::string global_format_buffer; - mp_units::detail::quantity_global_format_specs global_specs = {specs.fill, specs.align, specs.width}; - mp_units::detail::format_global_buffer(std::back_inserter(global_format_buffer), global_specs); + if (specs.width == 0) { + // Avoid extra copying if width is not specified + format_system_state(ctx.out(), s, ctx, std::index_sequence_for{}); + return ctx.out(); + } + std::basic_string quantity_buffer; + format_system_state(std::back_inserter(quantity_buffer), s, ctx, std::index_sequence_for{}); - return MP_UNITS_STD_FMT::vformat_to(ctx.out(), global_format_buffer, - MP_UNITS_STD_FMT::make_format_args(value_buffer)); + std::basic_string fill_align_width_format_str; + mp_units::detail::format_global_buffer(std::back_inserter(fill_align_width_format_str), specs); + return MP_UNITS_STD_FMT::vformat_to(ctx.out(), fill_align_width_format_str, + MP_UNITS_STD_FMT::make_format_args(quantity_buffer)); } -private: - mp_units::detail::dynamic_format_specs specs; }; diff --git a/example/kalman_filter/kalman_filter-example_1.cpp b/example/kalman_filter/kalman_filter-example_1.cpp index c038f253e..a5f8778ad 100644 --- a/example/kalman_filter/kalman_filter-example_1.cpp +++ b/example/kalman_filter/kalman_filter-example_1.cpp @@ -35,36 +35,37 @@ import mp_units; using namespace mp_units; -void print_header(const kalman::State auto& initial) +void print_header(const kalman::SystemState auto& initial) { std::cout << MP_UNITS_STD_FMT::format("Initial: {}\n", initial); std::cout << MP_UNITS_STD_FMT::format("{:>2} | {:>9} | {:>8} | {:>14} | {:>14}\n", "N", "Gain", "Measured", "Curr. Estimate", "Next Estimate"); } -void print(auto iteration, QuantityOf auto gain, Quantity auto measured, - const kalman::State auto& current, const kalman::State auto& next) +void print(auto iteration, QuantityOf auto gain, QuantityPoint auto measured, + const kalman::SystemState auto& current, const kalman::SystemState auto& next) { - std::cout << MP_UNITS_STD_FMT::format("{:2} | {:9} | {:8} | {:14} | {:14}\n", iteration, gain, measured, current, - next); + std::cout << MP_UNITS_STD_FMT::format("{:2} | {:9:N[.2f]} | {:8} | {:14:0[:N[.2f]]} | {:14:0[:N[.2f]]}\n", iteration, + gain, measured, current, next); } int main() { using namespace mp_units::si::unit_symbols; - using state = kalman::state>; + using state = kalman::system_state>; + using qp = quantity_point; - const state initial = {1 * kg}; - const std::array measurements = {1'030 * g, 989 * g, 1'017 * g, 1'009 * g, 1'013 * g, - 979 * g, 1'008 * g, 1'042 * g, 1'012 * g, 1'011 * g}; + const state initial_guess{qp{1 * kg}}; + const std::array measurements = {qp{996 * g}, qp{994 * g}, qp{1021 * g}, qp{1000 * g}, qp{1002 * g}, + qp{1010 * g}, qp{983 * g}, qp{971 * g}, qp{993 * g}, qp{1023 * g}}; - print_header(initial); - state next = initial; - for (int index = 1; const auto& v : measurements) { - const auto& previous = next; - const auto gain = 1. / index * one; - const auto current = state_update(previous, v, gain); + print_header(initial_guess); + state next = initial_guess; + for (int index = 1; const auto& measurement : measurements) { + const state& previous = next; + const quantity gain = 1. / index * one; + const state current = state_update(previous, measurement, gain); next = current; - print(index++, gain, v, current, next); + print(index++, gain, measurement, current, next); } } diff --git a/example/kalman_filter/kalman_filter-example_2.cpp b/example/kalman_filter/kalman_filter-example_2.cpp index 0260d2de3..d9fab7f96 100644 --- a/example/kalman_filter/kalman_filter-example_2.cpp +++ b/example/kalman_filter/kalman_filter-example_2.cpp @@ -39,36 +39,38 @@ inline constexpr bool mp_units::is_vector = true; using namespace mp_units; -void print_header(const kalman::State auto& initial) +void print_header(const kalman::SystemState auto& initial) { std::cout << MP_UNITS_STD_FMT::format("Initial: {}\n", initial); std::cout << MP_UNITS_STD_FMT::format("{:>2} | {:>8} | {:>23} | {:>23}\n", "N", "Measured", "Curr. Estimate", "Next Estimate"); } -void print(auto iteration, Quantity auto measured, const kalman::State auto& current, const kalman::State auto& next) +void print(auto iteration, QuantityPoint auto measured, const kalman::SystemState auto& current, + const kalman::SystemState auto& next) { - std::cout << MP_UNITS_STD_FMT::format("{:2} | {:8} | {:.1} | {:.1}\n", iteration, measured, current, next); + std::cout << MP_UNITS_STD_FMT::vformat("{:2} | {:8} | {:23:0[:N[.2f]]1[:N[.2f]]} | {:23:0[:N[.2f]]1[:N[.2f]]}\n", + MP_UNITS_STD_FMT::make_format_args(iteration, measured, current, next)); } int main() { using namespace mp_units::si::unit_symbols; - using state = kalman::state, quantity>; + using qp = quantity_point; + using state = kalman::system_state>; - const auto interval = isq::duration(5 * s); - const state initial = {30 * km, 40 * m / s}; - const quantity measurements[] = {30'110 * m, 30'265 * m, 30'740 * m, 30'750 * m, - 31'135 * m, 31'015 * m, 31'180 * m, 31'610 * m, - 31'960 * m, 31'865 * m}; + const quantity interval = isq::duration(5 * s); + const state initial{qp{30 * km}, quantity_point{40 * m / s}}; + const std::array measurements = {qp{30'171 * m}, qp{30'353 * m}, qp{30'756 * m}, qp{30'799 * m}, qp{31'018 * m}, + qp{31'278 * m}, qp{31'276 * m}, qp{31'379 * m}, qp{31'748 * m}, qp{32'175 * m}}; std::array gain = {0.2 * one, 0.1 * one}; print_header(initial); state next = state_extrapolation(initial, interval); - for (int index = 1; const auto& measured : measurements) { - const auto& previous = next; - const auto current = state_update(previous, measured, gain, interval); + for (int index = 1; const auto& measurement : measurements) { + const state& previous = next; + const state current = state_update(previous, measurement, gain, interval); next = state_extrapolation(current, interval); - print(index++, measured, current, next); + print(index++, measurement, current, next); } } diff --git a/example/kalman_filter/kalman_filter-example_3.cpp b/example/kalman_filter/kalman_filter-example_3.cpp index 5d453d481..931cba60a 100644 --- a/example/kalman_filter/kalman_filter-example_3.cpp +++ b/example/kalman_filter/kalman_filter-example_3.cpp @@ -39,36 +39,38 @@ inline constexpr bool mp_units::is_vector = true; using namespace mp_units; -void print_header(const kalman::State auto& initial) +void print_header(const kalman::SystemState auto& initial) { std::cout << MP_UNITS_STD_FMT::format("Initial: {}\n", initial); - std::cout << MP_UNITS_STD_FMT::format("{:>2} | {:>8} | {:>24} | {:>24}\n", "N", "Measured", "Curr. Estimate", + std::cout << MP_UNITS_STD_FMT::format("{:>2} | {:>8} | {:>23} | {:>23}\n", "N", "Measured", "Curr. Estimate", "Next Estimate"); } -void print(auto iteration, Quantity auto measured, const kalman::State auto& current, const kalman::State auto& next) +void print(auto iteration, QuantityPoint auto measured, const kalman::SystemState auto& current, + const kalman::SystemState auto& next) { - std::cout << MP_UNITS_STD_FMT::format("{:2} | {:8} | {:>24.1} | {:>24.1}\n", iteration, measured, current, next); + std::cout << MP_UNITS_STD_FMT::format("{:2} | {:8} | {:23:0[:N[.2f]]1[:N[.2f]]} | {:23:0[:N[.2f]]1[:N[.2f]]}\n", + iteration, measured, current, next); } int main() { using namespace mp_units::si::unit_symbols; - using state = kalman::state, quantity>; + using qp = quantity_point; + using state = kalman::system_state>; - const auto interval = isq::duration(5 * s); - const state initial = {30 * km, 50 * m / s}; - const quantity measurements[] = {30'160 * m, 30'365 * m, 30'890 * m, 31'050 * m, - 31'785 * m, 32'215 * m, 33'130 * m, 34'510 * m, - 36'010 * m, 37'265 * m}; + const quantity interval = isq::duration(5 * s); + const state initial{qp{30 * km}, quantity_point{50 * m / s}}; + const std::array measurements = {qp{30'221 * m}, qp{30'453 * m}, qp{30'906 * m}, qp{30'999 * m}, qp{31'368 * m}, + qp{31'978 * m}, qp{32'526 * m}, qp{33'379 * m}, qp{34'698 * m}, qp{36'275 * m}}; std::array gain = {0.2 * one, 0.1 * one}; print_header(initial); state next = state_extrapolation(initial, interval); - for (int index = 1; const auto& measured : measurements) { - const auto& previous = next; - const auto current = state_update(previous, measured, gain, interval); + for (int index = 1; const auto& measurement : measurements) { + const state& previous = next; + const state current = state_update(previous, measurement, gain, interval); next = state_extrapolation(current, interval); - print(index++, measured, current, next); + print(index++, measurement, current, next); } } diff --git a/example/kalman_filter/kalman_filter-example_4.cpp b/example/kalman_filter/kalman_filter-example_4.cpp index 00696c025..073bc4a1e 100644 --- a/example/kalman_filter/kalman_filter-example_4.cpp +++ b/example/kalman_filter/kalman_filter-example_4.cpp @@ -39,37 +39,40 @@ inline constexpr bool mp_units::is_vector = true; using namespace mp_units; -void print_header(const kalman::State auto& initial) +void print_header(const kalman::SystemState auto& initial) { std::cout << MP_UNITS_STD_FMT::format("Initial: {}\n", initial); std::cout << MP_UNITS_STD_FMT::format("{:>2} | {:>8} | {:>35} | {:>35}\n", "N", "Measured", "Curr. Estimate", "Next Estimate"); } -void print(auto iteration, Quantity auto measured, const kalman::State auto& current, const kalman::State auto& next) +void print(auto iteration, QuantityPoint auto measured, const kalman::SystemState auto& current, + const kalman::SystemState auto& next) { - std::cout << MP_UNITS_STD_FMT::format("{:2} | {:8} | {:>35.1} | {:>35.1}\n", iteration, measured, current, next); + std::cout << MP_UNITS_STD_FMT::format( + "{:2} | {:8} | {:35:0[:N[.2f]]1[:N[.2f]]2[:N[.2f]]} | {:35:0[:N[.2f]]1[:N[.2f]]2[:N[.2f]]}\n", iteration, measured, + current, next); } int main() { using namespace mp_units::si::unit_symbols; - using state = kalman::state, quantity, - quantity>; - const auto interval = isq::duration(5. * s); - const state initial = {30 * km, 50 * m / s, 0 * m / s2}; + using qp = quantity_point; + using state = + kalman::system_state, quantity_point>; - const quantity measurements[] = {30'160 * m, 30'365 * m, 30'890 * m, 31'050 * m, - 31'785 * m, 32'215 * m, 33'130 * m, 34'510 * m, - 36'010 * m, 37'265 * m}; + const quantity interval = isq::duration(5. * s); + const state initial{qp{30 * km}, quantity_point{50 * m / s}, quantity_point{0 * m / s2}}; + const std::array measurements = {qp{30'221 * m}, qp{30'453 * m}, qp{30'906 * m}, qp{30'999 * m}, qp{31'368 * m}, + qp{31'978 * m}, qp{32'526 * m}, qp{33'379 * m}, qp{34'698 * m}, qp{36'275 * m}}; std::array gain = {0.5 * one, 0.4 * one, 0.1 * one}; print_header(initial); state next = state_extrapolation(initial, interval); - for (int index = 1; const auto& measured : measurements) { - const auto& previous = next; - const auto current = state_update(previous, measured, gain, interval); + for (int index = 1; const auto& measurement : measurements) { + const state& previous = next; + const state current = state_update(previous, measurement, gain, interval); next = state_extrapolation(current, interval); - print(index++, measured, current, next); + print(index++, measurement, current, next); } } diff --git a/example/kalman_filter/kalman_filter-example_5.cpp b/example/kalman_filter/kalman_filter-example_5.cpp index b4be7e3c1..8a594ded3 100644 --- a/example/kalman_filter/kalman_filter-example_5.cpp +++ b/example/kalman_filter/kalman_filter-example_5.cpp @@ -36,45 +36,45 @@ import mp_units; using namespace mp_units; -template -void print_header(kalman::estimation initial) +template +void print_header(kalman::system_state_estimate initial) { - std::cout << MP_UNITS_STD_FMT::format("Initial: {}\n", initial); - std::cout << MP_UNITS_STD_FMT::format("{:>2} | {:>5} | {:>8} | {:>16} | {:>16}\n", "N", "Gain", "Measured", + std::cout << MP_UNITS_STD_FMT::format("Initial: {} {}\n", initial.state(), initial.variance()); + std::cout << MP_UNITS_STD_FMT::format("{:>2} | {:>8} | {:>5} | {:>16} | {:>16}\n", "N", "Measured", "Gain", "Curr. Estimate", "Next Estimate"); } -template K> -void print(auto iteration, K gain, Q measured, kalman::estimation current, kalman::estimation next) +template K> +void print(auto iteration, QP measured, K gain, kalman::system_state_estimate current, + kalman::system_state_estimate next) { - std::cout << MP_UNITS_STD_FMT::format("{:2} | {:5%.2Q} | {:8} | {:>16.2} | {:>16.2}\n", iteration, gain, measured, - current, next); + std::cout << MP_UNITS_STD_FMT::format( + "{:2} | {:8} | {:5:N[.2f]} | {:6:0[:N[.2f]]} {:8:N[.2f]} | {:6:0[:N[.2f]]} {:8:N[.2f]}\n", iteration, measured, + gain, current.state(), current.variance(), next.state(), next.variance()); } int main() { - using namespace kalman; using namespace mp_units::si::unit_symbols; + using qp = quantity_point; + using estimate = kalman::system_state_estimate; + using state = estimate::state_type; - const estimation initial = {state{isq::height(60. * m)}, pow<2>(isq::height(15. * m))}; - const quantity measurements[] = {48.54 * m, 47.11 * m, 55.01 * m, 55.15 * m, 49.89 * m, - 40.85 * m, 46.72 * m, 50.05 * m, 51.27 * m, 49.95 * m}; - const auto measurement_uncertainty = pow<2>(isq::height(5. * m)); + const estimate initial{state{qp{60. * m}}, 15. * m}; + const std::array measurements = {qp{49.03 * m}, qp{48.44 * m}, qp{55.21 * m}, qp{49.98 * m}, qp{50.6 * m}, + qp{52.61 * m}, qp{45.87 * m}, qp{42.64 * m}, qp{48.26 * m}, qp{55.84 * m}}; + const quantity measurement_error = isq::height(5. * m); + const quantity measurement_variance = pow<2>(measurement_error); - auto update = [=](const estimation& previous, const Q& measurement, - QuantityOf auto gain) { - return estimation{state_update(previous.state, measurement, gain), covariance_update(previous.uncertainty, gain)}; - }; - - auto predict = [](const estimation& current) { return current; }; + auto predict = [](const estimate& current) { return current; }; print_header(initial); - estimation next = predict(initial); - for (int index = 1; const auto& measured : measurements) { - const auto& previous = next; - const auto gain = kalman_gain(previous.uncertainty, measurement_uncertainty); - const estimation current = update(previous, measured, gain); + estimate next = predict(initial); + for (int index = 1; const auto& measurement : measurements) { + const estimate& previous = next; + const quantity gain = kalman::kalman_gain(previous.variance(), measurement_variance); + const estimate current = state_estimate_update(previous, measurement, gain); next = predict(current); - print(index++, gain, measured, current, next); + print(index++, measurement, gain, current, next); } } diff --git a/example/kalman_filter/kalman_filter-example_6.cpp b/example/kalman_filter/kalman_filter-example_6.cpp index a6ab2131f..59ce7a0fc 100644 --- a/example/kalman_filter/kalman_filter-example_6.cpp +++ b/example/kalman_filter/kalman_filter-example_6.cpp @@ -27,62 +27,59 @@ import mp_units; #else #include +#include #include -#include #include -#include +#include #endif -// Based on: https://www.kalmanfilter.net/kalman1d.html#ex6 +// Based on: https://www.kalmanfilter.net/kalman1d_pn.html#ex6 using namespace mp_units; template -void print_header(kalman::estimation initial) +void print_header(kalman::system_state_estimate initial) { - std::cout << MP_UNITS_STD_FMT::format("Initial: {}\n", initial); - std::cout << MP_UNITS_STD_FMT::format("{:>2} | {:>7} | {:>10} | {:>18} | {:>18}\n", "N", "Gain", "Measured", + std::cout << MP_UNITS_STD_FMT::format("Initial: {}\n", initial.state(), initial.variance()); + std::cout << MP_UNITS_STD_FMT::format("{:>2} | {:>10} | {:>7} | {:>22} | {:>22}\n", "N", "Measured", "Gain", "Curr. Estimate", "Next Estimate"); } template K> -void print(auto iteration, K gain, QP measured, kalman::estimation current, kalman::estimation next) +void print(auto iteration, QP measured, K gain, kalman::system_state_estimate current, + kalman::system_state_estimate next) { - std::cout << MP_UNITS_STD_FMT::format("{:2} | {:7%.4Q} | {:10%.3Q %q} | {:>18.3} | {:>18.3}\n", iteration, gain, - measured.quantity_ref_from(QP::point_origin), current, next); + std::cout << MP_UNITS_STD_FMT::format( + "{:2} | {:10} | {:7:N[.4f]} | {:10:0[:N[.3f]]} {:11:N[.4f]} | {:10:0[:N[.3f]]} {:11:N[.4f]}\n", iteration, measured, + gain, current.state(), current.variance(), next.state(), next.variance()); } int main() { - constexpr auto deg_C = isq::Celsius_temperature[si::degree_Celsius]; + using namespace mp_units::si::unit_symbols; + using qp = quantity_point; + using estimate = kalman::system_state_estimate; + using state = estimate::state_type; - using namespace kalman; + const quantity process_noise_variance = 0.0001 * pow<2>(deg_C); + const estimate initial{state{qp{60. * deg_C}}, 100. * deg_C}; + const std::array measurements = {qp{49.986 * deg_C}, qp{49.963 * deg_C}, qp{50.09 * deg_C}, qp{50.001 * deg_C}, + qp{50.018 * deg_C}, qp{50.05 * deg_C}, qp{49.938 * deg_C}, qp{49.858 * deg_C}, + qp{49.965 * deg_C}, qp{50.114 * deg_C}}; + const quantity measurement_error = 0.1 * deg_C; + const quantity measurement_variance = pow<2>(measurement_error); - const auto process_noise_variance = 0.0001 * (deg_C * deg_C); - const estimation initial = {state{si::ice_point + 10. * deg_C}, pow<2>(100. * deg_C)}; - const std::array measurements = {si::ice_point + 49.95 * deg_C, si::ice_point + 49.967 * deg_C, - si::ice_point + 50.1 * deg_C, si::ice_point + 50.106 * deg_C, - si::ice_point + 49.992 * deg_C, si::ice_point + 49.819 * deg_C, - si::ice_point + 49.933 * deg_C, si::ice_point + 50.007 * deg_C, - si::ice_point + 50.023 * deg_C, si::ice_point + 49.99 * deg_C}; - const auto measurement_uncertainty = pow<2>(0.1 * deg_C); - - auto update = [=](const estimation& previous, const QP& meassurement, - QuantityOf auto gain) { - return estimation{state_update(previous.state, meassurement, gain), covariance_update(previous.uncertainty, gain)}; - }; - - auto predict = [=](const estimation& current) { - return estimation{current.state, covariance_extrapolation(current.uncertainty, process_noise_variance)}; + auto predict = [=](const estimate& current) { + return estimate{current.state(), kalman::covariance_extrapolation(current.variance(), process_noise_variance)}; }; print_header(initial); - estimation next = predict(initial); - for (int index = 1; const auto& m : measurements) { - const auto& previous = next; - const auto gain = kalman_gain(previous.uncertainty, measurement_uncertainty); - const estimation current = update(previous, m, gain); + estimate next = predict(initial); + for (int index = 1; const auto& measurement : measurements) { + const estimate& previous = next; + const quantity gain = kalman::kalman_gain(previous.variance(), measurement_variance); + const estimate current = state_estimate_update(previous, measurement, gain); next = predict(current); - print(index++, gain, m, current, next); + print(index++, measurement, gain, current, next); } } diff --git a/example/kalman_filter/kalman_filter-example_7.cpp b/example/kalman_filter/kalman_filter-example_7.cpp index 49c832fe9..ecf52f964 100644 --- a/example/kalman_filter/kalman_filter-example_7.cpp +++ b/example/kalman_filter/kalman_filter-example_7.cpp @@ -27,62 +27,59 @@ import mp_units; #else #include +#include #include -#include #include -#include +#include #endif -// Based on: https://www.kalmanfilter.net/kalman1d.html#ex7 +// Based on: https://www.kalmanfilter.net/kalman1d_pn.html#ex7 using namespace mp_units; template -void print_header(kalman::estimation initial) +void print_header(kalman::system_state_estimate initial) { - std::cout << MP_UNITS_STD_FMT::format("Initial: {}\n", initial); - std::cout << MP_UNITS_STD_FMT::format("{:>2} | {:>7} | {:>10} | {:>18} | {:>18}\n", "N", "Gain", "Measured", + std::cout << MP_UNITS_STD_FMT::format("Initial: {}\n", initial.state(), initial.variance()); + std::cout << MP_UNITS_STD_FMT::format("{:>2} | {:>10} | {:>7} | {:>22} | {:>22}\n", "N", "Measured", "Gain", "Curr. Estimate", "Next Estimate"); } template K> -void print(auto iteration, K gain, QP measured, kalman::estimation current, kalman::estimation next) +void print(auto iteration, QP measured, K gain, kalman::system_state_estimate current, + kalman::system_state_estimate next) { - std::cout << MP_UNITS_STD_FMT::format("{:2} | {:7%.4Q} | {:10%.3Q %q} | {:>18.3} | {:>18.3}\n", iteration, gain, - measured.quantity_ref_from(QP::point_origin), current, next); + std::cout << MP_UNITS_STD_FMT::format( + "{:2} | {:10} | {:7:N[.4f]} | {:10:0[:N[.3f]]} {:11:N[.4f]} | {:10:0[:N[.3f]]} {:11:N[.4f]}\n", iteration, measured, + gain, current.state(), current.variance(), next.state(), next.variance()); } int main() { - constexpr auto deg_C = isq::Celsius_temperature[si::degree_Celsius]; + using namespace mp_units::si::unit_symbols; + using qp = quantity_point; + using estimate = kalman::system_state_estimate; + using state = estimate::state_type; - using namespace kalman; + const quantity process_noise_variance = 0.0001 * pow<2>(deg_C); + const estimate initial{state{qp{10. * deg_C}}, 100. * deg_C}; + const std::array measurements = {qp{50.486 * deg_C}, qp{50.963 * deg_C}, qp{51.597 * deg_C}, qp{52.001 * deg_C}, + qp{52.518 * deg_C}, qp{53.05 * deg_C}, qp{53.438 * deg_C}, qp{53.858 * deg_C}, + qp{54.465 * deg_C}, qp{55.114 * deg_C}}; + const quantity measurement_error = 0.1 * deg_C; + const quantity measurement_variance = pow<2>(measurement_error); - const auto process_noise_variance = 0.0001 * (deg_C * deg_C); - const estimation initial = {state{si::ice_point + 10. * deg_C}, pow<2>(100. * deg_C)}; - const std::array measurements = {si::ice_point + 50.45 * deg_C, si::ice_point + 50.967 * deg_C, - si::ice_point + 51.6 * deg_C, si::ice_point + 52.106 * deg_C, - si::ice_point + 52.492 * deg_C, si::ice_point + 52.819 * deg_C, - si::ice_point + 53.433 * deg_C, si::ice_point + 54.007 * deg_C, - si::ice_point + 54.523 * deg_C, si::ice_point + 54.99 * deg_C}; - const auto measurement_uncertainty = pow<2>(0.1 * deg_C); - - auto update = [=](const estimation& previous, const QP& meassurement, - QuantityOf auto gain) { - return estimation{state_update(previous.state, meassurement, gain), covariance_update(previous.uncertainty, gain)}; - }; - - auto predict = [=](const estimation& current) { - return estimation{current.state, covariance_extrapolation(current.uncertainty, process_noise_variance)}; + auto predict = [=](const estimate& current) { + return estimate{current.state(), kalman::covariance_extrapolation(current.variance(), process_noise_variance)}; }; print_header(initial); - estimation next = predict(initial); - for (int index = 1; const auto& m : measurements) { - const auto& previous = next; - const auto gain = kalman_gain(previous.uncertainty, measurement_uncertainty); - const estimation current = update(previous, m, gain); + estimate next = predict(initial); + for (int index = 1; const auto& measurement : measurements) { + const estimate& previous = next; + const quantity gain = kalman::kalman_gain(previous.variance(), measurement_variance); + const estimate current = state_estimate_update(previous, measurement, gain); next = predict(current); - print(index++, gain, m, current, next); + print(index++, measurement, gain, current, next); } } diff --git a/example/kalman_filter/kalman_filter-example_8.cpp b/example/kalman_filter/kalman_filter-example_8.cpp index 474ecff70..e9e06da0d 100644 --- a/example/kalman_filter/kalman_filter-example_8.cpp +++ b/example/kalman_filter/kalman_filter-example_8.cpp @@ -27,62 +27,59 @@ import mp_units; #else #include +#include #include -#include #include -#include +#include #endif -// Based on: https://www.kalmanfilter.net/kalman1d.html#ex8 +// Based on: https://www.kalmanfilter.net/kalman1d_pn.html#ex8 using namespace mp_units; template -void print_header(kalman::estimation initial) +void print_header(kalman::system_state_estimate initial) { - std::cout << MP_UNITS_STD_FMT::format("Initial: {}\n", initial); - std::cout << MP_UNITS_STD_FMT::format("{:>2} | {:>7} | {:>10} | {:>16} | {:>16}\n", "N", "Gain", "Measured", + std::cout << MP_UNITS_STD_FMT::format("Initial: {}\n", initial.state(), initial.variance()); + std::cout << MP_UNITS_STD_FMT::format("{:>2} | {:>10} | {:>7} | {:>22} | {:>22}\n", "N", "Measured", "Gain", "Curr. Estimate", "Next Estimate"); } template K> -void print(auto iteration, K gain, QP measured, kalman::estimation current, kalman::estimation next) +void print(auto iteration, QP measured, K gain, kalman::system_state_estimate current, + kalman::system_state_estimate next) { - std::cout << MP_UNITS_STD_FMT::format("{:2} | {:7%.3Q} | {:10%.3Q %q} | {:>16.2} | {:>16.2}\n", iteration, gain, - measured.quantity_ref_from(QP::point_origin), current, next); + std::cout << MP_UNITS_STD_FMT::format( + "{:2} | {:10} | {:7:N[.4f]} | {:10:0[:N[.3f]]} {:11:N[.4f]} | {:10:0[:N[.3f]]} {:11:N[.4f]}\n", iteration, measured, + gain, current.state(), current.variance(), next.state(), next.variance()); } int main() { - constexpr auto deg_C = isq::Celsius_temperature[si::degree_Celsius]; + using namespace mp_units::si::unit_symbols; + using qp = quantity_point; + using estimate = kalman::system_state_estimate; + using state = estimate::state_type; - using namespace kalman; + const quantity process_noise_variance = 0.15 * pow<2>(deg_C); + const estimate initial{state{qp{10. * deg_C}}, 100. * deg_C}; + const std::array measurements = {qp{50.486 * deg_C}, qp{50.963 * deg_C}, qp{51.597 * deg_C}, qp{52.001 * deg_C}, + qp{52.518 * deg_C}, qp{53.05 * deg_C}, qp{53.438 * deg_C}, qp{53.858 * deg_C}, + qp{54.465 * deg_C}, qp{55.114 * deg_C}}; + const quantity measurement_error = 0.1 * deg_C; + const quantity measurement_variance = pow<2>(measurement_error); - const auto process_noise_variance = 0.15 * (deg_C * deg_C); - const estimation initial = {state{si::ice_point + 10. * deg_C}, pow<2>(100. * deg_C)}; - const std::array measurements = {si::ice_point + 50.45 * deg_C, si::ice_point + 50.967 * deg_C, - si::ice_point + 51.6 * deg_C, si::ice_point + 52.106 * deg_C, - si::ice_point + 52.492 * deg_C, si::ice_point + 52.819 * deg_C, - si::ice_point + 53.433 * deg_C, si::ice_point + 54.007 * deg_C, - si::ice_point + 54.523 * deg_C, si::ice_point + 54.99 * deg_C}; - const auto measurement_uncertainty = pow<2>(0.1 * deg_C); - - auto update = [=](const estimation& previous, const QP& meassurement, - QuantityOf auto gain) { - return estimation{state_update(previous.state, meassurement, gain), covariance_update(previous.uncertainty, gain)}; - }; - - auto predict = [=](const estimation& current) { - return estimation{current.state, covariance_extrapolation(current.uncertainty, process_noise_variance)}; + auto predict = [=](const estimate& current) { + return estimate{current.state(), kalman::covariance_extrapolation(current.variance(), process_noise_variance)}; }; print_header(initial); - estimation next = predict(initial); - for (int index = 1; const auto& m : measurements) { - const auto& previous = next; - const auto gain = kalman_gain(previous.uncertainty, measurement_uncertainty); - const estimation current = update(previous, m, gain); + estimate next = predict(initial); + for (int index = 1; const auto& measurement : measurements) { + const estimate& previous = next; + const quantity gain = kalman::kalman_gain(previous.variance(), measurement_variance); + const estimate current = state_estimate_update(previous, measurement, gain); next = predict(current); - print(index++, gain, m, current, next); + print(index++, measurement, gain, current, next); } } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 363634aaf..cee2b7f17 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -42,6 +42,9 @@ message(STATUS "${projectPrefix}BUILD_AS_SYSTEM_HEADERS: ${${projectPrefix}BUILD option(${projectPrefix}BUILD_CXX_MODULES "Add C++ modules to the list of default targets" OFF) message(STATUS "${projectPrefix}BUILD_CXX_MODULES: ${${projectPrefix}BUILD_CXX_MODULES}") +option(${projectPrefix}API_FREESTANDING "Builds only freestanding part of the library" OFF) +message(STATUS "${projectPrefix}API_FREESTANDING: ${${projectPrefix}API_FREESTANDING}") + if(${projectPrefix}BUILD_AS_SYSTEM_HEADERS) set(${projectPrefix}_AS_SYSTEM SYSTEM) endif() @@ -68,13 +71,21 @@ cache_var_values(API_STRING_VIEW_RET AUTO TRUE FALSE) set(${projectPrefix}API_NO_CRTP AUTO CACHE STRING "Enable class definitions without CRTP idiom") cache_var_values(API_NO_CRTP AUTO TRUE FALSE) +set(${projectPrefix}API_CONTRACTS GSL-LITE CACHE STRING "Enable contract checking") +cache_var_values(API_CONTRACTS NONE GSL-LITE MS-GSL) + +if(${projectPrefix}API_FREESTANDING AND NOT ${projectPrefix}API_CONTRACTS STREQUAL "NONE") + message(FATAL_ERROR "'${projectPrefix}API_CONTRACTS' should be set to 'NONE' for a freestanding build") +endif() + # C++ features check_cxx_feature_supported(__cpp_lib_format ${projectPrefix}LIB_FORMAT_SUPPORTED) check_cxx_feature_supported("__cpp_constexpr >= 202211L" ${projectPrefix}STATIC_CONSTEXPR_VARS_IN_CONSTEXPR_FUNCTIONS) check_cxx_feature_supported(__cpp_explicit_this_parameter ${projectPrefix}EXPLICIT_THIS_PARAMETER_SUPPORTED) # validate settings -if(${projectPrefix}API_STD_FORMAT STREQUAL "TRUE" +if(NOT ${projectPrefix}API_FREESTANDING + AND ${projectPrefix}API_STD_FORMAT STREQUAL "TRUE" AND NOT (${projectPrefix}LIB_FORMAT_SUPPORTED # libc++ has a basic supports for std::format but does not set __cpp_lib_format diff --git a/src/cmake/AddMPUnitsModule.cmake b/src/cmake/AddMPUnitsModule.cmake index be007c90c..87457db21 100644 --- a/src/cmake/AddMPUnitsModule.cmake +++ b/src/cmake/AddMPUnitsModule.cmake @@ -54,7 +54,7 @@ function(add_mp_units_module name target_name) # validate and process arguments validate_unparsed(${name} ARG) - validate_arguments_exists(${name} ARG DEPENDENCIES MODULE_INTERFACE_UNIT) + validate_arguments_exists(${name} ARG MODULE_INTERFACE_UNIT) if(${projectPrefix}TARGET_SCOPE STREQUAL INTERFACE) set(SCOPE "INTERFACE") diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index ed4525f38..9d2839f30 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -31,17 +31,10 @@ function(set_feature_flag name) endif() endfunction() -# find dependencies -if(NOT TARGET gsl::gsl-lite) - find_package(gsl-lite REQUIRED) -endif() - # core library definition add_mp_units_module( core mp-units-core - DEPENDENCIES gsl::gsl-lite HEADERS include/mp-units/bits/core_gmf.h - include/mp-units/bits/fmt.h include/mp-units/bits/get_associated_quantity.h include/mp-units/bits/get_common_base.h include/mp-units/bits/hacks.h @@ -79,20 +72,35 @@ add_mp_units_module( include/mp-units/framework/value_cast.h include/mp-units/compat_macros.h include/mp-units/concepts.h - include/mp-units/format.h include/mp-units/framework.h - include/mp-units/math.h - include/mp-units/ostream.h - include/mp-units/random.h MODULE_INTERFACE_UNIT mp-units-core.cpp ) +if(NOT ${projectPrefix}API_FREESTANDING) + target_sources( + mp-units-core + PUBLIC FILE_SET + HEADERS + BASE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/include + FILES + include/mp-units/bits/fmt.h + include/mp-units/bits/requires_hosted.h + include/mp-units/math.h + include/mp-units/ostream.h + include/mp-units/format.h + include/mp-units/random.h + ) +endif() + set_feature_flag(API_STD_FORMAT) set_feature_flag(API_STRING_VIEW_RET) set_feature_flag(API_NO_CRTP) -if(${projectPrefix}API_STD_FORMAT STREQUAL "FALSE" OR (${projectPrefix}API_STD_FORMAT STREQUAL "AUTO" - AND NOT ${projectPrefix}LIB_FORMAT_SUPPORTED) +# Text formatting +if(NOT ${projectPrefix}API_FREESTANDING + AND (${projectPrefix}API_STD_FORMAT STREQUAL "FALSE" OR (${projectPrefix}API_STD_FORMAT STREQUAL "AUTO" + AND NOT ${projectPrefix}LIB_FORMAT_SUPPORTED)) ) if(NOT TARGET fmt::fmt) find_package(fmt REQUIRED) @@ -100,6 +108,24 @@ if(${projectPrefix}API_STD_FORMAT STREQUAL "FALSE" OR (${projectPrefix}API_STD_F target_link_libraries(mp-units-core ${${projectPrefix}TARGET_SCOPE} fmt::fmt) endif() +# Contracts checking +if(${projectPrefix}API_CONTRACTS STREQUAL "NONE") + target_compile_definitions(mp-units-core ${${projectPrefix}TARGET_SCOPE} ${projectPrefix}API_CONTRACTS=0) +elseif(${projectPrefix}API_CONTRACTS STREQUAL "GSL-LITE") + if(NOT TARGET gsl::gsl-lite) + find_package(gsl-lite REQUIRED) + endif() + target_link_libraries(mp-units-core ${${projectPrefix}TARGET_SCOPE} gsl::gsl-lite) + target_compile_definitions(mp-units-core ${${projectPrefix}TARGET_SCOPE} ${projectPrefix}API_CONTRACTS=2) +elseif(${projectPrefix}API_CONTRACTS STREQUAL "MS-GSL") + if(NOT TARGET Microsoft.GSL::GSL) + find_package(Microsoft.GSL REQUIRED) + endif() + target_link_libraries(mp-units-core ${${projectPrefix}TARGET_SCOPE} Microsoft.GSL::GSL) + target_compile_definitions(mp-units-core ${${projectPrefix}TARGET_SCOPE} ${projectPrefix}API_CONTRACTS=3) +endif() + +# C++20 modules if(${projectPrefix}BUILD_CXX_MODULES) if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 18) @@ -112,6 +138,7 @@ if(${projectPrefix}BUILD_CXX_MODULES) endif() endif() +# UTF-8 source and execution character set if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") target_compile_options( mp-units-core ${${projectPrefix}TARGET_SCOPE} @@ -119,4 +146,8 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") ) endif() -# target_compile_options(mp-units-core ${${projectPrefix}TARGET_SCOPE} "-ftime-trace") +# Freestanding +target_compile_definitions( + mp-units-core ${${projectPrefix}TARGET_SCOPE} + ${projectPrefix}HOSTED=$> +) diff --git a/src/core/include/mp-units/bits/core_gmf.h b/src/core/include/mp-units/bits/core_gmf.h index dfb93961f..21d97e8f9 100644 --- a/src/core/include/mp-units/bits/core_gmf.h +++ b/src/core/include/mp-units/bits/core_gmf.h @@ -22,15 +22,14 @@ #pragma once -#include #include #include #include -#include #include #include #include #include +#include #include #include #include @@ -38,14 +37,20 @@ #include #include #include -#include -#include -#include +#include #include #include #include #include +#if MP_UNITS_HOSTED +#include +#include +#include +#include +#include +#include + #if MP_UNITS_USE_FMTLIB MP_UNITS_DIAGNOSTIC_PUSH MP_UNITS_DIAGNOSTIC_IGNORE_UNREACHABLE @@ -56,6 +61,8 @@ MP_UNITS_DIAGNOSTIC_POP #include #endif +#endif + #if __cpp_lib_text_encoding #include #endif diff --git a/src/core/include/mp-units/bits/fmt.h b/src/core/include/mp-units/bits/fmt.h index 16d98363a..8e84ac0bf 100644 --- a/src/core/include/mp-units/bits/fmt.h +++ b/src/core/include/mp-units/bits/fmt.h @@ -30,11 +30,12 @@ // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic, cppcoreguidelines-pro-type-union-access) #pragma once +#include #include #include #ifndef MP_UNITS_IN_MODULE_INTERFACE -#include +#include #include #include #include @@ -45,6 +46,9 @@ namespace mp_units::detail { +// TODO the below should be exposed by the C++ Standard Library (used in our examples) +MP_UNITS_EXPORT_BEGIN + enum class fmt_align : std::int8_t { none, left, right, center, numeric }; enum class fmt_arg_id_kind : std::int8_t { none, @@ -109,6 +113,8 @@ struct fill_t { [[nodiscard]] constexpr const Char& operator[](size_t index) const { return data_[index]; } }; +MP_UNITS_EXPORT_END + template inline constexpr bool is_integer = std::is_integral_v && !std::is_same_v && !std::is_same_v && !std::is_same_v; @@ -131,24 +137,10 @@ template template [[nodiscard]] constexpr std::make_unsigned_t to_unsigned(Int value) { - gsl_Expects(std::is_unsigned_v || value >= 0); + MP_UNITS_EXPECTS(std::is_unsigned_v || value >= 0); return static_cast>(value); } -struct width_checker { - template - [[nodiscard]] constexpr unsigned long long operator()(T value) const - { - if constexpr (is_integer) { - if constexpr (std::numeric_limits::is_signed) - if (value < 0) MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("negative width")); - return static_cast(value); - } - MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("width is not integer")); - return 0; - } -}; - template [[nodiscard]] constexpr int get_dynamic_spec(FormatArg arg) { @@ -167,6 +159,9 @@ template return arg; } +// TODO the below should be exposed by the C++ Standard Library (used in our examples) +MP_UNITS_EXPORT_BEGIN + template constexpr void handle_dynamic_spec(int& value, fmt_arg_ref ref, Context& ctx) { @@ -184,12 +179,28 @@ constexpr void handle_dynamic_spec(int& value, fmt_arg_ref + [[nodiscard]] constexpr unsigned long long operator()(T value) const + { + if constexpr (is_integer) { + if constexpr (std::numeric_limits::is_signed) + if (value < 0) MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("negative width")); + return static_cast(value); + } + MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("width is not integer")); + return 0; + } +}; + +MP_UNITS_EXPORT_END + // Parses the range [begin, end) as an unsigned integer. This function assumes // that the range is non-empty and the first character is a digit. template [[nodiscard]] constexpr int parse_nonnegative_int(const Char*& begin, const Char* end, int error_value) { - gsl_Expects(begin != end && '0' <= *begin && *begin <= '9'); + MP_UNITS_EXPECTS(begin != end && '0' <= *begin && *begin <= '9'); unsigned value = 0, prev = 0; auto p = begin; do { @@ -250,7 +261,7 @@ template template [[nodiscard]] constexpr const Char* parse_arg_id(const Char* begin, const Char* end, Handler& handler) { - gsl_Expects(begin != end); + MP_UNITS_EXPECTS(begin != end); Char c = *begin; if (c != '}' && c != ':') return ::mp_units::detail::do_parse_arg_id(begin, end, handler); handler.on_auto(); @@ -292,7 +303,7 @@ template fmt_arg_ref& ref, MP_UNITS_STD_FMT::basic_format_parse_context& ctx) { - gsl_Expects(begin != end); + MP_UNITS_EXPECTS(begin != end); if ('0' <= *begin && *begin <= '9') { const int val = ::mp_units::detail::parse_nonnegative_int(begin, end, -1); if (val != -1) @@ -329,7 +340,7 @@ template [[nodiscard]] constexpr const Char* parse_align(const Char* begin, const Char* end, Specs& specs, fmt_align default_align = fmt_align::none) { - gsl_Expects(begin != end); + MP_UNITS_EXPECTS(begin != end); auto align = fmt_align::none; auto p = begin + code_point_length(begin); if (end - p <= 0) p = begin; diff --git a/src/core/include/mp-units/bits/get_associated_quantity.h b/src/core/include/mp-units/bits/get_associated_quantity.h index 2a4743033..881c61e86 100644 --- a/src/core/include/mp-units/bits/get_associated_quantity.h +++ b/src/core/include/mp-units/bits/get_associated_quantity.h @@ -59,7 +59,7 @@ template [[nodiscard]] consteval auto get_associated_quantity_impl(U u); template -using to_quantity_spec = std::remove_const_t; +using to_quantity_spec = decltype(get_associated_quantity_impl(U{})); template [[nodiscard]] consteval auto get_associated_quantity_impl(U u) diff --git a/src/core/include/mp-units/bits/hacks.h b/src/core/include/mp-units/bits/hacks.h index f1d9b8374..5030debb7 100644 --- a/src/core/include/mp-units/bits/hacks.h +++ b/src/core/include/mp-units/bits/hacks.h @@ -73,6 +73,10 @@ #define MP_UNITS_DIAGNOSTIC_IGNORE_DEPRECATED #endif +#if !defined MP_UNITS_HOSTED && defined __STDC_HOSTED__ +#define MP_UNITS_HOSTED __STDC_HOSTED__ +#endif + #if MP_UNITS_COMP_MSVC #define MP_UNITS_TYPENAME typename @@ -83,10 +87,26 @@ #endif -// TODO revise the below when clang-18 is released -#if MP_UNITS_COMP_CLANG >= 18 && __cplusplus >= 202300L && !defined __cpp_explicit_this_parameter +#if MP_UNITS_COMP_GCC + +#define MP_UNITS_REMOVE_CONST(expr) std::remove_const_t + +#else + +#define MP_UNITS_REMOVE_CONST(expr) expr + +#endif + +#if !defined __cpp_lib_ranges_to_container + +namespace std { + +struct from_range_t { + explicit from_range_t() = default; +}; +inline constexpr from_range_t from_range{}; -#define __cpp_explicit_this_parameter 202110L +} // namespace std #endif diff --git a/src/core/include/mp-units/bits/ratio.h b/src/core/include/mp-units/bits/ratio.h index 96c677473..bcf778dd7 100644 --- a/src/core/include/mp-units/bits/ratio.h +++ b/src/core/include/mp-units/bits/ratio.h @@ -24,9 +24,9 @@ #include #include +#include #ifndef MP_UNITS_IN_MODULE_INTERFACE -#include #include // IWYU pragma: export #include #include @@ -44,16 +44,16 @@ template { constexpr std::intmax_t c = std::uintmax_t{1} << (sizeof(std::intmax_t) * 4); - const std::intmax_t a0 = abs(lhs) % c; - const std::intmax_t a1 = abs(lhs) / c; - const std::intmax_t b0 = abs(rhs) % c; - const std::intmax_t b1 = abs(rhs) / c; + [[maybe_unused]] const std::intmax_t a0 = abs(lhs) % c; + [[maybe_unused]] const std::intmax_t a1 = abs(lhs) / c; + [[maybe_unused]] const std::intmax_t b0 = abs(rhs) % c; + [[maybe_unused]] const std::intmax_t b1 = abs(rhs) / c; // overflow in multiplication - gsl_Assert(a1 == 0 || b1 == 0); - gsl_Assert(a0 * b1 + b0 * a1 < (c >> 1)); // NOLINT(hicpp-signed-bitwise) - gsl_Assert(b0 * a0 <= INTMAX_MAX); - gsl_Assert((a0 * b1 + b0 * a1) * c <= INTMAX_MAX - b0 * a0); + MP_UNITS_ASSERT(a1 == 0 || b1 == 0); + MP_UNITS_ASSERT(a0 * b1 + b0 * a1 < (c >> 1)); // NOLINT(hicpp-signed-bitwise) + MP_UNITS_ASSERT(b0 * a0 <= INTMAX_MAX); + MP_UNITS_ASSERT((a0 * b1 + b0 * a1) * c <= INTMAX_MAX - b0 * a0); return lhs * rhs; } @@ -72,7 +72,7 @@ MP_UNITS_EXPORT struct ratio { // NOLINTNEXTLINE(bugprone-easily-swappable-parameters, google-explicit-constructor, hicpp-explicit-conversions) MP_UNITS_CONSTEVAL explicit(false) ratio(std::intmax_t n, std::intmax_t d = 1) : num{n}, den{d} { - gsl_Expects(den != 0); + MP_UNITS_EXPECTS(den != 0); if (num == 0) den = 1; else { @@ -112,9 +112,9 @@ MP_UNITS_EXPORT struct ratio { if (r1.num == r2.num && r1.den == r2.den) return ratio{r1.num, r1.den}; // gcd(a/b,c/d) = gcd(a⋅d, c⋅b) / b⋅d - gsl_Assert(std::numeric_limits::max() / r1.num > r2.den); - gsl_Assert(std::numeric_limits::max() / r2.num > r1.den); - gsl_Assert(std::numeric_limits::max() / r1.den > r2.den); + MP_UNITS_ASSERT(std::numeric_limits::max() / r1.num > r2.den); + MP_UNITS_ASSERT(std::numeric_limits::max() / r2.num > r1.den); + MP_UNITS_ASSERT(std::numeric_limits::max() / r1.den > r2.den); const std::intmax_t num = std::gcd(r1.num * r2.den, r2.num * r1.den); const std::intmax_t den = r1.den * r2.den; diff --git a/src/core/include/mp-units/bits/requires_hosted.h b/src/core/include/mp-units/bits/requires_hosted.h new file mode 100644 index 000000000..dbacc01a0 --- /dev/null +++ b/src/core/include/mp-units/bits/requires_hosted.h @@ -0,0 +1,29 @@ +// The MIT License (MIT) +// +// Copyright (c) 2018 Mateusz Pusz +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +#include + +#if !MP_UNITS_HOSTED +#error "This header is not available in freestanding mode." +#endif diff --git a/src/core/include/mp-units/bits/text_tools.h b/src/core/include/mp-units/bits/text_tools.h index dccd3e918..c79ff946f 100644 --- a/src/core/include/mp-units/bits/text_tools.h +++ b/src/core/include/mp-units/bits/text_tools.h @@ -23,6 +23,7 @@ #pragma once #include +#include #include #include #include @@ -100,12 +101,12 @@ constexpr Out copy(const symbol_text& txt, text_encoding encoding, Out out for (const char8_t ch : txt.unicode()) *out++ = static_cast(ch); return out; } else - throw std::invalid_argument("Unicode text can't be copied to CharT output"); + MP_UNITS_THROW(std::invalid_argument("Unicode text can't be copied to CharT output")); } else { if constexpr (is_same_v) return ::mp_units::detail::copy(txt.ascii().begin(), txt.ascii().end(), out); else - throw std::invalid_argument("ASCII text can't be copied to CharT output"); + MP_UNITS_THROW(std::invalid_argument("ASCII text can't be copied to CharT output")); } } diff --git a/src/core/include/mp-units/compat_macros.h b/src/core/include/mp-units/compat_macros.h index 227551c7b..026f04e9a 100644 --- a/src/core/include/mp-units/compat_macros.h +++ b/src/core/include/mp-units/compat_macros.h @@ -40,6 +40,15 @@ #endif +#if MP_UNITS_HOSTED +#define MP_UNITS_THROW(expr) throw expr +#else +#include +#define MP_UNITS_THROW(expr) std::abort() +#endif + +#if MP_UNITS_HOSTED + #if defined MP_UNITS_API_STD_FORMAT && !MP_UNITS_API_STD_FORMAT #define MP_UNITS_USE_FMTLIB 1 @@ -59,21 +68,7 @@ #define MP_UNITS_FMT_TO_ARG_ID(arg) (arg) #define MP_UNITS_FMT_FROM_ARG_ID(arg) (arg) -// This re-uses code from fmt; -#if FMT_EXCEPTIONS -#if FMT_MSC_VERSION || defined(__NVCC__) -#define MP_UNITS_THROW(x) ::fmt::detail::do_throw(x) -#else -#define MP_UNITS_THROW(x) throw x -#endif -#else -#define MP_UNITS_THROW(x) \ - do { \ - FMT_ASSERT(false, (x).what()); \ - } while (false) -#endif - -#else +#else // MP_UNITS_USE_FMTLIB #if !defined __cpp_lib_format && !defined MP_UNITS_COMP_CLANG #error "std::formatting facility not supported" @@ -83,10 +78,8 @@ #define MP_UNITS_FMT_LOCALE(loc) loc #define MP_UNITS_FMT_TO_ARG_ID(arg) static_cast(arg) #define MP_UNITS_FMT_FROM_ARG_ID(arg) static_cast(arg) -#define MP_UNITS_THROW(arg) throw arg - -#endif +#endif // MP_UNITS_USE_FMTLIB #ifndef MP_UNITS_IN_MODULE_INTERFACE @@ -102,5 +95,39 @@ MP_UNITS_DIAGNOSTIC_POP #endif // IWYU pragma: end_exports +#endif + +#endif // MP_UNITS_HOSTED + +#if MP_UNITS_API_CONTRACTS == 2 || __has_include() + +#include + +#define MP_UNITS_EXPECTS(expr) gsl_Expects(expr) +#define MP_UNITS_EXPECTS_DEBUG(expr) gsl_ExpectsDebug(expr) +#define MP_UNITS_ASSERT(expr) gsl_Assert(expr) +#define MP_UNITS_ASSERT_DEBUG(expr) gsl_AssertDebug(expr) + +#elif MP_UNITS_API_CONTRACTS == 3 || __has_include() + +#include +#include + +#define MP_UNITS_EXPECTS(expr) Expects(expr) +#if defined NDEBUG +#define MP_UNITS_EXPECTS_DEBUG(expr) static_cast(0) +#else +#define MP_UNITS_EXPECTS_DEBUG(expr) Expects(expr) +#endif +#define MP_UNITS_ASSERT(expr) Expects(expr) +#define MP_UNITS_ASSERT_DEBUG(expr) assert(expr) + +#else + +#define MP_UNITS_EXPECTS(expr) static_cast(0) +#define MP_UNITS_EXPECTS_DEBUG(expr) static_cast(0) +#define MP_UNITS_ASSERT(expr) static_cast(0) +#define MP_UNITS_ASSERT_DEBUG(expr) static_cast(0) + #endif // NOLINTEND(cppcoreguidelines-macro-usage) diff --git a/src/core/include/mp-units/ext/algorithm.h b/src/core/include/mp-units/ext/algorithm.h index 1d5a7d67e..6c50dedca 100644 --- a/src/core/include/mp-units/ext/algorithm.h +++ b/src/core/include/mp-units/ext/algorithm.h @@ -20,8 +20,9 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -// Copy-pasted C++ standard libraries to be replaced with `import std;` when available -// `#include ` is too heavy to do in every translation unit +// Copy-pasted C++ standard libraries +// TODO To be replaced with `import std;` when available +// `#include ` is too heavy to do in every translation unit #pragma once @@ -72,45 +73,6 @@ constexpr bool all_of(InputIt first, InputIt last, UnaryPred p) return find_if_not(first, last, p) == last; } -template -constexpr bool equal(InputIt1 first1, InputIt1 last1, InputIt2 first2) -{ - for (; first1 != last1; ++first1, ++first2) { - if (!(*first1 == *first2)) { - return false; - } - } - return true; -} - -template -constexpr auto lexicographical_compare_three_way(I1 first1, I1 last1, I2 first2, I2 last2, Cmp comp) - -> decltype(comp(*first1, *first2)) -{ - using ret_t = decltype(comp(*first1, *first2)); - static_assert(std::disjunction_v, std::is_same, - std::is_same>, - "The return type must be a comparison category type."); - - bool exhaust1 = (first1 == last1); - bool exhaust2 = (first2 == last2); - MP_UNITS_DIAGNOSTIC_PUSH - MP_UNITS_DIAGNOSTIC_IGNORE_ZERO_AS_NULLPOINTER_CONSTANT - for (; !exhaust1 && !exhaust2; exhaust1 = (++first1 == last1), exhaust2 = (++first2 == last2)) - if (auto c = comp(*first1, *first2); c != 0) return c; - MP_UNITS_DIAGNOSTIC_POP - - if (!exhaust1) return std::strong_ordering::greater; - if (!exhaust2) return std::strong_ordering::less; - return std::strong_ordering::equal; -} - -template -constexpr auto lexicographical_compare_three_way(I1 first1, I1 last1, I2 first2, I2 last2) -{ - return ::mp_units::detail::lexicographical_compare_three_way(first1, last1, first2, last2, std::compare_three_way()); -} - template constexpr ForwardIt max_element(ForwardIt first, ForwardIt last) { @@ -170,4 +132,18 @@ constexpr OutputIt copy(InputIt first, InputIt last, OutputIt d_first) return d_first; } +template +constexpr void iter_swap(ForwardIt1 a, ForwardIt2 b) +{ + using std::swap; + swap(*a, *b); +} + +template +constexpr ForwardIt2 swap_ranges(ForwardIt1 first1, ForwardIt1 last1, ForwardIt2 first2) +{ + for (; first1 != last1; ++first1, ++first2) iter_swap(first1, first2); + return first2; +} + } // namespace mp_units::detail diff --git a/src/core/include/mp-units/ext/fixed_string.h b/src/core/include/mp-units/ext/fixed_string.h index 8dac28220..1cfe2369c 100644 --- a/src/core/include/mp-units/ext/fixed_string.h +++ b/src/core/include/mp-units/ext/fixed_string.h @@ -26,18 +26,20 @@ // NOLINTBEGIN(*-avoid-c-arrays) #pragma once -// TODO use when moved to C++20 modules (parsing takes too long for each translation unit) +#include // IWYU pragma: keep #include #include // IWYU pragma: keep -#include +#include #ifndef MP_UNITS_IN_MODULE_INTERFACE -#include #include // IWYU pragma: export #include #include -#include +#include #include +#if MP_UNITS_HOSTED +#include +#endif #endif MP_UNITS_EXPORT @@ -49,121 +51,247 @@ namespace mp_units { * @tparam CharT Character type to be used by the string * @tparam N The size of the string */ -template -struct basic_fixed_string { - CharT data_[N + 1] = {}; +template> +class basic_fixed_string { +public: + CharT data_[N + 1] = {}; // exposition only + // types + using traits_type = Traits; using value_type = CharT; - using pointer = CharT*; - using const_pointer = const CharT*; - using reference = CharT&; - using const_reference = const CharT&; - using const_iterator = const CharT*; + using pointer = value_type*; + using const_pointer = const value_type*; + using reference = value_type&; + using const_reference = const value_type&; + using const_iterator = const value_type*; using iterator = const_iterator; + using const_reverse_iterator = std::reverse_iterator; + using reverse_iterator = const_reverse_iterator; using size_type = std::size_t; using difference_type = std::ptrdiff_t; + // construction and assignment + template... Chars> + requires(sizeof...(Chars) == N) && (... && !std::is_pointer_v) + constexpr explicit basic_fixed_string(Chars... chars) noexcept : data_{chars..., CharT{}} + { + } + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) - constexpr explicit(false) basic_fixed_string(const CharT (&txt)[N + 1]) noexcept + consteval explicit(false) basic_fixed_string(const CharT (&txt)[N + 1]) noexcept { - gsl_Expects(txt[N] == CharT{}); + MP_UNITS_EXPECTS(txt[N] == CharT{}); for (std::size_t i = 0; i < N; ++i) data_[i] = txt[i]; } template S> requires std::convertible_to, CharT> - constexpr explicit basic_fixed_string(It first, S last) noexcept + constexpr basic_fixed_string(It begin, S end) { - gsl_Expects(std::distance(first, last) == N); - for (auto it = data_; first != last; ++first, ++it) *it = *first; + MP_UNITS_EXPECTS(std::distance(begin, end) == N); + for (auto it = data_; begin != end; ++begin, ++it) *it = *begin; } - template... Rest> - requires(1 + sizeof...(Rest) == N) - constexpr explicit basic_fixed_string(CharT first, Rest... rest) noexcept : data_{first, rest..., CharT{}} + template + requires std::convertible_to, CharT> + constexpr basic_fixed_string(std::from_range_t, R&& r) { + MP_UNITS_EXPECTS(std::ranges::size(r) == N); + for (auto it = data_; auto&& v : std::forward(r)) *it++ = std::forward(v); } - [[nodiscard]] constexpr bool empty() const noexcept { return N == 0; } - [[nodiscard]] constexpr size_type size() const noexcept { return N; } - [[nodiscard]] constexpr const_pointer data() const noexcept { return static_cast(data_); } - [[nodiscard]] constexpr const CharT* c_str() const noexcept { return data(); } - [[nodiscard]] constexpr value_type operator[](size_type index) const noexcept - { - gsl_Expects(index < N); - return data()[index]; - } + constexpr basic_fixed_string(const basic_fixed_string&) noexcept = default; + constexpr basic_fixed_string& operator=(const basic_fixed_string&) noexcept = default; + // iterator support [[nodiscard]] constexpr const_iterator begin() const noexcept { return data(); } - [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return data(); } [[nodiscard]] constexpr const_iterator end() const noexcept { return data() + size(); } - [[nodiscard]] constexpr const_iterator cend() const noexcept { return data() + size(); } + [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return begin(); } + [[nodiscard]] constexpr const_iterator cend() const noexcept { return end(); } + [[nodiscard]] constexpr const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); } + [[nodiscard]] constexpr const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); } + [[nodiscard]] constexpr const_reverse_iterator crbegin() const noexcept { return rbegin(); } + [[nodiscard]] constexpr const_reverse_iterator crend() const noexcept { return rend(); } + + // capacity + [[nodiscard]] static constexpr std::integral_constant size() noexcept { return {}; } + [[nodiscard]] static constexpr std::integral_constant length() noexcept { return {}; } + [[nodiscard]] static constexpr std::integral_constant max_size() noexcept { return {}; } + [[nodiscard]] static constexpr std::bool_constant empty() noexcept { return {}; } + + // element access + [[nodiscard]] constexpr const_reference operator[](size_type pos) const + { + MP_UNITS_EXPECTS(pos < N); + return data()[pos]; + } + +#if MP_UNITS_HOSTED + [[nodiscard]] constexpr const_reference at(size_type pos) const + { + if (pos >= size()) throw std::out_of_range("basic_fixed_string::at"); + return (*this)[pos]; + } +#endif + + [[nodiscard]] constexpr const_reference front() const + { + MP_UNITS_EXPECTS(!empty()); + return (*this)[0]; + } + [[nodiscard]] constexpr const_reference back() const + { + MP_UNITS_EXPECTS(!empty()); + return (*this)[N - 1]; + } + + // modifiers + constexpr void swap(basic_fixed_string& s) noexcept { swap_ranges(begin(), end(), s.begin()); } + // string operations + [[nodiscard]] constexpr const_pointer c_str() const noexcept { return data(); } + [[nodiscard]] constexpr const_pointer data() const noexcept { return static_cast(data_); } + [[nodiscard]] constexpr std::basic_string_view view() const noexcept + { + return std::basic_string_view(cbegin(), cend()); + } // NOLINTNEXTLINE(*-explicit-conversions, google-explicit-constructor) - [[nodiscard]] constexpr explicit(false) operator std::basic_string_view() const noexcept + [[nodiscard]] constexpr explicit(false) operator std::basic_string_view() const noexcept { - return std::basic_string_view(cbegin(), cend()); + return view(); } template - [[nodiscard]] constexpr friend basic_fixed_string operator+( - const basic_fixed_string& lhs, const basic_fixed_string& rhs) noexcept + [[nodiscard]] constexpr friend basic_fixed_string operator+( + const basic_fixed_string& lhs, const basic_fixed_string& rhs) noexcept { - CharT txt[N + N2 + 1] = {}; - - for (size_t i = 0; i != N; ++i) txt[i] = lhs[i]; - for (size_t i = 0; i != N2; ++i) txt[N + i] = rhs[i]; + CharT txt[N + N2]; + CharT* it = txt; + for (CharT c : lhs) *it++ = c; + for (CharT c : rhs) *it++ = c; + return basic_fixed_string(txt, it); + } - return basic_fixed_string(txt); + [[nodiscard]] constexpr friend basic_fixed_string operator+(const basic_fixed_string& lhs, + CharT rhs) noexcept + { + CharT txt[N + 1]; + CharT* it = txt; + for (CharT c : lhs) *it++ = c; + *it++ = rhs; + return basic_fixed_string(txt, it); } - [[nodiscard]] constexpr bool operator==(const basic_fixed_string&) const = default; + [[nodiscard]] constexpr friend basic_fixed_string operator+( + const CharT lhs, const basic_fixed_string& rhs) noexcept + { + CharT txt[1 + N]; + CharT* it = txt; + *it++ = lhs; + for (CharT c : rhs) *it++ = c; + return basic_fixed_string(txt, it); + } template - [[nodiscard]] friend constexpr bool operator==(const basic_fixed_string&, const basic_fixed_string&) + [[nodiscard]] consteval friend basic_fixed_string operator+( + const basic_fixed_string& lhs, const CharT (&rhs)[N2]) noexcept { - return false; + MP_UNITS_EXPECTS(rhs[N2 - 1] == CharT{}); + CharT txt[N + N2]; + CharT* it = txt; + for (CharT c : lhs) *it++ = c; + for (CharT c : rhs) *it++ = c; + return txt; } - template + template + [[nodiscard]] consteval friend basic_fixed_string operator+( + const CharT (&lhs)[N1], const basic_fixed_string& rhs) noexcept + { + MP_UNITS_EXPECTS(lhs[N1 - 1] == CharT{}); + CharT txt[N1 + N]; + CharT* it = txt; + for (size_t i = 0; i != N1 - 1; ++i) *it++ = lhs[i]; + for (CharT c : rhs) *it++ = c; + *it++ = CharT(); + return txt; + } + + // non-member comparison functions + template + [[nodiscard]] friend constexpr bool operator==(const basic_fixed_string& lhs, + const basic_fixed_string& rhs) + { + return lhs.view() == rhs.view(); + } + template + [[nodiscard]] friend consteval bool operator==(const basic_fixed_string& lhs, const CharT (&rhs)[N2]) + { + MP_UNITS_EXPECTS(rhs[N2 - 1] == CharT{}); + return lhs.view() == std::basic_string_view(std::cbegin(rhs), std::cend(rhs) - 1); + } + + template [[nodiscard]] friend constexpr auto operator<=>(const basic_fixed_string& lhs, - const basic_fixed_string& rhs) + const basic_fixed_string& rhs) + { + return lhs.view() <=> rhs.view(); + } + template + [[nodiscard]] friend consteval auto operator<=>(const basic_fixed_string& lhs, const CharT (&rhs)[N2]) { - // TODO std::lexicographical_compare_three_way(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); - return detail::lexicographical_compare_three_way(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); + MP_UNITS_EXPECTS(rhs[N2 - 1] == CharT{}); + return lhs.view() <=> std::basic_string_view(std::cbegin(rhs), std::cend(rhs) - 1); } - template + // inserters and extractors +#if MP_UNITS_HOSTED friend std::basic_ostream& operator<<(std::basic_ostream& os, - const basic_fixed_string& str) + const basic_fixed_string& str) { return os << str.c_str(); } +#endif }; +// deduction guides +template CharT, std::convertible_to... Rest> +basic_fixed_string(CharT, Rest...) -> basic_fixed_string; + template basic_fixed_string(const CharT (&str)[N]) -> basic_fixed_string; -template... Rest> -basic_fixed_string(CharT, Rest...) -> basic_fixed_string; +template CharT, std::size_t N> +basic_fixed_string(std::from_range_t, std::array) -> basic_fixed_string; +// typedef-names template using fixed_string = basic_fixed_string; - -template -using fixed_wstring = basic_fixed_string; - template using fixed_u8string = basic_fixed_string; - template using fixed_u16string = basic_fixed_string; - template using fixed_u32string = basic_fixed_string; +template +using fixed_wstring = basic_fixed_string; } // namespace mp_units +// hash support +template +struct std::hash> : std::hash {}; +template +struct std::hash> : std::hash {}; +template +struct std::hash> : std::hash {}; +template +struct std::hash> : std::hash {}; +template +struct std::hash> : std::hash {}; + +#if MP_UNITS_HOSTED +// formatting support template struct MP_UNITS_STD_FMT::formatter> : formatter> { template @@ -172,4 +300,6 @@ struct MP_UNITS_STD_FMT::formatter> : for return formatter>::format(std::basic_string_view(str), ctx); } }; +#endif + // NOLINTEND(*-avoid-c-arrays) diff --git a/src/core/include/mp-units/ext/type_traits.h b/src/core/include/mp-units/ext/type_traits.h index ce68be694..25b47e954 100644 --- a/src/core/include/mp-units/ext/type_traits.h +++ b/src/core/include/mp-units/ext/type_traits.h @@ -88,9 +88,8 @@ void to_base_specialization_of(const volatile Type*); } // namespace detail template typename Type> -// inline constexpr bool // TODO: Replace with concept when it works with MSVC -concept is_derived_from_specialization_of = requires(T* t) { detail::to_base_specialization_of(t); }; - +inline constexpr bool is_derived_from_specialization_of = + requires(T* t) { detail::to_base_specialization_of(t); }; namespace detail { @@ -137,7 +136,7 @@ concept one_of = (false || ... || std::same_as); template [[nodiscard]] consteval bool contains() { - return (false || ... || is_same_v, T>); + return (false || ... || is_same_v); } template typename T, typename... Ts> @@ -161,7 +160,7 @@ template auto V> template [[nodiscard]] consteval auto get() { - if constexpr (is_same_v>) + if constexpr (is_same_v) return V1; else return get(); diff --git a/src/core/include/mp-units/format.h b/src/core/include/mp-units/format.h index 544ce69c5..d901b8dcd 100644 --- a/src/core/include/mp-units/format.h +++ b/src/core/include/mp-units/format.h @@ -24,22 +24,21 @@ #pragma once +#include +// #include +#include #include #include #include #include #include -namespace mp_units::detail { +#ifndef MP_UNITS_IN_MODULE_INTERFACE +#include +#endif -template -struct fill_align_width_format_specs { - fill_t fill; - fmt_align align : 4 = fmt_align::none; - int width = 0; - fmt_arg_ref width_ref; -}; +namespace mp_units::detail { template [[nodiscard]] constexpr const Char* at_most_one_of(const Char* begin, const Char* end, std::string_view modifiers) @@ -51,6 +50,17 @@ template return it; } +// TODO the below should be exposed by the C++ Standard Library (used in our examples) +MP_UNITS_EXPORT_BEGIN + +template +struct fill_align_width_format_specs { + fill_t fill; + fmt_align align : 4 = fmt_align::none; + int width = 0; + fmt_arg_ref width_ref; +}; + template [[nodiscard]] constexpr const Char* parse_fill_align_width(MP_UNITS_STD_FMT::basic_format_parse_context& ctx, const Char* begin, const Char* end, Specs& specs, @@ -89,6 +99,8 @@ OutputIt format_global_buffer(OutputIt out, const fill_align_width_format_specs< return MP_UNITS_STD_FMT::format_to(out, "}}"); } +MP_UNITS_EXPORT_END + } // namespace mp_units::detail // diff --git a/src/core/include/mp-units/framework/dimension.h b/src/core/include/mp-units/framework/dimension.h index 770194b98..4aa746dd0 100644 --- a/src/core/include/mp-units/framework/dimension.h +++ b/src/core/include/mp-units/framework/dimension.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -33,12 +34,13 @@ #include #ifndef MP_UNITS_IN_MODULE_INTERFACE -#include #include #include #include -#include #include +#if MP_UNITS_HOSTED +#include +#endif #endif namespace mp_units { @@ -280,7 +282,8 @@ template Out, typename... Expr> constexpr Out dimension_symbol_impl(Out out, const derived_dimension&, const dimension_symbol_formatting& fmt, bool negative_power) { - gsl_Expects(negative_power == false); + (void)negative_power; + MP_UNITS_EXPECTS(negative_power == false); return dimension_symbol_impl(out, typename derived_dimension::_num_{}, typename derived_dimension::_den_{}, fmt); } @@ -317,9 +320,15 @@ MP_UNITS_EXPORT template buffer; dimension_symbol_to(std::back_inserter(buffer), D{}, fmt); return buffer.size(); +#else + std::array buffer; // TODO unsafe + auto end = dimension_symbol_to(buffer.begin(), D{}, fmt); + return end - buffer.begin(); +#endif }; #if MP_UNITS_API_STRING_VIEW_RET // Permitting static constexpr variables in constexpr functions @@ -328,8 +337,7 @@ MP_UNITS_EXPORT template(D{}); - return basic_fixed_string(buffer.begin(), buffer.end()); + return basic_fixed_string(std::from_range, detail::get_symbol_buffer(D{})); #endif } diff --git a/src/core/include/mp-units/framework/dimension_concepts.h b/src/core/include/mp-units/framework/dimension_concepts.h index 9826e1b01..11be622d4 100644 --- a/src/core/include/mp-units/framework/dimension_concepts.h +++ b/src/core/include/mp-units/framework/dimension_concepts.h @@ -109,6 +109,6 @@ concept Dimension = detail::BaseDimension || detail::DerivedDimension; * Satisfied when both argument satisfy a `Dimension` concept and when they compare equal. */ MP_UNITS_EXPORT template -concept DimensionOf = Dimension && Dimension> && (T{} == D); +concept DimensionOf = Dimension && Dimension && (T{} == D); } // namespace mp_units diff --git a/src/core/include/mp-units/framework/expression_template.h b/src/core/include/mp-units/framework/expression_template.h index c0eddbda1..291bfedb2 100644 --- a/src/core/include/mp-units/framework/expression_template.h +++ b/src/core/include/mp-units/framework/expression_template.h @@ -534,8 +534,8 @@ template typename Proj, template typename To, ty expr_type_projectable... Dens> [[nodiscard]] consteval auto expr_map_impl(type_list, type_list) { - return (OneType{} * ... * map_power(typename expr_type_map, Proj>::type{})) / - (OneType{} * ... * map_power(typename expr_type_map, Proj>::type{})); + return (OneType{} * ... * map_power(typename expr_type_map::type{})) / + (OneType{} * ... * map_power(typename expr_type_map::type{})); } /** diff --git a/src/core/include/mp-units/framework/magnitude.h b/src/core/include/mp-units/framework/magnitude.h index c7ec64db6..bd18ab525 100644 --- a/src/core/include/mp-units/framework/magnitude.h +++ b/src/core/include/mp-units/framework/magnitude.h @@ -38,6 +38,7 @@ #ifndef MP_UNITS_IN_MODULE_INTERFACE #include #include +#include #include #include #endif @@ -278,7 +279,7 @@ template // As this function should only be called at compile time, the terminations herein function as // "parameter-compatible static_asserts", and should not result in terminations at runtime. if (exp < 0) { - std::terminate(); // int_power only supports positive integer powers + std::abort(); // int_power only supports positive integer powers } constexpr auto checked_multiply = [](auto a, auto b) { @@ -286,7 +287,7 @@ template MP_UNITS_DIAGNOSTIC_PUSH MP_UNITS_DIAGNOSTIC_IGNORE_FLOAT_EQUAL if (result / a != b) { - std::terminate(); // Wraparound detected + std::abort(); // Wraparound detected } MP_UNITS_DIAGNOSTIC_POP return result; @@ -294,17 +295,8 @@ template constexpr auto checked_square = [checked_multiply](auto a) { return checked_multiply(a, a); }; - // TODO(chogg): Unify this implementation with the one in pow.h. That one takes its exponent as a - // template parameter, rather than a function parameter. - - if (exp == 0) { - return T{1}; - } - - if (exp % 2 == 1) { - return checked_multiply(base, int_power(base, exp - 1)); - } - + if (exp == 0) return T{1}; + if (exp % 2 == 1) return checked_multiply(base, int_power(base, exp - 1)); return checked_square(int_power(base, exp / 2)); } @@ -319,12 +311,12 @@ template // terminations is to act as "static_assert substitutes", not to actually terminate at runtime. const auto exp = get_exponent(el); if (exp.den != 1) { - std::terminate(); // Rational powers not yet supported + std::abort(); // Rational powers not yet supported } if (exp.num < 0) { if constexpr (std::is_integral_v) { - std::terminate(); // Cannot represent reciprocal as integer + std::abort(); // Cannot represent reciprocal as integer } else { return T{1} / compute_base_power(inverse(el)); } @@ -339,7 +331,6 @@ template // The input is the desired result, but in a (wider) intermediate type. The point of this function // is to cast to the desired type, but avoid overflow in doing so. template -// TODO(chogg): Migrate this to use `treat_as_floating_point`. requires(!std::is_integral_v || std::is_integral_v) [[nodiscard]] consteval To checked_static_cast(From x) { @@ -347,7 +338,7 @@ template // to produce compiler errors, because we cannot `static_assert` on function arguments. if constexpr (std::is_integral_v) { if (!std::in_range(x)) { - std::terminate(); // Cannot represent magnitude in this type + std::abort(); // Cannot represent magnitude in this type } } diff --git a/src/core/include/mp-units/framework/quantity.h b/src/core/include/mp-units/framework/quantity.h index a4880f483..fb63eb82c 100644 --- a/src/core/include/mp-units/framework/quantity.h +++ b/src/core/include/mp-units/framework/quantity.h @@ -26,6 +26,7 @@ // IWYU pragma: private, include #include #include +#include #include #include #include @@ -36,7 +37,6 @@ #include #ifndef MP_UNITS_IN_MODULE_INTERFACE -#include #include // IWYU pragma: export #include #endif @@ -140,8 +140,7 @@ class quantity { template requires std::same_as, Rep> - constexpr quantity(Value&& v, std::remove_const_t) : - numerical_value_is_an_implementation_detail_(std::forward(v)) + constexpr quantity(Value&& v, decltype(R)) : numerical_value_is_an_implementation_detail_(std::forward(v)) { } @@ -353,7 +352,7 @@ class quantity { friend constexpr decltype(auto) operator%=(Q&& lhs, const quantity& rhs) { - gsl_ExpectsDebug(rhs != zero()); + MP_UNITS_EXPECTS_DEBUG(rhs != zero()); lhs.numerical_value_is_an_implementation_detail_ %= rhs.numerical_value_is_an_implementation_detail_; return std::forward(lhs); } @@ -393,7 +392,7 @@ class quantity { } friend constexpr decltype(auto) operator/=(Q&& lhs, const Value& v) { - gsl_ExpectsDebug(v != quantity_values::zero()); + MP_UNITS_EXPECTS_DEBUG(v != quantity_values::zero()); lhs.numerical_value_is_an_implementation_detail_ /= v; return std::forward(lhs); } @@ -407,7 +406,7 @@ class quantity { } friend constexpr decltype(auto) operator/=(Q1&& lhs, const Q2& rhs) { - gsl_ExpectsDebug(rhs != rhs.zero()); + MP_UNITS_EXPECTS_DEBUG(rhs != rhs.zero()); lhs.numerical_value_is_an_implementation_detail_ /= rhs.numerical_value_is_an_implementation_detail_; return std::forward(lhs); } @@ -451,7 +450,7 @@ template detail::CommonlyInvocableQuantities, quantity, quantity> [[nodiscard]] constexpr Quantity auto operator%(const quantity& lhs, const quantity& rhs) { - gsl_ExpectsDebug(rhs != rhs.zero()); + MP_UNITS_EXPECTS_DEBUG(rhs != rhs.zero()); using ret = detail::common_quantity_for, quantity, quantity>; const ret ret_lhs(lhs); const ret ret_rhs(rhs); @@ -486,7 +485,7 @@ template requires detail::InvocableQuantities, quantity, quantity> [[nodiscard]] constexpr Quantity auto operator/(const quantity& lhs, const quantity& rhs) { - gsl_ExpectsDebug(rhs != rhs.zero()); + MP_UNITS_EXPECTS_DEBUG(rhs != rhs.zero()); return quantity{lhs.numerical_value_ref_in(get_unit(R1)) / rhs.numerical_value_ref_in(get_unit(R2)), R1 / R2}; } @@ -495,7 +494,7 @@ template detail::InvokeResultOf, Rep, const Value&> [[nodiscard]] constexpr QuantityOf auto operator/(const quantity& q, const Value& v) { - gsl_ExpectsDebug(v != quantity_values::zero()); + MP_UNITS_EXPECTS_DEBUG(v != quantity_values::zero()); return quantity{q.numerical_value_ref_in(get_unit(R)) / v, R}; } diff --git a/src/core/include/mp-units/framework/quantity_concepts.h b/src/core/include/mp-units/framework/quantity_concepts.h index 6886b8a97..37929f198 100644 --- a/src/core/include/mp-units/framework/quantity_concepts.h +++ b/src/core/include/mp-units/framework/quantity_concepts.h @@ -41,7 +41,7 @@ void to_base_specialization_of_quantity(const volatile quantity*); template inline constexpr bool is_derived_from_specialization_of_quantity = - requires(std::remove_reference_t* t) { to_base_specialization_of_quantity(t); }; + requires(T* t) { to_base_specialization_of_quantity(t); }; } // namespace detail diff --git a/src/core/include/mp-units/framework/quantity_point.h b/src/core/include/mp-units/framework/quantity_point.h index adacb6b79..24eb9957d 100644 --- a/src/core/include/mp-units/framework/quantity_point.h +++ b/src/core/include/mp-units/framework/quantity_point.h @@ -169,8 +169,7 @@ class quantity_point { template requires QuantityOf, get_quantity_spec(R)> && std::constructible_from - constexpr quantity_point(Q&& q, std::remove_const_t) : - quantity_from_origin_is_an_implementation_detail_(std::forward(q)) + constexpr quantity_point(Q&& q, decltype(PO)) : quantity_from_origin_is_an_implementation_detail_(std::forward(q)) { } @@ -187,7 +186,6 @@ class quantity_point { template QP> requires std::constructible_from - // TODO add perfect forwarding // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) constexpr explicit(!std::convertible_to) quantity_point(const QP& qp) : quantity_from_origin_is_an_implementation_detail_([&] { @@ -222,7 +220,7 @@ class quantity_point { [[nodiscard]] constexpr MP_UNITS_CONSTRAINED_AUTO_WORKAROUND(QuantityPointOf) auto point_for( NewPO new_origin) const { - if constexpr (is_same_v>) + if constexpr (is_same_v) return *this; else return ::mp_units::quantity_point{*this - new_origin, new_origin}; @@ -388,7 +386,7 @@ explicit( template // TODO simplify when gcc catches up - requires ReferenceOf, PO1.quantity_spec> + requires ReferenceOf [[nodiscard]] constexpr QuantityPoint auto operator+(const quantity_point& qp, const quantity& q) requires requires { qp.quantity_ref_from(PO1) + q; } @@ -401,7 +399,7 @@ template template // TODO simplify when gcc catches up - requires ReferenceOf, PO2.quantity_spec> + requires ReferenceOf [[nodiscard]] constexpr QuantityPoint auto operator+(const quantity& q, const quantity_point& qp) requires requires { q + qp.quantity_ref_from(PO2); } @@ -425,7 +423,7 @@ template template // TODO simplify when gcc catches up - requires ReferenceOf, PO1.quantity_spec> + requires ReferenceOf [[nodiscard]] constexpr QuantityPoint auto operator-(const quantity_point& qp, const quantity& q) requires requires { qp.quantity_ref_from(PO1) - q; } diff --git a/src/core/include/mp-units/framework/quantity_point_concepts.h b/src/core/include/mp-units/framework/quantity_point_concepts.h index ee5ca8eb5..aa2a13ccf 100644 --- a/src/core/include/mp-units/framework/quantity_point_concepts.h +++ b/src/core/include/mp-units/framework/quantity_point_concepts.h @@ -105,7 +105,7 @@ concept PointOrigin = detail::AbsolutePointOrigin || detail::RelativePointOri * Satisfied by all quantity point origins that are defined using a provided quantity specification. */ MP_UNITS_EXPORT template -concept PointOriginFor = PointOrigin && QuantitySpecOf, T::quantity_spec>; +concept PointOriginFor = PointOrigin && QuantitySpecOf; MP_UNITS_EXPORT template auto PO, RepresentationOf Rep> @@ -141,7 +141,7 @@ template template concept SameAbsolutePointOriginAs = - PointOrigin && PointOrigin> && same_absolute_point_origins(T{}, V); + PointOrigin && PointOrigin && same_absolute_point_origins(T{}, V); } // namespace detail diff --git a/src/core/include/mp-units/framework/quantity_spec.h b/src/core/include/mp-units/framework/quantity_spec.h index 1ff3dc1d2..383a61f3a 100644 --- a/src/core/include/mp-units/framework/quantity_spec.h +++ b/src/core/include/mp-units/framework/quantity_spec.h @@ -38,8 +38,10 @@ #include #ifndef MP_UNITS_IN_MODULE_INTERFACE +#include #include #include +#include #endif namespace mp_units { @@ -216,11 +218,11 @@ MP_UNITS_EXPORT_END */ #ifdef MP_UNITS_API_NO_CRTP template auto... Args> - requires(... && !QuantitySpec>) + requires(... && !QuantitySpec) struct quantity_spec : detail::quantity_spec_interface { #else template auto... Args> - requires(... && !QuantitySpec>) + requires(... && !QuantitySpec) struct quantity_spec : detail::quantity_spec_interface { #endif static constexpr detail::BaseDimension auto dimension = Dim; @@ -259,11 +261,11 @@ struct quantity_spec : detail::quantity_spec_interface */ #ifdef MP_UNITS_API_NO_CRTP template auto... Args> - requires(... && !QuantitySpec>) + requires(... && !QuantitySpec) struct quantity_spec : detail::quantity_spec_interface { #else template auto... Args> - requires(... && !QuantitySpec>) + requires(... && !QuantitySpec) struct quantity_spec : detail::quantity_spec_interface { #endif static constexpr auto _equation_ = Eq; @@ -300,12 +302,12 @@ struct quantity_spec : detail::quantity_spec_interface */ #ifdef MP_UNITS_API_NO_CRTP template auto... Args> - requires(... && !QuantitySpec>) -struct quantity_spec : std::remove_const_t { + requires(... && !QuantitySpec) +struct quantity_spec : decltype(QS) { #else template auto... Args> - requires(... && !QuantitySpec>) -struct quantity_spec : std::remove_const_t { + requires(... && !QuantitySpec) +struct quantity_spec : decltype(QS) { #endif static constexpr auto _parent_ = QS; static constexpr quantity_character character = detail::quantity_character_init(QS.character); @@ -359,16 +361,16 @@ struct quantity_spec : std::remove_const_t { #ifdef MP_UNITS_API_NO_CRTP template auto... Args> - requires(!requires { QS._equation_; } || - (requires { QS._equation_; } && (explicitly_convertible(Eq, QS._equation_)))) && - (... && !QuantitySpec>) + requires(!requires { QS._equation_; } || (requires { + QS._equation_; + } && (explicitly_convertible(Eq, QS._equation_)))) && (... && !QuantitySpec) struct quantity_spec : quantity_spec { #else template auto... Args> - requires(!requires { QS._equation_; } || - (requires { QS._equation_; } && (explicitly_convertible(Eq, QS._equation_)))) && - (... && !QuantitySpec>) + requires(!requires { QS._equation_; } || (requires { + QS._equation_; + } && (explicitly_convertible(Eq, QS._equation_)))) && (... && !QuantitySpec) struct quantity_spec : quantity_spec { #endif static constexpr auto _equation_ = Eq; @@ -481,7 +483,7 @@ struct kind_of_ : quantity_spec, Q{}> { MP_UNITS_EXPORT template requires(detail::get_kind_tree_root(Q) == Q) -inline constexpr kind_of_> kind_of; +inline constexpr kind_of_ kind_of; namespace detail { @@ -491,7 +493,7 @@ struct is_dimensionless : std::true_type {}; template [[nodiscard]] consteval QuantitySpec auto clone_kind_of(Q q) { - if constexpr ((... && QuantityKindSpec>)) + if constexpr ((... && QuantityKindSpec)) return kind_of; else return q; @@ -550,11 +552,6 @@ template requires detail::non_zero [[nodiscard]] consteval QuantitySpec auto pow(Q q) { - // TODO Does the below make sense? - // `2 * 2` should compare to `4` - // `2 * one * (2 * one)` should compare to `4 * one` - // `2 * rad * (2 * rad)` should compare to `4 * rad^2` - // all are dimensionless quantities :-( if constexpr (Num == 0 || Q{} == dimensionless) return dimensionless; else if constexpr (detail::ratio{Num, Den} == 1) @@ -564,11 +561,9 @@ template detail::expr_pow( detail::remove_kind(q))); else if constexpr (Den == 1) - return detail::clone_kind_of( - derived_quantity_spec, Num>>{}); + return detail::clone_kind_of(derived_quantity_spec>{}); else - return detail::clone_kind_of( - derived_quantity_spec, Num, Den>>{}); + return detail::clone_kind_of(derived_quantity_spec>{}); } @@ -915,12 +910,11 @@ template return extract_results{false}; else if constexpr (from_exp > to_exp) return extract_results{true, pow(from_factor), pow(to_factor), - prepend_rest::first, - power_or_T, from_exp - to_exp>{}}; + prepend_rest::first, power_or_T{}}; else return extract_results{true, pow(from_factor), pow(to_factor), prepend_rest::second, - power_or_T, to_exp - from_exp>{}}; + power_or_T{}}; } } @@ -934,7 +928,7 @@ template; + using elem = decltype(Ext.elem); if constexpr (Entities == process_entities::numerators) { if constexpr (Ext.prepend == prepend_rest::first) return min(res, are_ingredients_convertible(type_list_push_front{}, den_from, num_to, den_to)); @@ -959,7 +953,7 @@ template; + using elem = decltype(Ext.elem); if constexpr (Entities == process_entities::from) { if constexpr (Ext.prepend == prepend_rest::first) return are_ingredients_convertible(type_list_push_front{}, den_from, num_to, den_to); @@ -1342,8 +1336,7 @@ template using enum specs_convertible_result; return res == no ? no : yes; }; - if constexpr ((NamedQuantitySpec> && - NamedQuantitySpec>) || + if constexpr ((NamedQuantitySpec && NamedQuantitySpec) || get_complexity(from_kind) == get_complexity(to_kind)) return convertible_impl(from_kind, to_kind); else if constexpr (get_complexity(from_kind) > get_complexity(to_kind)) @@ -1396,7 +1389,7 @@ template return are_ingredients_convertible(from, to); } else if constexpr (IntermediateDerivedQuantitySpec) { auto res = explode(from); - if constexpr (NamedQuantitySpec>) + if constexpr (NamedQuantitySpec) return convertible_impl(res.quantity, to); else if constexpr (requires { to._equation_; }) { auto eq = explode_to_equation(to); @@ -1405,7 +1398,7 @@ template return are_ingredients_convertible(from, to); } else if constexpr (IntermediateDerivedQuantitySpec) { auto res = explode(to); - if constexpr (NamedQuantitySpec>) + if constexpr (NamedQuantitySpec) return min(res.result, convertible_impl(from, res.quantity)); else if constexpr (requires { from._equation_; }) return min(res.result, convertible_impl(from._equation_, res.quantity)); @@ -1450,7 +1443,7 @@ namespace detail { template requires requires(Q q) { get_kind_tree_root(q); } -using to_kind = std::remove_const_t; +using to_kind = decltype(get_kind_tree_root(Q{})); #ifdef MP_UNITS_API_NO_CRTP template @@ -1507,8 +1500,8 @@ template requires(implicitly_convertible(get_kind_tree_root(q1), get_kind_tree_root(q2)) || implicitly_convertible(get_kind_tree_root(q2), get_kind_tree_root(q1))) { - using QQ1 = std::remove_const_t; - using QQ2 = std::remove_const_t; + using QQ1 = decltype(detail::remove_kind(q1)); + using QQ2 = decltype(detail::remove_kind(q2)); // NOLINTBEGIN(bugprone-branch-clone) if constexpr (is_same_v) diff --git a/src/core/include/mp-units/framework/quantity_spec_concepts.h b/src/core/include/mp-units/framework/quantity_spec_concepts.h index 920e9cf5b..ab5b047de 100644 --- a/src/core/include/mp-units/framework/quantity_spec_concepts.h +++ b/src/core/include/mp-units/framework/quantity_spec_concepts.h @@ -140,15 +140,14 @@ namespace detail { template concept NestedQuantityKindSpecOf = - QuantitySpec> && QuantitySpec> && - get_kind(From) != get_kind(To) && - std::derived_from, std::remove_cvref_t>; + QuantitySpec && QuantitySpec && get_kind(From) != get_kind(To) && + std::derived_from>; } MP_UNITS_EXPORT template concept QuantitySpecOf = - QuantitySpec && QuantitySpec> && implicitly_convertible(T{}, QS) && + QuantitySpec && QuantitySpec && implicitly_convertible(T{}, QS) && // the below is to make the following work // static_assert(ReferenceOf); // static_assert(!ReferenceOf); diff --git a/src/core/include/mp-units/framework/reference.h b/src/core/include/mp-units/framework/reference.h index 8050055aa..746abb443 100644 --- a/src/core/include/mp-units/framework/reference.h +++ b/src/core/include/mp-units/framework/reference.h @@ -38,7 +38,7 @@ namespace mp_units { namespace detail { template -using reference_t = reference, std::remove_const_t>; +using reference_t = reference; } @@ -258,13 +258,13 @@ MP_UNITS_EXPORT_END namespace detail { template -[[nodiscard]] consteval std::remove_const_t clone_reference_with(From) +[[nodiscard]] consteval MP_UNITS_REMOVE_CONST(decltype(To)) clone_reference_with(From) { return {}; } template -[[nodiscard]] consteval reference> clone_reference_with(reference) +[[nodiscard]] consteval reference clone_reference_with(reference) { return {}; } diff --git a/src/core/include/mp-units/framework/reference_concepts.h b/src/core/include/mp-units/framework/reference_concepts.h index 6ac2187b3..a2c78365c 100644 --- a/src/core/include/mp-units/framework/reference_concepts.h +++ b/src/core/include/mp-units/framework/reference_concepts.h @@ -76,7 +76,7 @@ template * the provided quantity_spec type. */ template -concept ReferenceOf = Reference && QuantitySpecOf, QS>; +concept ReferenceOf = Reference && QuantitySpecOf; MP_UNITS_EXPORT_END diff --git a/src/core/include/mp-units/framework/symbol_text.h b/src/core/include/mp-units/framework/symbol_text.h index d36aa082d..e89aab96e 100644 --- a/src/core/include/mp-units/framework/symbol_text.h +++ b/src/core/include/mp-units/framework/symbol_text.h @@ -26,10 +26,11 @@ // IWYU pragma: private, include #include #include +#include +#include #include #ifndef MP_UNITS_IN_MODULE_INTERFACE -#include #include // IWYU pragma: export #include #include @@ -85,41 +86,42 @@ constexpr fixed_u8string to_u8string(fixed_string txt) * @tparam M The size of the ASCII-only symbol */ MP_UNITS_EXPORT template -struct symbol_text { +class symbol_text { +public: fixed_u8string unicode_; fixed_string ascii_; // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) constexpr explicit(false) symbol_text(char ch) : unicode_(static_cast(ch)), ascii_(ch) { - gsl_Expects(detail::is_basic_literal_character_set_char(ch)); + MP_UNITS_EXPECTS(detail::is_basic_literal_character_set_char(ch)); } // NOLINTNEXTLINE(*-avoid-c-arrays, google-explicit-constructor, hicpp-explicit-conversions) - constexpr explicit(false) symbol_text(const char (&txt)[N + 1]) : + consteval explicit(false) symbol_text(const char (&txt)[N + 1]) : unicode_(detail::to_u8string(basic_fixed_string{txt})), ascii_(txt) { - gsl_Expects(txt[N] == char{}); - gsl_Expects(detail::is_basic_literal_character_set(txt)); + MP_UNITS_EXPECTS(txt[N] == char{}); + MP_UNITS_EXPECTS(detail::is_basic_literal_character_set(txt)); } // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) constexpr explicit(false) symbol_text(const fixed_string& txt) : unicode_(detail::to_u8string(txt)), ascii_(txt) { - gsl_Expects(detail::is_basic_literal_character_set(txt.data_)); + MP_UNITS_EXPECTS(detail::is_basic_literal_character_set(txt.data_)); } // NOLINTNEXTLINE(*-avoid-c-arrays) - constexpr symbol_text(const char8_t (&u)[N + 1], const char (&a)[M + 1]) : unicode_(u), ascii_(a) + consteval symbol_text(const char8_t (&u)[N + 1], const char (&a)[M + 1]) : unicode_(u), ascii_(a) { - gsl_Expects(u[N] == char8_t{}); - gsl_Expects(a[M] == char{}); - gsl_Expects(detail::is_basic_literal_character_set(a)); + MP_UNITS_EXPECTS(u[N] == char8_t{}); + MP_UNITS_EXPECTS(a[M] == char{}); + MP_UNITS_EXPECTS(detail::is_basic_literal_character_set(a)); } constexpr symbol_text(const fixed_u8string& u, const fixed_string& a) : unicode_(u), ascii_(a) { - gsl_Expects(detail::is_basic_literal_character_set(a.data_)); + MP_UNITS_EXPECTS(detail::is_basic_literal_character_set(a.data_)); } [[nodiscard]] constexpr const auto& unicode() const { return unicode_; } @@ -127,7 +129,7 @@ struct symbol_text { [[nodiscard]] constexpr bool empty() const { - gsl_AssertDebug(unicode().empty() == ascii().empty()); + MP_UNITS_ASSERT_DEBUG(unicode().empty() == ascii().empty()); return unicode().empty(); } diff --git a/src/core/include/mp-units/framework/system_reference.h b/src/core/include/mp-units/framework/system_reference.h index 39f16bcf0..b4c202ecf 100644 --- a/src/core/include/mp-units/framework/system_reference.h +++ b/src/core/include/mp-units/framework/system_reference.h @@ -60,7 +60,7 @@ namespace mp_units { * @tparam CoU coherent unit for a quantity in this system */ template - requires(!AssociatedUnit>) || (CoU == one) + requires(!AssociatedUnit) || (CoU == one) struct system_reference { static constexpr auto quantity_spec = Q; static constexpr auto coherent_unit = CoU; @@ -68,9 +68,9 @@ struct system_reference { template requires(convertible(coherent_unit, U{})) #if MP_UNITS_COMP_MSVC - [[nodiscard]] constexpr decltype(reference, U>{}) operator[](U) const + [[nodiscard]] constexpr decltype(reference{}) operator[](U) const #else - [[nodiscard]] constexpr reference, U> operator[](U) const + [[nodiscard]] constexpr reference operator[](U) const #endif { return {}; diff --git a/src/core/include/mp-units/framework/unit.h b/src/core/include/mp-units/framework/unit.h index ce9484a2a..03dde4f07 100644 --- a/src/core/include/mp-units/framework/unit.h +++ b/src/core/include/mp-units/framework/unit.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -40,12 +41,13 @@ #include #ifndef MP_UNITS_IN_MODULE_INTERFACE -#include #include #include #include -#include #include +#if MP_UNITS_HOSTED +#include +#endif #endif namespace mp_units { @@ -173,13 +175,13 @@ struct named_unit { */ template requires(!Symbol.empty()) -struct named_unit : std::remove_const_t { +struct named_unit : decltype(U) { static constexpr auto symbol = Symbol; ///< Unique unit identifier }; template requires(!Symbol.empty()) -struct named_unit : std::remove_const_t { +struct named_unit : decltype(U) { static constexpr auto symbol = Symbol; ///< Unique unit identifier static constexpr auto point_origin = PO; }; @@ -195,14 +197,14 @@ struct named_unit : std::remove_const_t { */ template requires(!Symbol.empty()) && (QS.dimension == detail::get_associated_quantity(U).dimension) -struct named_unit : std::remove_const_t { +struct named_unit : decltype(U) { static constexpr auto symbol = Symbol; ///< Unique unit identifier static constexpr auto quantity_spec = QS; }; template requires(!Symbol.empty()) && (QS.dimension == detail::get_associated_quantity(U).dimension) -struct named_unit : std::remove_const_t { +struct named_unit : decltype(U) { static constexpr auto symbol = Symbol; ///< Unique unit identifier static constexpr auto quantity_spec = QS; static constexpr auto point_origin = PO; @@ -232,7 +234,7 @@ struct named_unit : std::remove_const_t { */ MP_UNITS_EXPORT template requires(!Symbol.empty()) -struct prefixed_unit : std::remove_const_t { +struct prefixed_unit : decltype(M * U) { static constexpr auto symbol = Symbol + U.symbol; }; @@ -390,7 +392,7 @@ template return canonical_unit{pow(base.mag) * num.mag / den.mag, num.reference_unit / den.reference_unit}; } else { return canonical_unit{pow(base.mag), - derived_unit, Num, Den...>>{}}; + derived_unit>{}}; } } @@ -430,9 +432,9 @@ MP_UNITS_EXPORT_BEGIN * Multiplication by `1` returns the same unit, otherwise `scaled_unit` is being returned. */ template -[[nodiscard]] MP_UNITS_CONSTEVAL Unit auto operator*(M, const U u) +[[nodiscard]] MP_UNITS_CONSTEVAL Unit auto operator*(M, U u) { - if constexpr (std::is_same_v)>>) + if constexpr (std::is_same_v)>>) return u; else return scaled_unit{}; @@ -444,7 +446,7 @@ template * Returns the result of multiplication with an inverse unit. */ template -[[nodiscard]] MP_UNITS_CONSTEVAL Unit auto operator/(M mag, const U u) +[[nodiscard]] MP_UNITS_CONSTEVAL Unit auto operator/(M mag, U u) { return mag * inverse(u); } @@ -571,7 +573,7 @@ template else if constexpr (detail::ratio{Num, Den} == 1) return u; else if constexpr (detail::is_specialization_of_scaled_unit) - return scaled_unit(U::mag), std::remove_const_t(U::reference_unit))>>{}; + return scaled_unit(U::mag), decltype(pow(U::reference_unit))>{}; else if constexpr (detail::is_specialization_of_derived_unit) return detail::expr_pow(u); else if constexpr (Den == 1) @@ -657,7 +659,7 @@ template return u1; else { constexpr auto cm = detail::common_magnitude(canonical_lhs.mag, canonical_rhs.mag); - return scaled_unit>{}; + return scaled_unit{}; } } } @@ -718,8 +720,8 @@ constexpr Out print_separator(Out out, const unit_symbol_formatting& fmt) { if (fmt.separator == unit_symbol_separator::half_high_dot) { if (fmt.encoding != text_encoding::unicode) - throw std::invalid_argument( - "'unit_symbol_separator::half_high_dot' can be only used with 'text_encoding::unicode'"); + MP_UNITS_THROW( + std::invalid_argument("'unit_symbol_separator::half_high_dot' can be only used with 'text_encoding::unicode'")); const std::string_view dot = "⋅"; out = detail::copy(dot.begin(), dot.end(), out); } else { @@ -808,7 +810,8 @@ template Out, typename... Expr> constexpr Out unit_symbol_impl(Out out, const derived_unit&, const unit_symbol_formatting& fmt, bool negative_power) { - gsl_Expects(negative_power == false); + (void)negative_power; + MP_UNITS_EXPECTS(negative_power == false); return unit_symbol_impl(out, typename derived_unit::_num_{}, typename derived_unit::_den_{}, fmt); } @@ -843,9 +846,15 @@ MP_UNITS_EXPORT template buffer; unit_symbol_to(std::back_inserter(buffer), U{}, fmt); return buffer.size(); +#else + std::array buffer; // TODO unsafe + auto end = unit_symbol_to(buffer.begin(), U{}, fmt); + return end - buffer.begin(); +#endif }; #if MP_UNITS_API_STRING_VIEW_RET // Permitting static constexpr variables in constexpr functions @@ -854,8 +863,7 @@ MP_UNITS_EXPORT template(U{}); - return basic_fixed_string(buffer.begin(), buffer.end()); + return basic_fixed_string(std::from_range, detail::get_symbol_buffer(U{})); #endif } diff --git a/src/core/include/mp-units/framework/unit_concepts.h b/src/core/include/mp-units/framework/unit_concepts.h index c536e0b46..02a00c7a4 100644 --- a/src/core/include/mp-units/framework/unit_concepts.h +++ b/src/core/include/mp-units/framework/unit_concepts.h @@ -190,7 +190,7 @@ concept AssociatedUnit = Unit && detail::has_associated_quantity(U{}); */ MP_UNITS_EXPORT template concept UnitOf = - AssociatedUnit && QuantitySpec> && + AssociatedUnit && QuantitySpec && implicitly_convertible(get_quantity_spec(U{}), QS) && // the below is to make `dimensionless[radian]` invalid (get_kind(QS) == get_kind(get_quantity_spec(U{})) || !detail::NestedQuantityKindSpecOf); @@ -209,7 +209,7 @@ namespace detail { */ MP_UNITS_EXPORT template concept UnitCompatibleWith = - Unit && Unit> && QuantitySpec> && + Unit && Unit && QuantitySpec && (!AssociatedUnit || UnitOf)&&detail::have_same_canonical_reference_unit(U{}, U2); diff --git a/src/core/include/mp-units/math.h b/src/core/include/mp-units/math.h index e6957201a..50dd222ab 100644 --- a/src/core/include/mp-units/math.h +++ b/src/core/include/mp-units/math.h @@ -22,6 +22,8 @@ #pragma once +#include +// #include #include #include @@ -237,6 +239,29 @@ template common_reference(R * S, T)}; } +/** + * @brief Computes the fma of 2 quantities and a quantity point + * + * @param a: Multiplicand + * @param x: Multiplicand + * @param b: Addend + * @return QuantityPoint: The nearest floating point representable to ax+b + */ +template + requires requires { common_quantity_spec(get_quantity_spec(R) * get_quantity_spec(S), get_quantity_spec(T)); } && + (get_unit(R) * get_unit(S) == get_unit(T)) && requires(Rep1 v1, Rep2 v2, Rep3 v3) { + requires requires { fma(v1, v2, v3); } || requires { std::fma(v1, v2, v3); }; + } +[[nodiscard]] constexpr QuantityPointOf< + common_quantity_spec(get_quantity_spec(R) * get_quantity_spec(S), + get_quantity_spec(T))> auto fma(const quantity& a, const quantity& x, + const quantity_point& b) noexcept +{ + using std::fma; + return Origin + quantity{fma(a.numerical_value_ref_in(a.unit), x.numerical_value_ref_in(x.unit), + b.quantity_ref_from(b.point_origin).numerical_value_ref_in(b.unit)), + common_reference(R * S, T)}; +} /** * @brief Computes the floating-point remainder of the division operation x / y. diff --git a/src/core/include/mp-units/ostream.h b/src/core/include/mp-units/ostream.h index e03bb33c0..fbf7d575a 100644 --- a/src/core/include/mp-units/ostream.h +++ b/src/core/include/mp-units/ostream.h @@ -23,6 +23,8 @@ #pragma once +#include +// #include #include #include diff --git a/src/core/mp-units-core.cpp b/src/core/mp-units-core.cpp index f0278d293..e14347a80 100644 --- a/src/core/mp-units-core.cpp +++ b/src/core/mp-units-core.cpp @@ -8,8 +8,11 @@ export module mp_units.core; #include #include -#include #include + +#if MP_UNITS_HOSTED +#include #include #include #include +#endif diff --git a/src/mp-unitsConfig.cmake b/src/mp-unitsConfig.cmake index b42e3b558..f5bee933d 100644 --- a/src/mp-unitsConfig.cmake +++ b/src/mp-unitsConfig.cmake @@ -22,10 +22,14 @@ include(CMakeFindDependencyMacro) -if(NOT MP_UNITS_API_STD_FORMAT) +if(NOT MP_UNITS_API_FREESTANDING AND NOT MP_UNITS_API_STD_FORMAT) find_dependency(fmt) endif() -find_dependency(gsl-lite) +if(MP_UNITS_API_CONTRACTS STREQUAL "GSL-LITE") + find_dependency(gsl-lite) +elseif(MP_UNITS_API_CONTRACTS STREQUAL "MS-GSL") + find_dependency(Microsoft.GSL) +endif() include("${CMAKE_CURRENT_LIST_DIR}/mp-unitsTargets.cmake") diff --git a/src/systems/CMakeLists.txt b/src/systems/CMakeLists.txt index 3f9020532..0cac88729 100644 --- a/src/systems/CMakeLists.txt +++ b/src/systems/CMakeLists.txt @@ -25,8 +25,7 @@ cmake_minimum_required(VERSION 3.23) add_mp_units_module( systems mp-units-systems DEPENDENCIES mp-units::core - HEADERS include/mp-units/systems/angular/math.h - include/mp-units/systems/angular/units.h + HEADERS include/mp-units/systems/angular/units.h include/mp-units/systems/iec80000/binary_prefixes.h include/mp-units/systems/iec80000/quantities.h include/mp-units/systems/iec80000/unit_symbols.h @@ -39,9 +38,7 @@ add_mp_units_module( include/mp-units/systems/isq/si_quantities.h include/mp-units/systems/isq/space_and_time.h include/mp-units/systems/isq/thermodynamics.h - include/mp-units/systems/si/chrono.h include/mp-units/systems/si/constants.h - include/mp-units/systems/si/math.h include/mp-units/systems/si/prefixes.h include/mp-units/systems/si/unit_symbols.h include/mp-units/systems/si/units.h @@ -60,3 +57,17 @@ add_mp_units_module( include/mp-units/systems/usc.h MODULE_INTERFACE_UNIT mp-units-systems.cpp ) + +if(NOT ${projectPrefix}API_FREESTANDING) + target_sources( + mp-units-systems + PUBLIC FILE_SET + HEADERS + BASE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/include + FILES + include/mp-units/systems/angular/math.h + include/mp-units/systems/si/math.h + include/mp-units/systems/si/chrono.h + ) +endif() diff --git a/src/systems/include/mp-units/systems/angular.h b/src/systems/include/mp-units/systems/angular.h index 326ea2274..41e30bb9c 100644 --- a/src/systems/include/mp-units/systems/angular.h +++ b/src/systems/include/mp-units/systems/angular.h @@ -23,7 +23,9 @@ #pragma once // IWYU pragma: begin_exports +#if MP_UNITS_HOSTED #include +#endif #include #ifndef MP_UNITS_IN_MODULE_INTERFACE diff --git a/src/systems/include/mp-units/systems/angular/math.h b/src/systems/include/mp-units/systems/angular/math.h index 00e03b7aa..6dc96e860 100644 --- a/src/systems/include/mp-units/systems/angular/math.h +++ b/src/systems/include/mp-units/systems/angular/math.h @@ -22,6 +22,8 @@ #pragma once +#include +// #include #include diff --git a/src/systems/include/mp-units/systems/iec80000/binary_prefixes.h b/src/systems/include/mp-units/systems/iec80000/binary_prefixes.h index bfcc9b4f6..1f2e96603 100644 --- a/src/systems/include/mp-units/systems/iec80000/binary_prefixes.h +++ b/src/systems/include/mp-units/systems/iec80000/binary_prefixes.h @@ -42,14 +42,14 @@ template struct yobi_ : prefixed_unit<"Yi", mag_power<2, 80>, MP_UNITS_EXPORT_BEGIN -template inline constexpr kibi_> kibi; -template inline constexpr mebi_> mebi; -template inline constexpr gibi_> gibi; -template inline constexpr tebi_> tebi; -template inline constexpr pebi_> pebi; -template inline constexpr exbi_> exbi; -template inline constexpr zebi_> zebi; -template inline constexpr yobi_> yobi; +template inline constexpr kibi_ kibi; +template inline constexpr mebi_ mebi; +template inline constexpr gibi_ gibi; +template inline constexpr tebi_ tebi; +template inline constexpr pebi_ pebi; +template inline constexpr exbi_ exbi; +template inline constexpr zebi_ zebi; +template inline constexpr yobi_ yobi; // clang-format on MP_UNITS_EXPORT_END diff --git a/src/systems/include/mp-units/systems/si.h b/src/systems/include/mp-units/systems/si.h index b9b374d75..95f9fc874 100644 --- a/src/systems/include/mp-units/systems/si.h +++ b/src/systems/include/mp-units/systems/si.h @@ -23,9 +23,11 @@ #pragma once // IWYU pragma: begin_exports +#if MP_UNITS_HOSTED #include -#include #include +#endif +#include #include #include #include diff --git a/src/systems/include/mp-units/systems/si/chrono.h b/src/systems/include/mp-units/systems/si/chrono.h index e0e5422d8..2ddd71a15 100644 --- a/src/systems/include/mp-units/systems/si/chrono.h +++ b/src/systems/include/mp-units/systems/si/chrono.h @@ -22,6 +22,8 @@ #pragma once +#include +// #include #include #include diff --git a/src/systems/include/mp-units/systems/si/math.h b/src/systems/include/mp-units/systems/si/math.h index 400a5a25a..590a14eaf 100644 --- a/src/systems/include/mp-units/systems/si/math.h +++ b/src/systems/include/mp-units/systems/si/math.h @@ -22,6 +22,8 @@ #pragma once +#include +// #include #include #include diff --git a/src/systems/include/mp-units/systems/si/prefixes.h b/src/systems/include/mp-units/systems/si/prefixes.h index da2fb5b7a..0aac147b9 100644 --- a/src/systems/include/mp-units/systems/si/prefixes.h +++ b/src/systems/include/mp-units/systems/si/prefixes.h @@ -58,30 +58,30 @@ template struct quetta_ : prefixed_unit<"Q", mag_power<10, 30> MP_UNITS_EXPORT_BEGIN -template inline constexpr quecto_> quecto; -template inline constexpr ronto_> ronto; -template inline constexpr yocto_> yocto; -template inline constexpr zepto_> zepto; -template inline constexpr atto_> atto; -template inline constexpr femto_> femto; -template inline constexpr pico_> pico; -template inline constexpr nano_> nano; -template inline constexpr micro_> micro; -template inline constexpr milli_> milli; -template inline constexpr centi_> centi; -template inline constexpr deci_> deci; -template inline constexpr deca_> deca; -template inline constexpr hecto_> hecto; -template inline constexpr kilo_> kilo; -template inline constexpr mega_> mega; -template inline constexpr giga_> giga; -template inline constexpr tera_> tera; -template inline constexpr peta_> peta; -template inline constexpr exa_> exa; -template inline constexpr zetta_> zetta; -template inline constexpr yotta_> yotta; -template inline constexpr ronna_> ronna; -template inline constexpr quetta_> quetta; +template inline constexpr quecto_ quecto; +template inline constexpr ronto_ ronto; +template inline constexpr yocto_ yocto; +template inline constexpr zepto_ zepto; +template inline constexpr atto_ atto; +template inline constexpr femto_ femto; +template inline constexpr pico_ pico; +template inline constexpr nano_ nano; +template inline constexpr micro_ micro; +template inline constexpr milli_ milli; +template inline constexpr centi_ centi; +template inline constexpr deci_ deci; +template inline constexpr deca_ deca; +template inline constexpr hecto_ hecto; +template inline constexpr kilo_ kilo; +template inline constexpr mega_ mega; +template inline constexpr giga_ giga; +template inline constexpr tera_ tera; +template inline constexpr peta_ peta; +template inline constexpr exa_ exa; +template inline constexpr zetta_ zetta; +template inline constexpr yotta_ yotta; +template inline constexpr ronna_ ronna; +template inline constexpr quetta_ quetta; // clang-format on MP_UNITS_EXPORT_END diff --git a/src/systems/include/mp-units/systems/si/unit_symbols.h b/src/systems/include/mp-units/systems/si/unit_symbols.h index 8477bbb2e..060bfb93f 100644 --- a/src/systems/include/mp-units/systems/si/unit_symbols.h +++ b/src/systems/include/mp-units/systems/si/unit_symbols.h @@ -474,6 +474,32 @@ inline constexpr auto YF = yotta; inline constexpr auto RF = ronna; inline constexpr auto QF = quetta; +inline constexpr auto qohm = quecto; +inline constexpr auto rohm = ronto; +inline constexpr auto yohm = yocto; +inline constexpr auto zohm = zepto; +inline constexpr auto aohm = atto; +inline constexpr auto fohm = femto; +inline constexpr auto pohm = pico; +inline constexpr auto nohm = nano; +inline constexpr auto uohm = micro; +inline constexpr auto mohm = milli; +inline constexpr auto cohm = centi; +inline constexpr auto dohm = deci; +using si::ohm; +inline constexpr auto daohm = deca; +inline constexpr auto hohm = hecto; +inline constexpr auto kohm = kilo; +inline constexpr auto Mohm = mega; +inline constexpr auto Gohm = giga; +inline constexpr auto Tohm = tera; +inline constexpr auto Pohm = peta; +inline constexpr auto Eohm = exa; +inline constexpr auto Zohm = zetta; +inline constexpr auto Yohm = yotta; +inline constexpr auto Rohm = ronna; +inline constexpr auto Qohm = quetta; + inline constexpr auto qS = quecto; inline constexpr auto rS = ronto; inline constexpr auto yS = yocto; diff --git a/src/systems/include/mp-units/systems/usc.h b/src/systems/include/mp-units/systems/usc.h index 9a13c702b..cfa2eb76d 100644 --- a/src/systems/include/mp-units/systems/usc.h +++ b/src/systems/include/mp-units/systems/usc.h @@ -118,7 +118,7 @@ inline constexpr struct troy_pound : named_unit<"lb t", mag<12> * troy_once> {} inline constexpr struct inch_of_mercury : named_unit<"inHg", mag_ratio<3'386'389, 1'000> * si::pascal> {} inch_of_mercury; // https://en.wikipedia.org/wiki/United_States_customary_units#Temperature -inline constexpr struct zeroth_degree_Fahrenheit : relative_point_origin * si::degree_Celsius)> {} zeroth_degree_Fahrenheit; +inline constexpr struct zeroth_degree_Fahrenheit : relative_point_origin * si::degree_Celsius)}> {} zeroth_degree_Fahrenheit; inline constexpr struct degree_Fahrenheit : named_unit * si::degree_Celsius, zeroth_degree_Fahrenheit> {} degree_Fahrenheit; // clang-format on diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8430bfa4a..cdc5120da 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -22,5 +22,7 @@ cmake_minimum_required(VERSION 3.5) -add_subdirectory(runtime) +if(NOT ${projectPrefix}API_FREESTANDING) + add_subdirectory(runtime) +endif() add_subdirectory(static) diff --git a/test/runtime/CMakeLists.txt b/test/runtime/CMakeLists.txt index f78c0ad4f..fcd2cca81 100644 --- a/test/runtime/CMakeLists.txt +++ b/test/runtime/CMakeLists.txt @@ -24,7 +24,9 @@ cmake_minimum_required(VERSION 3.5) find_package(Catch2 3 REQUIRED) -add_executable(unit_tests_runtime distribution_test.cpp fmt_test.cpp math_test.cpp atomic_test.cpp) +add_executable( + unit_tests_runtime distribution_test.cpp fixed_string_test.cpp fmt_test.cpp math_test.cpp atomic_test.cpp +) if(${projectPrefix}BUILD_CXX_MODULES) target_compile_definitions(unit_tests_runtime PUBLIC ${projectPrefix}MODULES) endif() diff --git a/test/runtime/fixed_string_test.cpp b/test/runtime/fixed_string_test.cpp new file mode 100644 index 000000000..7efae3df2 --- /dev/null +++ b/test/runtime/fixed_string_test.cpp @@ -0,0 +1,68 @@ +// The MIT License (MIT) +// +// Copyright (c) 2018 Mateusz Pusz +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include +#include +#include +#include +#include +#ifdef MP_UNITS_MODULES +import mp_units; +#else +#include +#endif + +using namespace mp_units; + +TEST_CASE("fixed_string::at", "[fixed_string]") +{ + basic_fixed_string txt = "abc"; + SECTION("in range") + { + CHECK(txt.at(0) == 'a'); + CHECK(txt.at(1) == 'b'); + CHECK(txt.at(2) == 'c'); + } + SECTION("out of range") + { + REQUIRE_THROWS_MATCHES(txt.at(3), std::out_of_range, Catch::Matchers::Message("basic_fixed_string::at")); + REQUIRE_THROWS_MATCHES(txt.at(1024), std::out_of_range, Catch::Matchers::Message("basic_fixed_string::at")); + } +} + +TEST_CASE("fixed_string text output", "[fixed_string][ostream][fmt]") +{ + basic_fixed_string txt = "units"; + SECTION("iostream") + { + std::ostringstream os; + os << txt; + CHECK(os.str() == "units"); + } + SECTION("fmt") { CHECK(MP_UNITS_STD_FMT::format("{}", txt) == "units"); } +} + +TEST_CASE("fixed_string hash", "[fixed_string][hash]") +{ + basic_fixed_string txt = "units"; + CHECK(std::hash>{}(txt) == std::hash{}("units")); +} diff --git a/test/runtime/fmt_test.cpp b/test/runtime/fmt_test.cpp index e8437438d..8c26ee288 100644 --- a/test/runtime/fmt_test.cpp +++ b/test/runtime/fmt_test.cpp @@ -34,7 +34,6 @@ #ifdef MP_UNITS_MODULES import mp_units; #else -#include #include #include // IWYU pragma: keep #include @@ -51,18 +50,6 @@ inline constexpr bool mp_units::is_vector = true; using namespace mp_units; using namespace mp_units::si::unit_symbols; -TEST_CASE("fixed_string", "[text][ostream][fmt]") -{ - basic_fixed_string txt = "units"; - SECTION("iostream") - { - std::ostringstream os; - os << txt; - CHECK(os.str() == "units"); - } - SECTION("fmt") { CHECK(MP_UNITS_STD_FMT::format("{}", txt) == "units"); } -} - TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") { std::ostringstream os; diff --git a/test/static/CMakeLists.txt b/test/static/CMakeLists.txt index 21fd97112..584e7a236 100644 --- a/test/static/CMakeLists.txt +++ b/test/static/CMakeLists.txt @@ -32,7 +32,6 @@ add_library( unit_tests_static angular_test.cpp cgs_test.cpp - chrono_test.cpp compare_test.cpp concepts_test.cpp # custom_rep_test_min_expl.cpp @@ -40,7 +39,6 @@ add_library( dimension_test.cpp dimension_symbol_test.cpp fixed_string_test.cpp - fractional_exponent_quantity.cpp hep_test.cpp iau_test.cpp iec80000_test.cpp @@ -49,7 +47,6 @@ add_library( isq_test.cpp isq_angle_test.cpp # magnitude_test.cpp - math_test.cpp natural_test.cpp prime_test.cpp quantity_point_test.cpp @@ -65,5 +62,10 @@ add_library( usc_test.cpp ) +if(NOT ${projectPrefix}API_FREESTANDING) + target_sources(unit_tests_static PRIVATE chrono_test.cpp fractional_exponent_quantity.cpp math_test.cpp) +endif() + +target_compile_options(unit_tests_static PRIVATE $<$:-Wno-subobject-linkage>) target_link_libraries(unit_tests_static PRIVATE mp-units::mp-units) target_link_libraries(unit_tests_static PRIVATE unit_tests_static_truncating) diff --git a/test/static/concepts_test.cpp b/test/static/concepts_test.cpp index 65762ddbd..68033efa9 100644 --- a/test/static/concepts_test.cpp +++ b/test/static/concepts_test.cpp @@ -23,14 +23,18 @@ #include #include #include +#include +#include +#if MP_UNITS_HOSTED #include #include -#include #include -#include +#endif +#if MP_UNITS_HOSTED template inline constexpr bool mp_units::is_scalar> = true; +#endif namespace { @@ -45,9 +49,9 @@ struct dim_speed : decltype(isq::dim_length / isq::dim_time) {}; // BaseDimension static_assert(detail::BaseDimension); -static_assert(!detail::BaseDimension>); -static_assert(!detail::BaseDimension>); -static_assert(!detail::BaseDimension(isq::dim_length))>>); +static_assert(!detail::BaseDimension); +static_assert(!detail::BaseDimension); +static_assert(!detail::BaseDimension(isq::dim_length))>); static_assert(!detail::BaseDimension>>); static_assert(!detail::BaseDimension); static_assert(!detail::BaseDimension>); @@ -55,9 +59,9 @@ static_assert(!detail::BaseDimension); static_assert(!detail::BaseDimension); // DerivedDimension -static_assert(detail::DerivedDimension>); -static_assert(detail::DerivedDimension>); -static_assert(detail::DerivedDimension(isq::dim_length))>>); +static_assert(detail::DerivedDimension); +static_assert(detail::DerivedDimension); +static_assert(detail::DerivedDimension(isq::dim_length))>); static_assert(detail::DerivedDimension>>); static_assert(detail::DerivedDimension); static_assert(!detail::DerivedDimension); @@ -67,9 +71,9 @@ static_assert(!detail::DerivedDimension); // Dimension static_assert(Dimension); -static_assert(Dimension>); -static_assert(Dimension>); -static_assert(Dimension(isq::dim_length))>>); +static_assert(Dimension); +static_assert(Dimension); +static_assert(Dimension(isq::dim_length))>); static_assert(Dimension>>); static_assert(Dimension); static_assert(!Dimension); @@ -86,9 +90,9 @@ struct speed : decltype(isq::length / isq::time) {}; // this is not recommended static_assert(QuantitySpec); static_assert(QuantitySpec); static_assert(QuantitySpec); -static_assert(QuantitySpec)>>); -static_assert(QuantitySpec>); -static_assert(QuantitySpec(isq::length))>>); +static_assert(QuantitySpec)>); +static_assert(QuantitySpec); +static_assert(QuantitySpec(isq::length))>); static_assert(QuantitySpec); static_assert(!QuantitySpec); static_assert(!QuantitySpec); @@ -99,8 +103,8 @@ static_assert(detail::NamedQuantitySpec); static_assert(detail::NamedQuantitySpec); static_assert(detail::NamedQuantitySpec); static_assert(!detail::NamedQuantitySpec)>>); -static_assert(!detail::NamedQuantitySpec>); -static_assert(!detail::NamedQuantitySpec(isq::length))>>); +static_assert(!detail::NamedQuantitySpec); +static_assert(!detail::NamedQuantitySpec(isq::length))>); static_assert(detail::NamedQuantitySpec); static_assert(!detail::NamedQuantitySpec); static_assert(!detail::NamedQuantitySpec); @@ -109,10 +113,10 @@ static_assert(!detail::NamedQuantitySpec); // IntermediateDerivedQuantitySpec static_assert(!detail::IntermediateDerivedQuantitySpec); static_assert(!detail::IntermediateDerivedQuantitySpec); -static_assert(!detail::IntermediateDerivedQuantitySpec)>>); +static_assert(!detail::IntermediateDerivedQuantitySpec)>); static_assert(!detail::IntermediateDerivedQuantitySpec); -static_assert(detail::IntermediateDerivedQuantitySpec>); -static_assert(detail::IntermediateDerivedQuantitySpec(isq::length))>>); +static_assert(detail::IntermediateDerivedQuantitySpec); +static_assert(detail::IntermediateDerivedQuantitySpec(isq::length))>); static_assert(!detail::IntermediateDerivedQuantitySpec); static_assert(!detail::IntermediateDerivedQuantitySpec); static_assert(!detail::IntermediateDerivedQuantitySpec); @@ -123,8 +127,8 @@ static_assert(!detail::QuantityKindSpec); static_assert(!detail::QuantityKindSpec); static_assert(detail::QuantityKindSpec)>>); static_assert(!detail::QuantityKindSpec); -static_assert(!detail::QuantityKindSpec>); -static_assert(!detail::QuantityKindSpec(isq::length))>>); +static_assert(!detail::QuantityKindSpec); +static_assert(!detail::QuantityKindSpec(isq::length))>); static_assert(!detail::QuantityKindSpec); static_assert(!detail::QuantityKindSpec); static_assert(!detail::QuantityKindSpec); @@ -138,13 +142,13 @@ struct metre_per_second : decltype(si::metre / si::second) {}; static_assert(Unit); static_assert(Unit); -static_assert(Unit)>>); +static_assert(Unit)>); static_assert(Unit); -static_assert(Unit>); -static_assert(Unit>); -static_assert(Unit * si::second)>>); -static_assert(Unit>); -static_assert(Unit(si::metre))>>); +static_assert(Unit); +static_assert(Unit); +static_assert(Unit * si::second)>); +static_assert(Unit); +static_assert(Unit(si::metre))>); static_assert(Unit); static_assert(Unit, struct si::second>>); static_assert(Unit); @@ -157,18 +161,20 @@ static_assert(!Unit>); static_assert(!Unit, si::second>>); static_assert(!Unit); static_assert(!Unit); +#if MP_UNITS_HOSTED static_assert(!Unit); +#endif // NamedUnit static_assert(detail::NamedUnit); static_assert(detail::NamedUnit); static_assert(!detail::NamedUnit); -static_assert(!detail::NamedUnit)>>); -static_assert(!detail::NamedUnit>); -static_assert(!detail::NamedUnit>); -static_assert(!detail::NamedUnit * si::second)>>); -static_assert(!detail::NamedUnit>); -static_assert(!detail::NamedUnit(si::metre))>>); +static_assert(!detail::NamedUnit)>); +static_assert(!detail::NamedUnit); +static_assert(!detail::NamedUnit); +static_assert(!detail::NamedUnit * si::second)>); +static_assert(!detail::NamedUnit); +static_assert(!detail::NamedUnit(si::metre))>); static_assert(detail::NamedUnit); static_assert(!detail::NamedUnit, struct si::second>>); static_assert(!detail::NamedUnit); @@ -181,18 +187,20 @@ static_assert(!detail::NamedUnit>); static_assert(!detail::NamedUnit, si::second>>); static_assert(!detail::NamedUnit); static_assert(!detail::NamedUnit); +#if MP_UNITS_HOSTED static_assert(!detail::NamedUnit); +#endif // PrefixableUnit static_assert(PrefixableUnit); static_assert(PrefixableUnit); static_assert(!PrefixableUnit); -static_assert(!PrefixableUnit)>>); -static_assert(!PrefixableUnit>); -static_assert(!PrefixableUnit>); -static_assert(!PrefixableUnit * si::second)>>); -static_assert(!PrefixableUnit>); -static_assert(!PrefixableUnit(si::metre))>>); +static_assert(!PrefixableUnit)>); +static_assert(!PrefixableUnit); +static_assert(!PrefixableUnit); +static_assert(!PrefixableUnit * si::second)>); +static_assert(!PrefixableUnit); +static_assert(!PrefixableUnit(si::metre))>); static_assert(PrefixableUnit); static_assert(!PrefixableUnit, struct si::second>>); static_assert(!PrefixableUnit); @@ -205,18 +213,20 @@ static_assert(!PrefixableUnit>); static_assert(!PrefixableUnit, si::second>>); static_assert(!PrefixableUnit); static_assert(!PrefixableUnit); +#if MP_UNITS_HOSTED static_assert(!PrefixableUnit); +#endif // AssociatedUnit static_assert(AssociatedUnit); static_assert(!AssociatedUnit); static_assert(AssociatedUnit); -static_assert(AssociatedUnit)>>); -static_assert(AssociatedUnit>); -static_assert(AssociatedUnit>); -static_assert(AssociatedUnit * si::second)>>); -static_assert(AssociatedUnit>); -static_assert(AssociatedUnit(si::metre))>>); +static_assert(AssociatedUnit)>); +static_assert(AssociatedUnit); +static_assert(AssociatedUnit); +static_assert(AssociatedUnit * si::second)>); +static_assert(AssociatedUnit); +static_assert(AssociatedUnit(si::metre))>); static_assert(AssociatedUnit); static_assert(AssociatedUnit, struct si::second>>); static_assert(AssociatedUnit); @@ -229,7 +239,9 @@ static_assert(!AssociatedUnit>); static_assert(!AssociatedUnit, si::second>>); static_assert(!AssociatedUnit); static_assert(!AssociatedUnit); +#if MP_UNITS_HOSTED static_assert(!AssociatedUnit); +#endif // UnitOf static_assert(UnitOf); @@ -248,13 +260,13 @@ static_assert(!UnitOf); // Reference static_assert(Reference); -static_assert(Reference>); -static_assert(Reference>); -static_assert(Reference>); -static_assert(Reference>); +static_assert(Reference); +static_assert(Reference); +static_assert(Reference); +static_assert(Reference); static_assert(!Reference); static_assert(!Reference); -static_assert(!Reference)>>); +static_assert(!Reference)>); static_assert(!Reference); static_assert(!Reference); @@ -262,51 +274,57 @@ static_assert(!Reference); static_assert(ReferenceOf); static_assert(ReferenceOf); static_assert(!ReferenceOf); -static_assert(ReferenceOf, isq::length>); -static_assert(!ReferenceOf, isq::radius>); -static_assert(ReferenceOf, isq::length>); -static_assert(ReferenceOf, isq::radius>); +static_assert(ReferenceOf); +static_assert(!ReferenceOf); +static_assert(ReferenceOf); +static_assert(ReferenceOf); static_assert(!ReferenceOf); static_assert(ReferenceOf); -static_assert(ReferenceOf, dimensionless>); -static_assert(ReferenceOf, isq::rotation>); -static_assert(ReferenceOf, dimensionless>); +static_assert(ReferenceOf); +static_assert(ReferenceOf); +static_assert(ReferenceOf); static_assert(ReferenceOf); static_assert(!ReferenceOf); -static_assert(ReferenceOf, isq::angular_measure>); -static_assert(!ReferenceOf, dimensionless>); +static_assert(ReferenceOf); +static_assert(!ReferenceOf); static_assert(ReferenceOf); static_assert(ReferenceOf); -static_assert(!ReferenceOf, isq::rotation>); -static_assert(!ReferenceOf, isq::angular_measure>); +static_assert(!ReferenceOf); +static_assert(!ReferenceOf); // Representation static_assert(Representation); static_assert(Representation); -static_assert(Representation>); static_assert(!Representation); static_assert(!Representation>); -static_assert(!Representation); +#if MP_UNITS_HOSTED +static_assert(Representation>); static_assert(!Representation); +static_assert(!Representation); +#endif // RepresentationOf static_assert(RepresentationOf); static_assert(RepresentationOf); -static_assert(RepresentationOf, quantity_character::scalar>); static_assert(!RepresentationOf); static_assert(!RepresentationOf, quantity_character::scalar>); +#if MP_UNITS_HOSTED +static_assert(RepresentationOf, quantity_character::scalar>); static_assert(!RepresentationOf); static_assert(!RepresentationOf); +#endif // Quantity static_assert(Quantity>); static_assert(Quantity>); static_assert(Quantity>); static_assert(Quantity>); +#if MP_UNITS_HOSTED static_assert(!Quantity); +#endif static_assert(!Quantity>); -static_assert(!Quantity>); +static_assert(!Quantity); // QuantityOf static_assert(QuantityOf, isq::length>); @@ -332,8 +350,10 @@ static_assert(!QuantityOf, isq::rotation>); static_assert(!QuantityOf, isq::angular_measure>); // QuantityLike +#if MP_UNITS_HOSTED static_assert(QuantityLike); static_assert(QuantityLike); +#endif static_assert(!QuantityLike>); static_assert(!QuantityLike>); static_assert(!QuantityLike); @@ -345,12 +365,14 @@ static_assert(QuantityPoint>); static_assert(QuantityPoint>); static_assert(QuantityPoint>); static_assert(QuantityPoint>); -static_assert(!QuantityPoint>); +static_assert(!QuantityPoint); static_assert(!QuantityPoint>); static_assert(!QuantityPoint); static_assert(!QuantityPoint); +#if MP_UNITS_HOSTED static_assert(!QuantityPoint); static_assert(!QuantityPoint>); +#endif static_assert(!QuantityPoint); // QuantityPointOf @@ -383,9 +405,11 @@ static_assert(!PointOrigin>); static_assert(!PointOrigin>); static_assert(!PointOrigin>); static_assert(!PointOrigin>); -static_assert(!PointOrigin>); +static_assert(!PointOrigin); +#if MP_UNITS_HOSTED static_assert(!PointOrigin); static_assert(!PointOrigin>); +#endif static_assert(!PointOrigin); // PointOriginFor @@ -407,14 +431,18 @@ static_assert(!PointOriginFor, static_assert(!PointOriginFor, isq::length>); static_assert(!PointOriginFor, isq::radius>); static_assert(!PointOriginFor, isq::time>); -static_assert(!PointOriginFor, isq::length>); +static_assert(!PointOriginFor); +#if MP_UNITS_HOSTED static_assert(!PointOriginFor); static_assert(!PointOriginFor, isq::length>); +#endif static_assert(!PointOriginFor); // QuantityPointLike +#if MP_UNITS_HOSTED static_assert(QuantityPointLike>); static_assert(!QuantityPointLike); +#endif static_assert(!QuantityPointLike>); static_assert(!QuantityPointLike>); static_assert(!QuantityPointLike); diff --git a/test/static/custom_rep_test_min_impl.cpp b/test/static/custom_rep_test_min_impl.cpp index a473f2760..96be35981 100644 --- a/test/static/custom_rep_test_min_impl.cpp +++ b/test/static/custom_rep_test_min_impl.cpp @@ -82,7 +82,7 @@ static_assert(!std::convertible_to, quantity -concept creates_quantity = Unit> && requires { T{} * U; }; +concept creates_quantity = Unit && requires { T{} * U; }; static_assert(creates_quantity, si::metre>); static_assert(creates_quantity, si::metre>); diff --git a/test/static/fixed_string_test.cpp b/test/static/fixed_string_test.cpp index 93890a98e..f0001c796 100644 --- a/test/static/fixed_string_test.cpp +++ b/test/static/fixed_string_test.cpp @@ -21,42 +21,161 @@ // SOFTWARE. #include +#include #include using namespace mp_units; namespace { +constexpr std::array array = {'a', 'b', 'c'}; + +auto from_string = [] { + std::string_view txt = "abc"; + return fixed_string<3>(std::from_range, txt); +}; + +auto from_string_iter = [] { + std::string_view txt = "abc"; + return fixed_string<3>(txt.begin(), txt.end()); +}; + +constexpr fixed_string<0> txt0; constexpr basic_fixed_string txt1('a'); +constexpr basic_fixed_string txt2('a', 'b', 'c'); +constexpr basic_fixed_string txt3 = "abc"; +constexpr fixed_string<3> txt4(array.begin(), array.end()); +constexpr basic_fixed_string txt5(std::from_range, array); +constexpr basic_fixed_string txt6(from_string()); +constexpr basic_fixed_string txt7(from_string_iter()); + +constexpr fixed_string<3> txt8(txt2.begin(), txt2.end()); +constexpr fixed_string<3> txt9(txt2.rbegin(), txt2.rend()); + +static_assert(txt0.size() == 0); static_assert(txt1.size() == 1); +static_assert(txt2.size() == 3); +static_assert(txt3.size() == 3); +static_assert(txt4.size() == 3); +static_assert(txt5.size() == 3); +static_assert(txt6.size() == 3); +static_assert(txt7.size() == 3); +static_assert(txt8.size() == 3); +static_assert(txt9.size() == 3); + +static_assert(txt0.length() == 0); +static_assert(txt1.length() == 1); +static_assert(txt2.length() == 3); + +static_assert(txt0.max_size() == 0); +static_assert(txt1.max_size() == 1); +static_assert(txt2.max_size() == 3); + +static_assert(txt0.empty() == true); +static_assert(txt1.empty() == false); +static_assert(txt2.empty() == false); +static_assert(txt3.empty() == false); +static_assert(txt4.empty() == false); +static_assert(txt5.empty() == false); +static_assert(txt6.empty() == false); +static_assert(txt7.empty() == false); +static_assert(txt8.empty() == false); +static_assert(txt9.empty() == false); + static_assert(txt1[0] == 'a'); +static_assert(txt2[0] == 'a'); +static_assert(txt2[1] == 'b'); +static_assert(txt2[2] == 'c'); +static_assert(txt9[0] == 'c'); +static_assert(txt9[1] == 'b'); +static_assert(txt9[2] == 'a'); + +#if MP_UNITS_HOSTED +static_assert(txt1.at(0) == 'a'); +static_assert(txt2.at(0) == 'a'); +static_assert(txt2.at(1) == 'b'); +static_assert(txt2.at(2) == 'c'); +static_assert(txt9.at(0) == 'c'); +static_assert(txt9.at(1) == 'b'); +static_assert(txt9.at(2) == 'a'); +#endif + +static_assert(txt1.front() == 'a'); +static_assert(txt1.back() == 'a'); +static_assert(txt2.front() == 'a'); +static_assert(txt2.back() == 'c'); +static_assert(txt5.front() == 'a'); +static_assert(txt5.back() == 'c'); +static_assert(txt6.front() == 'a'); +static_assert(txt6.back() == 'c'); +static_assert(txt7.front() == 'a'); +static_assert(txt7.back() == 'c'); +static_assert(txt8.front() == 'a'); +static_assert(txt8.back() == 'c'); +static_assert(txt9.front() == 'c'); +static_assert(txt9.back() == 'a'); + +static_assert(std::string_view(txt0.data()) == ""); +static_assert(std::string_view(txt0.c_str()) == ""); +static_assert(std::string_view(txt1.data()) == "a"); +static_assert(std::string_view(txt1.c_str()) == "a"); +static_assert(std::string_view(txt2.data()) == "abc"); +static_assert(std::string_view(txt2.c_str()) == "abc"); + +static_assert(txt0 == ""); +static_assert("a" == txt1); +static_assert(txt2 == "abc"); +static_assert(txt3 == "abc"); +static_assert(txt4 == "abc"); +static_assert(txt5 == "abc"); +static_assert(txt6 == "abc"); +static_assert(txt7 == "abc"); +static_assert(txt8 == "abc"); +static_assert(txt9 == "cba"); + static_assert(txt1 == basic_fixed_string("a")); static_assert(txt1 != basic_fixed_string("b")); static_assert(txt1 != basic_fixed_string("aa")); static_assert(txt1 < basic_fixed_string("b")); static_assert(txt1 < basic_fixed_string("aa")); -static_assert(txt1 + basic_fixed_string('b') == basic_fixed_string("ab")); -static_assert(basic_fixed_string('b') + txt1 == basic_fixed_string("ba")); -static_assert(txt1 + basic_fixed_string("bc") == basic_fixed_string("abc")); -static_assert(basic_fixed_string("bc") + txt1 == basic_fixed_string("bca")); +static_assert(txt1 == "a"); +static_assert(txt1 != "b"); +static_assert(txt1 != "aa"); +static_assert(txt1 < "b"); +static_assert(txt1 < "aa"); + +static_assert(txt1 + basic_fixed_string('b') == "ab"); +static_assert(basic_fixed_string('b') + txt1 == "ba"); +static_assert(txt1 + basic_fixed_string("bc") == "abc"); +static_assert(basic_fixed_string("bc") + txt1 == "bca"); +static_assert(txt1 + 'b' == "ab"); +static_assert('b' + txt1 == "ba"); +static_assert(txt1 + "bc" == "abc"); +static_assert("bc" + txt1 == "bca"); -constexpr basic_fixed_string txt2("abc"); -static_assert(txt2.size() == 3); -static_assert(txt2[0] == 'a'); -static_assert(txt2[1] == 'b'); -static_assert(txt2[2] == 'c'); static_assert(txt2 == basic_fixed_string("abc")); static_assert(txt2 != basic_fixed_string("cba")); static_assert(txt2 != basic_fixed_string("abcd")); static_assert(txt2 < basic_fixed_string("b")); static_assert(txt2 > basic_fixed_string("aa")); -static_assert(txt2 + basic_fixed_string('d') == basic_fixed_string("abcd")); -static_assert(basic_fixed_string('d') + txt2 == basic_fixed_string("dabc")); -static_assert(txt2 + basic_fixed_string("def") == basic_fixed_string("abcdef")); -static_assert(basic_fixed_string("def") + txt2 == basic_fixed_string("defabc")); +static_assert(txt2 == "abc"); +static_assert(txt2 != "cba"); +static_assert(txt2 != "abcd"); +static_assert(txt2 < "b"); +static_assert(txt2 > "aa"); -#ifndef MP_UNITS_COMP_GCC -static_assert(std::string_view(basic_fixed_string("abcd")).find('c') == 2); -#endif +static_assert(txt2 + basic_fixed_string('d') == "abcd"); +static_assert(basic_fixed_string('d') + txt2 == "dabc"); +static_assert(txt2 + basic_fixed_string("def") == "abcdef"); +static_assert(basic_fixed_string("def") + txt2 == "defabc"); +static_assert(txt2 + 'd' == "abcd"); +static_assert('d' + txt2 == "dabc"); +static_assert(txt2 + "def" == "abcdef"); +static_assert("def" + txt2 == "defabc"); + +static_assert(std::string_view(txt2) == "abc"); +static_assert(txt2.view() == "abc"); +static_assert(std::string_view(txt2).find('b') == 1); +static_assert(txt2.view().find('b') == 1); } // namespace diff --git a/test/static/iau_test.cpp b/test/static/iau_test.cpp index fd33c4bc6..339c71253 100644 --- a/test/static/iau_test.cpp +++ b/test/static/iau_test.cpp @@ -20,7 +20,9 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +#if MP_UNITS_HOSTED #include // IWYU pragma: keep +#endif #include #include #include @@ -42,7 +44,7 @@ static_assert(isq::length(1 * LD) == 384'399 * si::kilo); static_assert(isq::length(1 * ly) == 9'460'730'472'580'800 * si::metre); static_assert(isq::length(10'000'000'000 * A) == 1 * si::metre); -#if __cpp_lib_constexpr_cmath || MP_UNITS_COMP_GCC +#if MP_UNITS_HOSTED && (__cpp_lib_constexpr_cmath || MP_UNITS_COMP_GCC) // TODO Should the below work for `1 * pc`? If yes, how to extent the type and how to convert it to a floating-point // representation for comparison purposes? static_assert(round(isq::length(1.L * pc)) == 30'856'775'814'913'673 * si::metre); diff --git a/test/static/magnitude_test.cpp b/test/static/magnitude_test.cpp index af739affa..f245b0b8d 100644 --- a/test/static/magnitude_test.cpp +++ b/test/static/magnitude_test.cpp @@ -83,7 +83,7 @@ static_assert(Magnitude)>); static_assert(Magnitude); // is_named_magnitude -static_assert(!is_named_magnitude)>>); +static_assert(!is_named_magnitude)>); static_assert(is_named_magnitude); // power_v diff --git a/test/static/quantity_point_test.cpp b/test/static/quantity_point_test.cpp index ef2520d16..096e33310 100644 --- a/test/static/quantity_point_test.cpp +++ b/test/static/quantity_point_test.cpp @@ -26,20 +26,25 @@ #include #include #include -#include #include #include #include #include #include +#if MP_UNITS_HOSTED +#include +#endif namespace { using namespace mp_units; using namespace mp_units::si::unit_symbols; using namespace mp_units::usc::unit_symbols; + +#if MP_UNITS_HOSTED using namespace std::chrono_literals; using sys_seconds = std::chrono::time_point; +#endif inline constexpr struct zeroth_length : absolute_point_origin { } zeroth_length; @@ -444,6 +449,7 @@ static_assert(!std::convertible_to, quantity_point, quantity>); static_assert(!std::convertible_to, quantity_point>); +#if MP_UNITS_HOSTED // quantity-like static_assert(!std::constructible_from, std::chrono::seconds>); static_assert(!std::convertible_to>); @@ -453,7 +459,7 @@ static_assert(!std::convertible_to, std::chrono::seconds>); static_assert(!std::convertible_to>); - +#endif // ---------------------- // explicit point origins @@ -499,6 +505,7 @@ static_assert(!std::convertible_to, quantity_point, quantity>); static_assert(!std::convertible_to, quantity_point>); +#if MP_UNITS_HOSTED // quantity-like static_assert(!std::constructible_from>, std::chrono::seconds>); @@ -516,6 +523,7 @@ static_assert( static_assert( !std::convertible_to>>); +#endif /////////////////////////////////////// @@ -775,6 +783,7 @@ static_assert(!std::constructible_from, quantity_point>); +#if MP_UNITS_HOSTED // quantity-point-like static_assert( std::constructible_from>, sys_seconds>); @@ -786,6 +795,7 @@ static_assert( !std::constructible_from>, sys_seconds>); static_assert( !std::convertible_to>>); +#endif ////////////////////////////////// @@ -889,6 +899,7 @@ static_assert(std::is_same_v); +#if MP_UNITS_HOSTED using namespace std::chrono_literals; static_assert(std::is_same_v); static_assert(std::is_same_v, @@ -897,6 +908,7 @@ static_assert(std::is_same_v>); static_assert(quantity_point{sys_seconds{24h}}.unit == si::second); static_assert(quantity_point{sys_seconds{24h}}.quantity_spec == kind_of); +#endif //////////// diff --git a/test/static/quantity_spec_test.cpp b/test/static/quantity_spec_test.cpp index 49a2d87ae..7a62e5c4d 100644 --- a/test/static/quantity_spec_test.cpp +++ b/test/static/quantity_spec_test.cpp @@ -121,10 +121,10 @@ static_assert(!detail::NamedQuantitySpec); static_assert(detail::IntermediateDerivedQuantitySpec); static_assert(!detail::QuantityKindSpec); -static_assert(QuantitySpec>>); -static_assert(!detail::NamedQuantitySpec>>); -static_assert(detail::IntermediateDerivedQuantitySpec>>); -static_assert(detail::QuantityKindSpec>>); +static_assert(QuantitySpec>); +static_assert(!detail::NamedQuantitySpec>); +static_assert(detail::IntermediateDerivedQuantitySpec>); +static_assert(detail::QuantityKindSpec>); static_assert(QuantitySpec / kind_of