From 866550eefc3ad5cd6fca90d587537468543d291d Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Tue, 10 Sep 2024 21:00:30 +0200 Subject: [PATCH 01/15] docs: MSVC compilation announced and its bugs listed on the compiler's conformance page --- docs/getting_started/cpp_compiler_support.md | 31 +++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/docs/getting_started/cpp_compiler_support.md b/docs/getting_started/cpp_compiler_support.md index 006b62f74..c62e03939 100644 --- a/docs/getting_started/cpp_compiler_support.md +++ b/docs/getting_started/cpp_compiler_support.md @@ -13,14 +13,29 @@ 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 | None | 17 | None | None | -| **`import std;`** | 23 | None | 18 | 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 | 194 :bug:{ title="BEWARE of MSVC Bugs!" } | +| **`std::format`** | 20 | 13 | 17 | None | 194 | +| **C++ modules** | 20 | None | 17 | None | None | +| **`import std;`** | 23 | None | 18 | None | None | +| **Static `constexpr` variables in `constexpr` functions** | 23 | 13 | 17 | None | None | +| **Explicit `this` parameter** | 23 | 14 | 18 | None | None | + +??? note "MSVC bugs" + + MSVC still has a poor C++20 conformance. We had to make many workarounds to our codebase to + make it compile on this compiler. Usage of such nasty preprocessor macros degrade the + readability and maintainability of our code. This is why we've applied those patches to + the main library code but not to unit tests and examples. Those still do not compile on MSVC. + + Here is a list of the most important MSVC bugs: + + - [Discrepancy in Behavior of operator*= and operator* for Multiplying int and double at compile time](https://developercommunity.visualstudio.com/t/Discrepancy-in-Behavior-of-operator-an/10732445) + - [Syntax error when using non-type template parameters in templated class member function](https://developercommunity.visualstudio.com/t/Syntax-error-when-using-non-type-templat/10729428) + - [Type always preferred over value when using qualified identifiers](https://developercommunity.visualstudio.com/t/Type-always-prefered-over-value-when-usi/10729382) + + Please upvote them so they get a higher fixing priority at Microsoft. !!! important From 93f41cf49988da3b210f2bfb606411dbcd10d6d1 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Fri, 13 Sep 2024 10:48:46 +0200 Subject: [PATCH 02/15] docs: Compiler Explorer link to basic operations example updated --- README.md | 2 +- docs/getting_started/look_and_feel.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3e4ea88f1..ab2e8771b 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ static_assert(10 * km / (5 * km) == 2 * one); static_assert(1000 / (1 * s) == 1 * kHz); ``` -_Try it on the [Compiler Explorer](https://godbolt.org/z/8acPeq743)._ +_Try it on the [Compiler Explorer](https://godbolt.org/z/fT1r4sohs)._ This library heavily uses C++20 features (concepts, classes as NTTPs, ...). Thanks to them the user gets a powerful but still easy to use interfaces and all unit conversions diff --git a/docs/getting_started/look_and_feel.md b/docs/getting_started/look_and_feel.md index 1c1050e5f..42cf62054 100644 --- a/docs/getting_started/look_and_feel.md +++ b/docs/getting_started/look_and_feel.md @@ -56,7 +56,7 @@ Here is a small example of operations possible on scalar quantities: static_assert(1000 / (1 * s) == 1 * kHz); ``` -!!! example "[Try it on Compiler Explorer](https://godbolt.org/z/8acPeq743)" +!!! example "[Try it on Compiler Explorer](https://godbolt.org/z/fT1r4sohs)" This library requires some C++20 features ([concepts and constraints](https://en.cppreference.com/w/cpp/language/constraints), From ae816a97fcef0d793f91ad4097a6fc7655c8b2f1 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Fri, 13 Sep 2024 17:07:03 +0200 Subject: [PATCH 03/15] docs: "Unit symbols" chapter added --- .../framework_basics/systems_of_units.md | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/docs/users_guide/framework_basics/systems_of_units.md b/docs/users_guide/framework_basics/systems_of_units.md index 39254c9b9..a45e82004 100644 --- a/docs/users_guide/framework_basics/systems_of_units.md +++ b/docs/users_guide/framework_basics/systems_of_units.md @@ -190,3 +190,70 @@ inline constexpr struct mag_pi final : magnitude ```cpp inline constexpr struct degree final : named_unit<{u8"°", "deg"}, mag_pi / mag<180> * si::radian> {} degree; ``` + + +## Unit symbols + +Units are available via their full names or through their short symbols. +To use a long version, it is enough to type: + +```cpp +quantity q1 = 42 * si::metre / si::second; +quantity q2 = 42 * si::kilo / si::hour; +``` + +To simplify how we spell it a short, user-friendly symbols are provided in a dedicated +subnamespace in systems definitions: + +```cpp +namespace si::unit_symbols { + +constexpr auto m = si::metre; +constexpr auto km = si::kilo; +constexpr auto s = si::second; +constexpr auto h = si::hour; + +} +``` + +Unit symbols introduce a lot of short identifiers into the current namespace. This is why they +are opt-in. A user has to explicitly "import" them from a dedicated `unit_symbols` namespace: + +=== "using-declaration" + + ```cpp + using namespace si::unit_symbols; + + quantity q1 = 42 * m / s; + quantity q2 = 42 * km / h; + ``` + +=== "using-directive" + + ```cpp + using si::unit_symbols::m; + using si::unit_symbols::km; + using si::unit_symbols::s; + using si::unit_symbols::h; + + quantity q1 = 42 * m / s; + quantity q2 = 42 * km / h; + ``` + +We also provide alternative object identifiers using Unicode characters in their names for most +unit symbols. The code using Unicode looks nicer, but it is harder to type on the keyboard. +This is why we provide both versions of identifiers for such units. + +=== "ASCII only" + + ```cpp + quantity resistance = 60 * kohm; + quantity capacitance = 100 * uF; + ``` + +=== "With Unicode glyphs" + + ```cpp + quantity resistance = 60 * kΩ; + quantity capacitance = 100 * µF; + ``` From 0670fbdd9f1e82b985ab06a468ea5e6936b55ee1 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Fri, 13 Sep 2024 21:38:59 +0200 Subject: [PATCH 04/15] feat: `value_cast()` complementary conversion function added --- .../framework_basics/value_conversions.md | 16 ++++++++-------- .../include/mp-units/framework/value_cast.h | 18 ++++++++++++++++++ test/static/quantity_point_test.cpp | 1 + test/static/quantity_test.cpp | 1 + 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/docs/users_guide/framework_basics/value_conversions.md b/docs/users_guide/framework_basics/value_conversions.md index 1ec7e340a..96181fd21 100644 --- a/docs/users_guide/framework_basics/value_conversions.md +++ b/docs/users_guide/framework_basics/value_conversions.md @@ -182,11 +182,11 @@ which may well be outside the range of one or both quantity types. The table below provides all the value conversions functions that may be run on `x` being the instance of either `quantity` or `quantity_point`: -| Forcing | Representation | Unit | Member function | Conversion function | -|:-------:|:--------------:|:----:|--------------------|-----------------------| -| No | Same | `u` | `x.in(u)` | | -| No | `T` | Same | `x.in()` | | -| No | `T` | `u` | `x.in(u)` | | -| Yes | Same | `u` | `x.force_in(u)` | `value_cast(x)` | -| Yes | `T` | Same | `x.force_in()` | `value_cast(x)` | -| Yes | `T` | `u` | `x.force_in(u)` | `value_cast(x)` | +| Forcing | Representation | Unit | Member function | Non-member function | +|:-------:|:--------------:|:----:|--------------------|------------------------------------------------| +| No | Same | `u` | `x.in(u)` | | +| No | `T` | Same | `x.in()` | | +| No | `T` | `u` | `x.in(u)` | | +| Yes | Same | `u` | `x.force_in(u)` | `value_cast(x)` | +| Yes | `T` | Same | `x.force_in()` | `value_cast(x)` | +| Yes | `T` | `u` | `x.force_in(u)` | `value_cast(x)` or `value_cast(x)` | diff --git a/src/core/include/mp-units/framework/value_cast.h b/src/core/include/mp-units/framework/value_cast.h index d5ebf69e9..5fe643735 100644 --- a/src/core/include/mp-units/framework/value_cast.h +++ b/src/core/include/mp-units/framework/value_cast.h @@ -88,6 +88,14 @@ template>(std::forward(q)); } +template> + requires(convertible(Q::reference, ToU)) && RepresentationOf && + std::constructible_from +[[nodiscard]] constexpr Quantity auto value_cast(FwdQ&& q) +{ + return detail::sudo_cast>(std::forward(q)); +} + /** * @brief Explicit cast of a quantity's representation @@ -168,6 +176,16 @@ template> + requires(convertible(QP::reference, ToU)) && RepresentationOf && + std::constructible_from +[[nodiscard]] constexpr QuantityPoint auto value_cast(FwdQP&& qp) +{ + return quantity_point{ + value_cast(std::forward(qp).quantity_from_origin_is_an_implementation_detail_), + QP::point_origin}; +} + /** * @brief Explicit cast of a quantity point's representation * diff --git a/test/static/quantity_point_test.cpp b/test/static/quantity_point_test.cpp index f04e9c1ad..2a86878a5 100644 --- a/test/static/quantity_point_test.cpp +++ b/test/static/quantity_point_test.cpp @@ -1725,6 +1725,7 @@ constexpr quantity_point lvalue_qp{2 * km}; static_assert(value_cast(lvalue_qp).quantity_from_zero().numerical_value_in(m) == 2000); static_assert(value_cast(lvalue_qp).quantity_from_zero().numerical_value_in(km) == 2.f); static_assert(value_cast(lvalue_qp).quantity_from_zero().numerical_value_in(m) == 2000.f); +static_assert(value_cast(lvalue_qp).quantity_from_zero().numerical_value_in(m) == 2000.f); } // namespace lvalue_tests static_assert(value_cast>(quantity_point{2000 * m}).quantity_from_zero().numerical_value_in(km) == 2); diff --git a/test/static/quantity_test.cpp b/test/static/quantity_test.cpp index 89f169bbf..6df1602a8 100644 --- a/test/static/quantity_test.cpp +++ b/test/static/quantity_test.cpp @@ -1014,6 +1014,7 @@ static_assert(value_cast(2000.0 * m / (3600.0 * s)).numerical_value_in(k static_assert(value_cast(1.23 * m).numerical_value_in(m) == 1); static_assert(value_cast(1.23 * m).numerical_value_in(km) == 0); +static_assert(value_cast(1.23 * m).numerical_value_in(km) == 0); static_assert((2 * km).force_in(m).numerical_value_in(m) == 2000); static_assert((2000 * m).force_in(km).numerical_value_in(km) == 2); From 9ca56f69115ab8cd0c86fb700e5d496c4e24a0c9 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Sat, 14 Sep 2024 21:37:00 +0200 Subject: [PATCH 05/15] avoid precision loss warning in magnitudes' root; fixes #611 --- src/core/include/mp-units/framework/magnitude.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/core/include/mp-units/framework/magnitude.h b/src/core/include/mp-units/framework/magnitude.h index 71cde3fc0..c865cbdc0 100644 --- a/src/core/include/mp-units/framework/magnitude.h +++ b/src/core/include/mp-units/framework/magnitude.h @@ -380,8 +380,9 @@ template // Always use `long double` for intermediate computations. We don't ever expect people to be // calling this at runtime, so we want maximum accuracy. + long double xld = static_cast(x); long double lo = 1.0; - long double hi = static_cast(x); + long double hi = xld; // Do a binary search to find the closest value such that `checked_int_pow` recovers the input. // @@ -398,7 +399,7 @@ template } // Early return if we get lucky with an exact answer. - if (result.value() == x) { + if (result.value() == xld) { return static_cast(mid); } @@ -408,7 +409,7 @@ template } // Preserve the invariant that `checked_int_pow(lo, n) < x < checked_int_pow(hi, n)`. - if (result.value() < x) { + if (result.value() < xld) { lo = mid; } else { hi = mid; @@ -416,8 +417,8 @@ template } // Pick whichever one gets closer to the target. - const auto lo_diff = x - checked_int_pow(lo, n).value(); - const auto hi_diff = checked_int_pow(hi, n).value() - x; + const auto lo_diff = xld - checked_int_pow(lo, n).value(); + const auto hi_diff = checked_int_pow(hi, n).value() - xld; return static_cast(lo_diff < hi_diff ? lo : hi); } From bd4a61d518ee518286a73a301a7f6df7714a25b8 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Sun, 15 Sep 2024 17:36:14 +0200 Subject: [PATCH 06/15] added test for upstream clang on macos-14, as an example for an arm64 platform --- .github/workflows/ci-test-package-cmake.yml | 25 ++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-test-package-cmake.yml b/.github/workflows/ci-test-package-cmake.yml index df1cae9f2..979ed1be9 100644 --- a/.github/workflows/ci-test-package-cmake.yml +++ b/.github/workflows/ci-test-package-cmake.yml @@ -136,6 +136,20 @@ jobs: cxx_modules: "False", std_format_support: "True" } + - { + name: "Clang-18 on Apple M1 (arm64)", + os: macos-14, + compiler: + { + type: CLANG, + version: 18, + cc: "/opt/homebrew/opt/llvm@18/bin/clang-18", + cxx: "/opt/homebrew/opt/llvm@18/bin/clang++", + }, + lib: "libc++", + cxx_modules: "False", + std_format_support: "True" + } - { name: "Apple Clang 15", os: macos-14, @@ -188,8 +202,8 @@ jobs: shell: bash run: | sudo apt install -y g++-${{ matrix.config.compiler.version }} - - name: Install Clang - if: matrix.config.compiler.type == 'CLANG' + - name: Install Clang with apt + if: matrix.config.compiler.type == 'CLANG' && matrix.config.os != 'macos-14' shell: bash working-directory: ${{ env.HOME }} run: | @@ -197,8 +211,13 @@ jobs: chmod +x llvm.sh sudo ./llvm.sh ${{ matrix.config.compiler.version }} sudo apt install -y clang-tools-${{ matrix.config.compiler.version }} + - name: Install Clang using homebrew + if: matrix.config.compiler.type == 'CLANG' && matrix.config.os == 'macos-14' + shell: bash + run: | + brew install llvm@18 - name: Install Libc++ - if: matrix.config.compiler.type == 'CLANG' && matrix.config.lib == 'libc++' + if: matrix.config.compiler.type == 'CLANG' && matrix.config.lib == 'libc++' && matrix.config.os != 'macos-14' 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 From 25534c6998a0b79b6f356b7a32c4576d1094db05 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Sun, 15 Sep 2024 18:34:28 +0200 Subject: [PATCH 07/15] also run conan tests on macOS 14 (again, an example for arm64) --- .github/workflows/ci-conan.yml | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-conan.yml b/.github/workflows/ci-conan.yml index 58df08808..380bbae52 100644 --- a/.github/workflows/ci-conan.yml +++ b/.github/workflows/ci-conan.yml @@ -143,6 +143,20 @@ jobs: std_format_support: "True", conan-config: "", } + - { + name: "Clang-18 on Apple M1 (arm64)", + os: macos-14, + compiler: + { + type: CLANG, + version: 18, + cc: "/opt/homebrew/opt/llvm@18/bin/clang-18", + cxx: "/opt/homebrew/opt/llvm@18/bin/clang++", + }, + lib: "libc++", + cxx_modules: "False", + std_format_support: "True" + } - { name: "Apple Clang 15", os: macos-13, @@ -196,8 +210,8 @@ jobs: shell: bash run: | sudo apt install -y g++-${{ matrix.config.compiler.version }} - - name: Install Clang - if: matrix.config.compiler.type == 'CLANG' + - name: Install Clang with apt + if: matrix.config.compiler.type == 'CLANG' && matrix.config.os != 'macos-14' shell: bash working-directory: ${{ env.HOME }} run: | @@ -205,8 +219,13 @@ jobs: chmod +x llvm.sh sudo ./llvm.sh ${{ matrix.config.compiler.version }} sudo apt install -y clang-tools-${{ matrix.config.compiler.version }} + - name: Install Clang using homebrew + if: matrix.config.compiler.type == 'CLANG' && matrix.config.os == 'macos-14' + shell: bash + run: | + brew install llvm@18 - name: Install Libc++ - if: matrix.config.compiler.type == 'CLANG' && matrix.config.lib == 'libc++' + if: matrix.config.compiler.type == 'CLANG' && matrix.config.lib == 'libc++' && matrix.config.os != 'macos-14' 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 From 618b097c42372fbf927e1ce567ed1d9f01fbedef Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Sun, 15 Sep 2024 19:29:40 -0600 Subject: [PATCH 08/15] refactor: `value_cast` now reuses `value_cast` --- src/core/include/mp-units/framework/value_cast.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/core/include/mp-units/framework/value_cast.h b/src/core/include/mp-units/framework/value_cast.h index 5fe643735..020424dcd 100644 --- a/src/core/include/mp-units/framework/value_cast.h +++ b/src/core/include/mp-units/framework/value_cast.h @@ -93,7 +93,7 @@ template [[nodiscard]] constexpr Quantity auto value_cast(FwdQ&& q) { - return detail::sudo_cast>(std::forward(q)); + return value_cast(std::forward(q)); } @@ -181,9 +181,7 @@ template [[nodiscard]] constexpr QuantityPoint auto value_cast(FwdQP&& qp) { - return quantity_point{ - value_cast(std::forward(qp).quantity_from_origin_is_an_implementation_detail_), - QP::point_origin}; + return value_cast(std::forward(qp)); } /** From 78efb77b967457298633e0a25a9c8454d1d4f68e Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Sun, 15 Sep 2024 19:30:13 -0600 Subject: [PATCH 09/15] style: empty leading line removed from `quantity.h` --- src/core/include/mp-units/framework/quantity.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/include/mp-units/framework/quantity.h b/src/core/include/mp-units/framework/quantity.h index 18490e072..3d1b319f8 100644 --- a/src/core/include/mp-units/framework/quantity.h +++ b/src/core/include/mp-units/framework/quantity.h @@ -1,4 +1,3 @@ - // The MIT License (MIT) // // Copyright (c) 2018 Mateusz Pusz From 3e502fb795a93c2f56bf78034c7ff92cc116aeb6 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Mon, 16 Sep 2024 20:34:12 +0200 Subject: [PATCH 10/15] increase tolerance for certain math tests to two epsilon --- test/runtime/almost_equals.h | 9 +++++---- test/runtime/math_test.cpp | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/test/runtime/almost_equals.h b/test/runtime/almost_equals.h index 0cc03e8eb..4c2153a0b 100644 --- a/test/runtime/almost_equals.h +++ b/test/runtime/almost_equals.h @@ -35,7 +35,7 @@ namespace mp_units { template struct AlmostEqualsMatcher : Catch::Matchers::MatcherGenericBase { - explicit AlmostEqualsMatcher(const T& target) : target_{target} {} + explicit AlmostEqualsMatcher(const T& target, int n_eps) : target_{target}, n_eps_{n_eps} {} template U> requires std::same_as @@ -48,7 +48,7 @@ struct AlmostEqualsMatcher : Catch::Matchers::MatcherGenericBase { const auto y = common(other).numerical_value_in(common::unit); if constexpr (treat_as_floating_point) { const auto maxXYOne = std::max({rep{1}, abs(x), abs(y)}); - return abs(x - y) <= std::numeric_limits::epsilon() * maxXYOne; + return abs(x - y) <= (n_eps_ * std::numeric_limits::epsilon()) * maxXYOne; } else { if (x >= 0) { return x - 1 <= y && y - 1 <= x; @@ -71,12 +71,13 @@ struct AlmostEqualsMatcher : Catch::Matchers::MatcherGenericBase { private: const T& target_; + int n_eps_; }; template -AlmostEqualsMatcher AlmostEquals(const T& target) +AlmostEqualsMatcher AlmostEquals(const T& target, int n_eps = 1) { - return AlmostEqualsMatcher{target}; + return AlmostEqualsMatcher{target, n_eps}; } diff --git a/test/runtime/math_test.cpp b/test/runtime/math_test.cpp index 2a59135b8..79018bff8 100644 --- a/test/runtime/math_test.cpp +++ b/test/runtime/math_test.cpp @@ -448,7 +448,7 @@ TEST_CASE("Angle trigonometric functions", "[trig][angle]") REQUIRE_THAT(sin(0 * angle[grad]), AlmostEquals(0. * one)); REQUIRE_THAT(sin(100 * angle[grad]), AlmostEquals(1. * one)); - REQUIRE_THAT(sin(200 * angle[grad]), AlmostEquals(0. * one)); + REQUIRE_THAT(sin(200 * angle[grad]), AlmostEquals(0. * one, 2)); REQUIRE_THAT(sin(300 * angle[grad]), AlmostEquals(-1. * one)); } @@ -475,7 +475,7 @@ TEST_CASE("Angle trigonometric functions", "[trig][angle]") REQUIRE_THAT(tan(0 * angle[grad]), AlmostEquals(0. * one)); REQUIRE_THAT(tan(50 * angle[grad]), AlmostEquals(1. * one)); REQUIRE_THAT(tan(150 * angle[grad]), AlmostEquals(-1. * one)); - REQUIRE_THAT(tan(200 * angle[grad]), AlmostEquals(0. * one)); + REQUIRE_THAT(tan(200 * angle[grad]), AlmostEquals(0. * one, 2)); } } From 7ce8d695b180fc76bceb9a04b9e3b4c08d8b2e79 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Tue, 17 Sep 2024 12:48:13 -0600 Subject: [PATCH 11/15] test: `std::complex`-based quantities tests added --- test/static/quantity_test.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/static/quantity_test.cpp b/test/static/quantity_test.cpp index 6df1602a8..23853b927 100644 --- a/test/static/quantity_test.cpp +++ b/test/static/quantity_test.cpp @@ -37,9 +37,15 @@ import std; #include #if MP_UNITS_HOSTED #include +#include #endif #endif +#if MP_UNITS_HOSTED +template +constexpr bool mp_units::is_scalar> = true; +#endif + template<> constexpr bool mp_units::is_vector = true; @@ -267,6 +273,12 @@ static_assert(quantity(2000 * m).force_in(km).numerical_val static_assert((15. * m).in(nm).numerical_value_in(m) == 15.); static_assert((15'000. * nm).in(m).numerical_value_in(nm) == 15'000.); +#if MP_UNITS_HOSTED +using namespace std::complex_literals; +static_assert(((2. + 1i) * V).in(mV).numerical_value_in(mV) == 2000. + 1000i); +static_assert(((2. + 1i) * V).in(mV).numerical_value_in(V) == 2. + 1i); +#endif + template typename Q> concept invalid_unit_conversion = requires { requires !requires { Q(2000 * m).in(km); }; // truncating conversion From 80880f1014bb596220290c1782e532d205371bcb Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Tue, 17 Sep 2024 16:11:19 -0600 Subject: [PATCH 12/15] docs: examples line numbers fixed --- docs/users_guide/examples/avg_speed.md | 32 +++++++-------- docs/users_guide/examples/hello_units.md | 47 ++++++++++++----------- docs/users_guide/examples/si_constants.md | 21 ++++++---- 3 files changed, 54 insertions(+), 46 deletions(-) diff --git a/docs/users_guide/examples/avg_speed.md b/docs/users_guide/examples/avg_speed.md index 397cafc1c..313725df6 100644 --- a/docs/users_guide/examples/avg_speed.md +++ b/docs/users_guide/examples/avg_speed.md @@ -7,7 +7,7 @@ tags: # `avg_speed` -!!! example "[Try it on Compiler Explorer](https://godbolt.org/z/TnqGa4sdn)" +!!! example "[Try it on Compiler Explorer](https://godbolt.org/z/v9c5T6bc4)" Let's continue the previous example. This time, our purpose will not be to showcase as many library features as possible, but we will scope on different interfaces one can provide @@ -18,28 +18,28 @@ First, we either import a module or include all the necessary header files and i the identifiers from the `mp_units` namespace: ```cpp title="avg_speed.cpp" linenums="1" ---8<-- "example/avg_speed.cpp:28:42" +--8<-- "example/avg_speed.cpp:28:46" ``` Next, we define two functions calculating average speed based on quantities of fixed units and integral and floating-point representation types, respectively, and a third function that we introduced in the [previous example](hello_units.md): -```cpp title="avg_speed.cpp" linenums="16" ---8<-- "example/avg_speed.cpp:44:58" +```cpp title="avg_speed.cpp" linenums="20" +--8<-- "example/avg_speed.cpp:48:62" ``` We also added a simple utility to print our results: -```cpp title="avg_speed.cpp" linenums="31" ---8<-- "example/avg_speed.cpp:60:66" +```cpp title="avg_speed.cpp" linenums="35" +--8<-- "example/avg_speed.cpp:64:70" ``` Now, let's analyze how those three utility functions behave with different sets of arguments. First, we are going to use quantities of SI units and integral representation: -```cpp title="avg_speed.cpp" linenums="38" ---8<-- "example/avg_speed.cpp:68:82" +```cpp title="avg_speed.cpp" linenums="42" +--8<-- "example/avg_speed.cpp:72:86" ``` The above provides the following output: @@ -61,8 +61,8 @@ representation types (the resulting speed is `108 km/h`). The second scenario is really similar to the previous one, but this time, function arguments have floating-point representation types: -```cpp title="avg_speed.cpp" linenums="53" ---8<-- "example/avg_speed.cpp:84:95" +```cpp title="avg_speed.cpp" linenums="57" +--8<-- "example/avg_speed.cpp:88:99" ``` Conversion from floating-point to integral representation types is @@ -82,8 +82,8 @@ Average speed of a car that makes 220 km in 2 h is 110 km/h. Next, let's do the same for integral and floating-point representations, but this time using international mile: -```cpp title="avg_speed.cpp" linenums="65" ---8<-- "example/avg_speed.cpp:97:129" +```cpp title="avg_speed.cpp" linenums="69" +--8<-- "example/avg_speed.cpp:101:132" ``` One important difference here is the fact that as it is not possible to make a lossless conversion @@ -108,8 +108,8 @@ Please note how the first and third results get truncated using integral represe In the end, we repeat the scenario for CGS units: -```cpp title="avg_speed.cpp" linenums="97" ---8<-- "example/avg_speed.cpp:131:161" +```cpp title="avg_speed.cpp" linenums="101" +--8<-- "example/avg_speed.cpp:134:165" ``` Again, we observe `value_cast` being used in the same places and consistent truncation errors @@ -129,6 +129,6 @@ Average speed of a car that makes 2.2e+07 cm in 7200 s is 110 km/h. The example file ends with a simple `main()` function: -```cpp title="avg_speed.cpp" linenums="128" ---8<-- "example/avg_speed.cpp:163:" +```cpp title="avg_speed.cpp" linenums="133" +--8<-- "example/avg_speed.cpp:167:" ``` diff --git a/docs/users_guide/examples/hello_units.md b/docs/users_guide/examples/hello_units.md index dd69fcf47..a434e0862 100644 --- a/docs/users_guide/examples/hello_units.md +++ b/docs/users_guide/examples/hello_units.md @@ -6,32 +6,32 @@ tags: # `hello_units` -!!! example "[Try it on Compiler Explorer](https://godbolt.org/z/bT4GGPbef)" +!!! example "[Try it on Compiler Explorer](https://godbolt.org/z/MYn5qjPzh)" This is a really simple example showcasing the features of the **mp-units** library. First, we either import the `mp_units` module or include the headers for: -- an International System of Quantities (ISQ) -- an International System of units (SI) -- units derived from the International Yard and Pound -- text formatting and stream output support +- an International System of Quantities (ISQ), +- an International System of units (SI), +- units derived from the International Yard and Pound, +- text formatting and stream output support. ```cpp title="hello_units.cpp" linenums="1" ---8<-- "example/hello_units.cpp:28:41" +--8<-- "example/hello_units.cpp:28:45" ``` Also, to shorten the definitions, we "import" all the symbols from the `mp_units` namespace. -```cpp title="hello_units.cpp" linenums="14" ---8<-- "example/hello_units.cpp:42:43" +```cpp title="hello_units.cpp" linenums="18" +--8<-- "example/hello_units.cpp:46:47" ``` Next, we define a simple function that calculates the average speed based on the provided arguments of length and time: -```cpp title="hello_units.cpp" linenums="15" ---8<-- "example/hello_units.cpp:44:47" +```cpp title="hello_units.cpp" linenums="19" +--8<-- "example/hello_units.cpp:48:51" ``` The above function template takes any quantities implicitly convertible to `isq::length` @@ -45,37 +45,37 @@ that its quantity type is implicitly convertible to `isq::speed`. type is beneficial for users of such a function as it provides more information of what to expect from a function than just using `auto`. -```cpp title="hello_units.cpp" linenums="19" ---8<-- "example/hello_units.cpp:49:52" +```cpp title="hello_units.cpp" linenums="23" +--8<-- "example/hello_units.cpp:53:56" ``` The above lines explicitly opt into using unit symbols from two systems of units. As this introduces a lot of short identifiers into the current scope, it is not done implicitly while including a header file. -```cpp title="hello_units.cpp" linenums="23" ---8<-- "example/hello_units.cpp:54:60" +```cpp title="hello_units.cpp" linenums="27" +--8<-- "example/hello_units.cpp:58:64" ``` -- Lines `23` & `24` create a quantity of kind `isq::length / isq::time` with the numbers +- Lines `27` & `28` create a quantity of kind `isq::length / isq::time` with the numbers and units provided. Such quantities can be converted or assigned to any other quantity with a matching kind. -- Line `25` calls our function template with quantities of kind `isq::length` and +- Line `29` calls our function template with quantities of kind `isq::length` and `isq::time` and number and units provided. -- Line `26` explicitly provides quantity types of the quantities passed to a function template. +- Line `30` explicitly provides quantity types of the quantities passed to a function template. This time, those will not be quantity kinds anymore and will have [more restrictive conversion rules](../framework_basics/simple_and_typed_quantities.md#quantity_cast-to-force-unsafe-conversions). -- Line `27` changes the unit of a quantity `v3` to `m / s` in a +- Line `31` changes the unit of a quantity `v3` to `m / s` in a [value-preserving way](../framework_basics/value_conversions.md#value-preserving-conversions) (floating-point representations are considered to be value-preserving). -- Line `28` does a similar operation, but this time, it would also succeed for +- Line `32` does a similar operation, but this time, it would also succeed for [value-truncating cases](../framework_basics/value_conversions.md#value-truncating-conversions) (if that was the case). -- Line `29` does a [value-truncating conversion](../framework_basics/value_conversions.md#value-truncating-conversions) +- Line `33` does a [value-truncating conversion](../framework_basics/value_conversions.md#value-truncating-conversions) of changing the underlying representation type from `double` to `int`. -```cpp title="hello_units.cpp" linenums="30" ---8<-- "example/hello_units.cpp:62" +```cpp title="hello_units.cpp" linenums="34" +--8<-- "example/hello_units.cpp:66" ``` The above presents [various ways to print a quantity](../framework_basics/text_output.md). @@ -86,3 +86,6 @@ Both stream insertion operations and `std::format` facilities are supported. `MP_UNITS_STD_FMT` is used for compatibility reasons. If a specific compiler does not support `std::format` or a user prefers to use the `{fmt}` library, this macro will resolve to `fmt` namespace. Otherwise, the `std` namespace will be used. + + More about it can be found in the [Wide Compatibility](../use_cases/wide_compatibility.md#mp_units_std_fmt) + chapter. diff --git a/docs/users_guide/examples/si_constants.md b/docs/users_guide/examples/si_constants.md index 05dbfbc24..f5feb1535 100644 --- a/docs/users_guide/examples/si_constants.md +++ b/docs/users_guide/examples/si_constants.md @@ -6,7 +6,7 @@ tags: # `si_constants` -!!! example "[Try it on Compiler Explorer](https://godbolt.org/z/MevcK8vYT)" +!!! example "[Try it on Compiler Explorer](https://godbolt.org/z/eGqbW5d8K)" The next example presents all the seven defining constants of the SI system. We can observe how [Faster-than-lightspeed Constants](../framework_basics/faster_than_lightspeed_constants.md) @@ -22,15 +22,20 @@ the simplicity of this example, we to be able to express vector quantities with simple scalar types. ```cpp title="si_constants.cpp" linenums="14" ---8<-- "example/si_constants.cpp:42:" +--8<-- "example/si_constants.cpp:42:44" ``` -The main part of the example prints all of the SI-defining constants. While analyzing the output of -this program (provided below), we can easily notice that a direct printing of the quantity provides -just a value `1` with a proper constant symbol. This is the main power of the -[Faster-than-lightspeed Constants](../framework_basics/faster_than_lightspeed_constants.md) feature. -Only after we explicitly convert the unit of a quantity to proper SI units we get an actual numeric -value of the constant. +The main part of the example prints all of the SI-defining constants: + +```cpp title="si_constants.cpp" linenums="17" +--8<-- "example/si_constants.cpp:45:" +``` + +While analyzing the output of this program (provided below), we can easily notice that a direct +printing of the quantity provides just a value `1` with a proper constant symbol. This is the main +power of the [Faster-than-lightspeed Constants](../framework_basics/faster_than_lightspeed_constants.md) +feature. Only after we explicitly convert the unit of a quantity to proper SI units we get an +actual numeric value of the constant. ```text The seven defining constants of the SI and the seven corresponding units they define: From 3ead7c2d520d1fdfd2b46ffa5f998676f9be7bdf Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Tue, 17 Sep 2024 16:12:03 -0600 Subject: [PATCH 13/15] feat: hw_voltage example added --- docs/users_guide/examples/hw_voltage.md | 72 ++++++++++++++++++ example/CMakeLists.txt | 1 + example/hw_voltage.cpp | 97 +++++++++++++++++++++++++ mkdocs.yml | 1 + 4 files changed, 171 insertions(+) create mode 100644 docs/users_guide/examples/hw_voltage.md create mode 100644 example/hw_voltage.cpp diff --git a/docs/users_guide/examples/hw_voltage.md b/docs/users_guide/examples/hw_voltage.md new file mode 100644 index 000000000..92925732e --- /dev/null +++ b/docs/users_guide/examples/hw_voltage.md @@ -0,0 +1,72 @@ +--- +tags: + - Affine Space + - Embedded + - Text Formatting +--- + +# `hw_voltage` + +!!! example "[Try it on Compiler Explorer](https://godbolt.org/z/jjod7hvsd)" + +As it was stated in [The Affine Space](../framework_basics/the_affine_space.md) chapter, +every measurement can (and probably should) be modelled as a `quantity_point`. This is +a perfect example of such a use case. + +This example implements a simplified scenario of measuring voltage read from hardware through +a mapped 16-bits register. The actual voltage range of [-10 V, 10 V] is mapped to [-32767, 32767] +on hardware. Translation of the value requires not only scaling of the value but also applying +of an offset. + +First we include all the dependencies: + +```cpp title="hw_voltage.cpp" linenums="1" +--8<-- "example/hw_voltage.cpp:28:43" +``` + +Next, we specify the real measurement voltage range to be in the range of [-10, 10]: + +```cpp title="hw_voltage.cpp" linenums="17" +--8<-- "example/hw_voltage.cpp:45:48" +``` + +and provide a storage type and special values for the hardware representation: + +```cpp title="hw_voltage.cpp" linenums="21" +--8<-- "example/hw_voltage.cpp:50:56" +``` + +Finally, we define a quantity point origin, an offset unit that scales the value and uses this +origin to offset the zero of the sale, and a dedicated quantity point alias using those: + +```cpp title="hw_voltage.cpp" linenums="28" +--8<-- "example/hw_voltage.cpp:60:66" +``` + +Now, when everything is ready, we can simulate mapping of our hardware register, and provide +a helper function that will read the value and construct a quantity point from the obtained copy: + +```cpp title="hw_voltage.cpp" linenums="35" +--8<-- "example/hw_voltage.cpp:69:77" +``` + +We also provide a simple print helper for our quantity points: + +```cpp title="hw_voltage.cpp" linenums="44" +--8<-- "example/hw_voltage.cpp:79:82" +``` + +In the main function we simulate setting of 3 values by our hardware. Each of them is read +and printed in the voltage unit used on the hardware as well as in the standard SI unit: + +```cpp title="hw_voltage.cpp" linenums="48" +--8<-- "example/hw_voltage.cpp:84:" +``` + +The above program results with the following text output: + +```text + 0 hwV (-10 V) + 32767 hwV ( 0 V) + 65534 hwV ( 10 V) +``` diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index de84b9a8f..7c329d4ee 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -57,6 +57,7 @@ add_example(currency) add_example(foot_pound_second) add_example(glide_computer glide_computer_lib) add_example(hello_units) +add_example(hw_voltage) add_example(measurement) add_example(si_constants) add_example(spectroscopy_units) diff --git a/example/hw_voltage.cpp b/example/hw_voltage.cpp new file mode 100644 index 000000000..8eef10858 --- /dev/null +++ b/example/hw_voltage.cpp @@ -0,0 +1,97 @@ +// 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. + +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// !!! Before you commit any changes to this file please make sure to check if it !!! +// !!! renders correctly in the documentation "Examples" section. !!! +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +#include +#ifdef MP_UNITS_IMPORT_STD +import std; +#else +#include +#include +#endif +#ifdef MP_UNITS_MODULES +import mp_units; +#else +#include +#include +#include +#endif + +using namespace mp_units; + +// real voltage range +inline constexpr int min_voltage = -10; +inline constexpr int max_voltage = 10; +inline constexpr int voltage_range = max_voltage - min_voltage; + +// hardware encoding of voltage +using voltage_hw_t = std::uint16_t; +inline constexpr voltage_hw_t voltage_hw_error = std::numeric_limits::max(); +inline constexpr voltage_hw_t voltage_hw_min = 0; +inline constexpr voltage_hw_t voltage_hw_max = voltage_hw_error - 1; +inline constexpr voltage_hw_t voltage_hw_range = voltage_hw_max - voltage_hw_min; +inline constexpr voltage_hw_t voltage_hw_zero = voltage_hw_range / 2; + + +// clang-format off +inline constexpr struct hw_voltage_origin final : + relative_point_origin(min_voltage)> {} hw_voltage_origin; + +inline constexpr struct hw_voltage_unit final : + named_unit<"hwV", mag_ratio * si::volt, hw_voltage_origin> {} hw_voltage_unit; + +using hw_voltage_quantity_point = quantity_point; +// clang-format on + +// mapped HW register +volatile voltage_hw_t hw_voltage_value; + +std::optional read_hw_voltage() +{ + voltage_hw_t local_copy = hw_voltage_value; + if (local_copy == voltage_hw_error) return std::nullopt; + return absolute(local_copy); +} + +void print(QuantityPoint auto qp) +{ + std::println("{:10} ({:5})", qp.quantity_from_zero(), value_cast(qp).quantity_from_zero()); +} + +int main() +{ + // simulate reading of 3 values from the hardware + hw_voltage_value = voltage_hw_min; + quantity_point qp1 = read_hw_voltage().value(); + hw_voltage_value = voltage_hw_zero; + quantity_point qp2 = read_hw_voltage().value(); + hw_voltage_value = voltage_hw_max; + quantity_point qp3 = read_hw_voltage().value(); + + print(qp1); + print(qp2); + print(qp3); +} diff --git a/mkdocs.yml b/mkdocs.yml index da83c3c84..d54a807ef 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -175,6 +175,7 @@ nav: - hello_units: users_guide/examples/hello_units.md - avg_speed: users_guide/examples/avg_speed.md - si_constants: users_guide/examples/si_constants.md + - hw_voltage: users_guide/examples/hw_voltage.md - Appendix: - Glossary: appendix/glossary.md - References: appendix/references.md From 7e777f228ab98c58adec79f8280fb52a67596076 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Tue, 17 Sep 2024 17:10:53 -0600 Subject: [PATCH 14/15] fix: hw_voltage compilation fixed on C++20 --- docs/users_guide/examples/hw_voltage.md | 26 ++++++++++++------------- example/hw_voltage.cpp | 6 ++++-- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/docs/users_guide/examples/hw_voltage.md b/docs/users_guide/examples/hw_voltage.md index 92925732e..42ac30600 100644 --- a/docs/users_guide/examples/hw_voltage.md +++ b/docs/users_guide/examples/hw_voltage.md @@ -21,46 +21,46 @@ of an offset. First we include all the dependencies: ```cpp title="hw_voltage.cpp" linenums="1" ---8<-- "example/hw_voltage.cpp:28:43" +--8<-- "example/hw_voltage.cpp:28:44" ``` Next, we specify the real measurement voltage range to be in the range of [-10, 10]: -```cpp title="hw_voltage.cpp" linenums="17" ---8<-- "example/hw_voltage.cpp:45:48" +```cpp title="hw_voltage.cpp" linenums="18" +--8<-- "example/hw_voltage.cpp:46:49" ``` and provide a storage type and special values for the hardware representation: -```cpp title="hw_voltage.cpp" linenums="21" ---8<-- "example/hw_voltage.cpp:50:56" +```cpp title="hw_voltage.cpp" linenums="22" +--8<-- "example/hw_voltage.cpp:51:57" ``` Finally, we define a quantity point origin, an offset unit that scales the value and uses this origin to offset the zero of the sale, and a dedicated quantity point alias using those: -```cpp title="hw_voltage.cpp" linenums="28" ---8<-- "example/hw_voltage.cpp:60:66" +```cpp title="hw_voltage.cpp" linenums="29" +--8<-- "example/hw_voltage.cpp:61:67" ``` Now, when everything is ready, we can simulate mapping of our hardware register, and provide a helper function that will read the value and construct a quantity point from the obtained copy: -```cpp title="hw_voltage.cpp" linenums="35" ---8<-- "example/hw_voltage.cpp:69:77" +```cpp title="hw_voltage.cpp" linenums="36" +--8<-- "example/hw_voltage.cpp:70:78" ``` We also provide a simple print helper for our quantity points: -```cpp title="hw_voltage.cpp" linenums="44" ---8<-- "example/hw_voltage.cpp:79:82" +```cpp title="hw_voltage.cpp" linenums="45" +--8<-- "example/hw_voltage.cpp:80:84" ``` In the main function we simulate setting of 3 values by our hardware. Each of them is read and printed in the voltage unit used on the hardware as well as in the standard SI unit: -```cpp title="hw_voltage.cpp" linenums="48" ---8<-- "example/hw_voltage.cpp:84:" +```cpp title="hw_voltage.cpp" linenums="50" +--8<-- "example/hw_voltage.cpp:86:" ``` The above program results with the following text output: diff --git a/example/hw_voltage.cpp b/example/hw_voltage.cpp index 8eef10858..f04c6489f 100644 --- a/example/hw_voltage.cpp +++ b/example/hw_voltage.cpp @@ -26,11 +26,12 @@ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! #include +#include #ifdef MP_UNITS_IMPORT_STD import std; #else +#include #include -#include #endif #ifdef MP_UNITS_MODULES import mp_units; @@ -78,7 +79,8 @@ std::optional read_hw_voltage() void print(QuantityPoint auto qp) { - std::println("{:10} ({:5})", qp.quantity_from_zero(), value_cast(qp).quantity_from_zero()); + std::cout << MP_UNITS_STD_FMT::format("{:10} ({:5})", qp.quantity_from_zero(), + value_cast(qp).quantity_from_zero()); } int main() From 5305de7a2852c0cda542e0baa959dc21dadd303d Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Tue, 17 Sep 2024 17:12:14 -0600 Subject: [PATCH 15/15] docs: "Text Formatting" tag removed from avg_speed example --- docs/users_guide/examples/avg_speed.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/users_guide/examples/avg_speed.md b/docs/users_guide/examples/avg_speed.md index 313725df6..9a0073a90 100644 --- a/docs/users_guide/examples/avg_speed.md +++ b/docs/users_guide/examples/avg_speed.md @@ -2,7 +2,6 @@ tags: - CGS System - International System - - Text Formatting --- # `avg_speed`