From 27c5f0e2b91c53d8b2598cbcd39f1c47875d05b7 Mon Sep 17 00:00:00 2001 From: Chris Leishman Date: Wed, 15 Jul 2026 12:27:31 -0700 Subject: [PATCH] Add scale-gated unary minus to temperature; bump to 2.1.0 Enables negative absolute-temperature literals such as -123_dc, which previously failed to compile: a user-defined literal never sees the sign, so -123_dc parses as -(123_dc) and temperature had no unary operator-. Unary minus is gated on _is_relative_scale, so it is available on Celsius and Fahrenheit (zero above absolute zero) but a compile error on Kelvin, where a negated temperature would fall below absolute zero. Deltas remain negatable on every scale. --- CMakeLists.txt | 2 +- README.md | 11 +++++++++++ docs/Doxyfile | 2 +- idf_component.yml | 2 +- include/thermo/thermo.hpp | 24 ++++++++++++++++++++++++ tests/thermo_test.cpp | 30 ++++++++++++++++++++++++++++++ vcpkg-port/vcpkg.json | 2 +- 7 files changed, 69 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ff93226..4c7c2d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ if(ESP_PLATFORM) endif() project(thermo - VERSION 2.0.0 + VERSION 2.1.0 DESCRIPTION "Type-safe temperature handling library modeled after std::chrono" HOMEPAGE_URL "https://github.com/cleishm/thermo-cpp" LANGUAGES CXX diff --git a/README.md b/README.md index 6fadb79..4a47788 100644 --- a/README.md +++ b/README.md @@ -143,12 +143,23 @@ using namespace thermo_literals; 293_k // kelvin(293) 68_f // fahrenheit(68) +// Negative absolute temperatures (unary minus on the literal) +-123_dc // decicelsius(-123) = -12.3°C +-40_f // fahrenheit(-40) +// -25_k // ill-formed: negating a Kelvin temperature falls below absolute zero + // Temperature deltas 5_Δc // delta_celsius(5) 5000_Δmc // delta_millicelsius(5000) 9_Δf // delta_fahrenheit(9) = delta_celsius(5) ``` +Unary minus is available on relative scales (Celsius, Fahrenheit) so that +negative literals like `-123_dc` work — the sign is applied to the value the +literal produces, i.e. `-(123_dc)`. It is deliberately *not* available on the +absolute Kelvin scale, where a negated temperature would fall below absolute +zero; `-25_k` is a compile error. Deltas are negatable on every scale. + ## Conversion Rules Conversions follow the same philosophy as `std::chrono`: diff --git a/docs/Doxyfile b/docs/Doxyfile index 4ed8ac3..3723aee 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -6,7 +6,7 @@ DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = "thermo" -PROJECT_NUMBER = "2.0.0" +PROJECT_NUMBER = "2.1.0" PROJECT_BRIEF = "Type-safe temperature handling library modeled after std::chrono" PROJECT_LOGO = OUTPUT_DIRECTORY = . diff --git a/idf_component.yml b/idf_component.yml index b89fd84..b5361f7 100644 --- a/idf_component.yml +++ b/idf_component.yml @@ -1,4 +1,4 @@ -version: "2.0.0" +version: "2.1.0" description: "Type-safe temperature handling library modeled after std::chrono" url: "https://github.com/cleishm/thermo-cpp" repository: "https://github.com/cleishm/thermo-cpp.git" diff --git a/include/thermo/thermo.hpp b/include/thermo/thermo.hpp index 59fd68d..a1adcd1 100644 --- a/include/thermo/thermo.hpp +++ b/include/thermo/thermo.hpp @@ -585,6 +585,17 @@ struct fahrenheit_scale { using degree = std::ratio<5, 9>; static constexpr const char* suffix = "°F"; }; + +/** + * @brief True when a scale's zero point is not absolute zero. + * + * Negating an absolute temperature only makes sense on a "relative" scale + * (e.g. Celsius, Fahrenheit), where the zero point sits above absolute zero. + * On an absolute scale (Kelvin, offset == 0) a negated temperature always + * falls below absolute zero, so unary minus is disabled there. + */ +template +inline constexpr bool _is_relative_scale = std::ratio_not_equal_v>; /** @endcond */ template> @@ -729,6 +740,19 @@ class temperature { constexpr temperature operator--(int) { return temperature(_d--); } + /** + * @brief Negates the temperature (e.g. to write @c -123_dc as @c -(123_dc)). + * + * Only available on relative scales such as Celsius and Fahrenheit; on an + * absolute scale (Kelvin) negation would fall below absolute zero and is a + * compile error. See @ref _is_relative_scale. + */ + constexpr temperature operator-() const + requires _is_relative_scale + { + return temperature(-_d); + } + template constexpr temperature& operator+=(const delta& d) { _d += d; diff --git a/tests/thermo_test.cpp b/tests/thermo_test.cpp index dfdf49a..1206bc8 100644 --- a/tests/thermo_test.cpp +++ b/tests/thermo_test.cpp @@ -333,6 +333,36 @@ TEST_CASE("delta literals", "[thermo][literals]") { REQUIRE((9000_Δmf).count() == 9000); } +// ============================================================================= +// Negation (enables negative literals, e.g. -123_dc == -(123_dc)) +// ============================================================================= + +template +concept has_unary_minus = requires(T t) { -t; }; + +// Available on relative scales (zero above absolute zero)... +static_assert(has_unary_minus); +static_assert(has_unary_minus); +static_assert(has_unary_minus); +// ...but not on the absolute scale, where a negated value is below absolute zero. +static_assert(!has_unary_minus); +static_assert(!has_unary_minus); +static_assert(!has_unary_minus); + +// Compile-time tests +static_assert((-123_dc).count() == -123); +static_assert((-40_f).count() == -40); +static_assert((-20_c).count() == -20); +static_assert(-(-5_c) == 5_c); + +// Runtime tests +TEST_CASE("temperature negation", "[thermo][temperature]") { + REQUIRE((-123_dc).count() == -123); + REQUIRE((-40_f).count() == -40); + REQUIRE((-20_c).count() == -20); + REQUIRE((-(-5_c)).count() == 5); +} + // ============================================================================= // String formatting // ============================================================================= diff --git a/vcpkg-port/vcpkg.json b/vcpkg-port/vcpkg.json index 0a2f0ce..8cbeb31 100644 --- a/vcpkg-port/vcpkg.json +++ b/vcpkg-port/vcpkg.json @@ -1,6 +1,6 @@ { "name": "cleishm-thermo-cpp", - "version": "2.0.0", + "version": "2.1.0", "description": "Type-safe temperature handling library modeled after std::chrono", "homepage": "https://github.com/cleishm/thermo-cpp", "license": "MIT",