Use ImplicitConversion in Quantity constructor#633
Conversation
This gives us a reasonably clear and satisfactory policy. Any time someone uses the `Quantity` constructor, we should treat it as an implicit conversion. (Recall that the `Quantity`-from-`Quantity` constructor is _always_ implicit, and the `explicit` one is explicitly deleted.) Any time someone uses `.in` or `.as`, we continue to use `static_cast`. This makes sense because it invokes a unit conversion, at least conceptually, and therefore it isn't really an instance of the kind of "pure wrapping" use case where we strongly want to preserve compiler errors. Or, in other words: the goal is that any time `double` to `float` would produce an error, so would the same `QuantityD` to `QuantityF`. The reason `.in` and `.as` should still use explicit `static_cast` is that this is not "the same" situation, as there is no unit conversion possible with raw `double` and `float`. On the implementation side, the cleanest approach I found was to add an explicit casting strategy template argument to `in_impl`, because every `Quantity` conversion is built on top of it. The implicit constructor gets `ImplicitConversion`, and everything else still uses `StaticCast`. This forces us to add friendship across different template instantiations of `Quantity` (because now we're initializing `value_` directly), but that presents no problem from a software quality point of view. For `QuantityPoint`, we needed to add an additional `rep_cast` for `in_impl` (because of promotion), but otherwise we can simply delegate to the `Quantity` machinery, which is nice. We also uncovered and fixed an API error: subtracting two `QuantityPoint` instances should return `auto`, not `Diff`, because if promotion happens, then we want to return the promoted rep. We want our types to behave just like the underlying C++ types, in all their weird and dubious glory. Helps #528. This is really the core of the solution on the code side. We will need to follow up with clear docs to explain the importance of avoiding `-isystem` when including Au, and the philosophy behind it.
|
|
|
||
| TEST(Conversions, SupportIntMHzToU32Hz) { | ||
| constexpr QuantityU32<Hertz> freq = mega(hertz)(40); | ||
| constexpr QuantityU32<Hertz> freq = mega(hertz)(40u); |
There was a problem hiding this comment.
So this wouldn't give us an error:
constexpr unsigned freq = 40;But of course, we creating a quantity first, then doing the assignment. I suppose this is reasonable. I'm guessing a Constant would avoid this?
constexpr QuantityU32<Hertz> freq = make_constant(mega(hertz) * mag<40>());So let's just say this is another use case for a more ergonomic way to create ad-hoc constants. In fact, if mega(herts)(40) created a Constant instead of a Quantity...
On the embedded side of things, we'll sometimes be more explicit of the rep we want when creating a Quantity with:
constexpr QuantityU32<Hertz> freq = mega(hertz)(uint32_t{40});There was a problem hiding this comment.
As much as I wish it were otherwise, mega(hertz)(40) could never be finagled to produce a Constant in the C++ language. We can't return different types based on the value of a (normal, non-template) function parameter, even if that parameter's value is known at compile time.
But yes, creation ergonomics aside, I do expect a Constant would handle this very nicely!
This gives us a reasonably clear and satisfactory policy. Any time
someone uses the
Quantityconstructor, we should treat it as animplicit conversion. (Recall that the
Quantity-from-Quantityconstructor is always implicit, and the
explicitone is explicitlydeleted.) Any time someone uses
.inor.as, we continue to usestatic_cast. This makes sense because it invokes a unit conversion,at least conceptually, and therefore it isn't really an instance of the
kind of "pure wrapping" use case where we strongly want to preserve
compiler errors.
Or, in other words: the goal is that any time
doubletofloatwouldproduce an error, so would the same
QuantityDtoQuantityF. Thereason
.inand.asshould still use explicitstatic_castis thatthis is not "the same" situation, as there is no unit conversion
possible with raw
doubleandfloat.On the implementation side, the cleanest approach I found was to add an
explicit casting strategy template argument to
in_impl, because everyQuantityconversion is built on top of it. The implicit constructorgets
ImplicitConversion, and everything else still usesStaticCast.This forces us to add friendship across different template
instantiations of
Quantity(because now we're initializingvalue_directly), but that presents no problem from a software quality point of
view.
For
QuantityPoint, we needed to add an additionalrep_castforin_impl(because of promotion), but otherwise we can simply delegateto the
Quantitymachinery, which is nice. We also uncovered and fixedan API error: subtracting two
QuantityPointinstances should returnauto, notDiff, because if promotion happens, then we want to returnthe promoted rep. We want our types to behave just like the underlying
C++ types, in all their weird and dubious glory.
Finally, we had to tweak one test in the parent PR, because of our
-Wsign-conversionjob. We now need to use40uinstead of40whenassigning to
QuantityU32, because this assignment uses the implicitconversion.
Helps #528. This is really the core of the solution on the code side.
We will need to follow up with clear docs to explain the importance of
avoiding
-isystemwhen including Au, and the philosophy behind it.