The way to achieve this with a units library is to make sure that every interface that brings values into a units library type, or extracts values from a units library type, forces the user to name the unit explicitly, at the callsite. Au was designed with this approach in mind from the ground up, and I was very heartened to see mp-units adopt it as well, to the point that mp-units is now every bit the equal of Au in unit safety. (Really, the whole 2.0 redesign for mp-units was a stunning leap forward in a lot of ways --- I was thrilled to see it.)
What you call unit safety, I'd argue, is a coding convention. You might enforce it by power; you can't enforce it solely by c++ language features, and that's a good thing. It's because it goes against composability. Think about this code:
auto l = [](double d) constexpr { return d * km; };
static_assert(l(12) == 12 * km);
I'd argue that l(12) is not unit-safe: simply reading that line, you cannot tell whether that static assert should succeed (i.e., I wrote d * km in the lambda) or fail (i.e., if I were to write d * m, or even 2 * d * km). I also didn't write return type in the lambda, as it's auto. Thus, no matter what you ensure in the library, if a user would like a lambda constructing a unit from a double, they have it today. Therefore, the argument is, whether it's a coding standard we promote.
Unit safety, as defined here, is a property that any individual line of code does or does not have. You're absolutely right that no API could ever achieve it universally. It's always possible for a sufficiently adversarial user to defeat it. The goal, though, is to design APIs that make unit safety easy to achieve, by strong default.
In this specific example, I fully agree that l(12) is not unit-safe. In fact, the root of this problem is that "auto l = ..." is not unit-safe. This individual line takes a raw number, and returns a quantity, but the name `l` does not name the unit. Therefore, this individual line should fail code review because it makes an interface that is not unit-safe, and that will cause downstream users to write non-unit-safe code.
Now to your question: if you have a typename that contains the unit name, isn't that enough? Say we had a type called `degrees_celsius`: why wouldn't it be OK to have a constructor from `double`, so you could simply write `degrees_celsius{42.0}`? (We would make it `explicit`, of course!)
There are units that you cannot construct as a + b * x, where an and b are constants. A common example is date (based on some absolute time) of countries: there are examples of days missing from calendars. Thus, in your serialisation / deserialisation, you'll need a way to express quantity_point constructions anyways. Users of the library could create such a thing in non-standard ways; or, we can provide a standard function to create it.
I believe dates generally are out of scope for P3045. That's more the domain of the chrono library. (Of course, we'll want robust interop between chrono durations and durations in the standard units library. Thankfully, the existing open-source libraries on which the proposal is based already have this property.)
Cheers,
Chip
Units are still checked while compiling, so zero type safety is sacrificed.
Thanks,
-lorro
--
Std-Proposals mailing list
Std-Proposals@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals