I had been using "consteval plus exception" as my way to get
"static_assert, but for parameters". Since consteval doesn't work, then
I can't _guarantee_ that the functions won't be called at runtime.
However, I still think throwing exceptions is better, because it will
cause the desired compiler errors on every configuration. (`assert`
often gets compiled out.)
Very much open to suggestion here.
For each Magnitude `m`, we now support:
- `is_rational(m)` tells whether it represents a rational number.
- `is_integral(m)` tells whether it represents an integer (and
`is_integral(m)` implies `is_rational(m)`).
- `m.value<T>` represents the value of `m` in the type `T`.
If `T` is integral, we only support `m.value<T>` when `is_integral(m)`.
We also perform all intermediate computations in the widest type of the
same category (floating point, signed, or unsigned). This means we can,
for example, give first-class support to embedded users who may have
hardware support only for `float`: we can ensure they get the most
accurate `float` value we can compute, without burdening them with a
dependency on `long double` in their runtime code.
We are not yet ready to replace `ratio` as the definition of unit
magnitudes, but we're a step closer. The next step will be to decompose
a Magnitude into numerator, denominator, and irrational parts, giving us
full bidirectional convertibility with existing `ratio` instances. Then
we can migrate over in a controlled fashion.
This doesn't show up on any other compiler, including MSVC 14.3, so I
think it's just a compiler bug. Cursory googling suggests perhaps that
some older versions of MSVC have immature support for `if constexpr`.
The motivation for the `mag` sub-namespace was to distinguish something
like `mag::product_t<...>` from `dim::product_t<...>`, based on the
idioms of Aurora Units. However, we have no need for a `product_t` type
trait, since we can just use `operator*()`, so we can eliminate this
sub-namespace.
The new test actually passed without modifying the code. However, that
might be implementation-dependent (presumably based on canonicalization
of the `ratio` template parameter), so I wanted a more obviously correct
implementation.
I got my requires expressions/clauses mixed up.
Unfortunately, this means we can't just shove all the "implementation
details" down to the bottom of the file. Oh well.
Instead of templating on `long double`, we template on "anything whose
value member is `long double`". This is how we work around gcc-10's
lack of support for floating point NTTPs.
To access the base value, we provide `.get_base()` member functions.
The clearest and most direct way I know to express "is this a base
power" is via a type trait, which is true only for `base_power<T>`.