Repository navigation
Units are constexpr, convert by one compile-time map, and reject mixed dimensions by concept - #592
Merged
Merged
Conversation
…d dimensions by concept Every LLUnit and LLUnitImplicit operation is constexpr, so a conversion, or a constant built from units, folds at compile time. A unit's conversion from its base unit is an ALUnitConversion, (x * m + c) / d, built from the text the macros already took (ALUnitConversion() * 9 / 5 + 32). Between two units it is one such map through their root unit, kept in lowest terms and, for an integer, worked in 64 bits with one division. That replaces the recursive ll_convert_units chain, the two overloads each macro generated and the linear-ops helpers, and fixes what the chain got wrong: a second offset went in without the first step's divisor (212 F came to 130.35 K), a divisor wider than 32 bits overflowed (U32_MAX microseconds came to 8 days), and a multiplication after a division dropped the divisor (/ 4 * 2 doubled where it should halve). A floating point conversion across levels now multiplies once (3600 for hours to seconds) where it multiplied per level. Operators between units take any mix of LLUnit and LLUnitImplicit and require ALCompatibleUnits, units of one root. What does not mix is a deleted overload rather than one left out, since a unit converts to its storage type and an operator left out would fall to float arithmetic: units of different dimensions, an explicit unit plus or minus a plain value, a product of units, and *= or /= by a unit. The static-assert overloads go with it, and each rejection can now be asked of a concept. Adding an explicit unit to an implicit one no longer truncates it into the implicit one's storage first. Comparisons convert the right operand into the left one's units, with != rewritten from ==. The relational operators stay written out: MSVC compiles a floating point <=> followed by < 0 into a chain of branches. LLUnitImplicit != LLUnit and the valueInUnits<U>(v) setter, which was const, now compile, and compound assignments return the unit. fmt::formatter formats a unit's value by its storage type's spec, then its label. It lives in llunittype.h, on fmt/base.h, because fmt 12 refuses a type with only an implicit conversion, so a formatter in another header could differ between translation units. The Microseconds label was a tab and "<s"; it is "µs". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 22 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
LLUnitandLLUnitImplicitare constexpr throughout, convert between units through one map worked out at compile time, and reject what does not mix by concept, with a deleted overload for each rejection.Conversions
A unit's conversion from its base unit is an
ALUnitConversion(alunitconversion.h),(x * m + c) / d, built from the text the macros already took:ALUnitConversion() * 9 / 5 + 32. A conversion between two units is one such map through their root unit, kept in lowest terms and, for an integer, worked in 64 bits with one division. It replaces the recursivell_convert_unitschain, the two overloads eachLL_DECLARE_DERIVED_UNITgenerated,LLUnitLinearOps/LLUnitInverseLinearOpsandsLevel. The macros stay as wrappers, and a unit can also be written as a type (ALBaseUnit,ALDerivedUnit).Fixed on the way, each with a test that fails against the old header:
U32_MAXmicroseconds came to 8 days./ 4 * 2doubled where it should halve.S32QuatloosImplicit(1) + F32Latinum(0.375f)truncated the explicit operand into the implicit one's storage first (2, now 2.5), unlike every other pairing of the two kinds.A floating point conversion across levels now multiplies once (3600 for hours to seconds) where it multiplied per level, so its last bit can round differently.
What does not mix
Operators between units take any mix of
LLUnitandLLUnitImplicitand requireALCompatibleUnits, units of one root. A rejection is a deleted overload rather than an omitted one: a unit converts to its storage type, so an omitted operator would be taken by float arithmetic and drop the units silently. Rejected: units of different dimensions (arithmetic, comparison, construction,+=), an explicit unit plus or minus a plain value, a product of units, and*=or/=by a unit. The static-assert overloads go, and each rejection can now be asked of a concept:std::is_constructible_v<F32Seconds, F32Bytes>is false, where it was true.The rest
!=is rewritten from==. The relational operators stay written out rather than rewritten from<=>: MSVC /O2 compiles a floating point<=>followed by< 0into a chain of branches.[[nodiscard]].LLUnitImplicit != LLUnitand thevalueInUnits<U>(v)setter, which wasconst, now compile.fmt::formatterfor both kinds: the value by its storage type's spec, then the label, so"{:.2f}"ofF32Seconds(1.5f)is1.50s. It lives inllunittype.honfmt/base.h: fmt 12 refuses a type with only an implicit conversion, so a formatter in another header could differ between translation units, andllunits.his not in the PCH. Formatting a unit needsfmt/format.hwhere it happens, as any fmt call does.<s; it isµs.Testing
vs2026-osRelWithDebInfo with tests off: the whole tree built, 1581 files, no warnings.llunits_testand the newalunitconversion_test(30 cases, 220 assertions) pass built outside the tree with the tree's MSVC flags, and with clang-cl at C++20 and C++23;lltrace_testcompiles against the new header.Not built here: macOS, Linux (GCC), and the llcommon test binary in a tests-on tree.
🤖 Generated with Claude Code