-
Notifications
You must be signed in to change notification settings - Fork 93
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to name a number accessor in quantity
?
#477
Comments
|
The reason I picked the "data" word for Au's I don't have any new ideas myself, but I'll be interested to see what people come up with! |
Adding As @mpusz mentioned at #476 (comment), The |
|
quantity_point
?quantity
?
I just implemented It seems that often we see the following pattern |
That wouldn't work if |
Yes, but why make it in the way it does not work? Do you suggest that we should also worry about the lifetime issues here? What initially bothered me was the mandatory repetition of the same unit. Are we OK with that? |
I think that expression is potentially bogus. |
Yes, but this is what we have done for many years now in the C++ Standard Library. For example https://en.cppreference.com/w/cpp/utility/optional/value. |
Of course, considering your use case with passing the reference to an underlying API for updating the wrapped value, I see your point. In such case, const auto temp = value_cast<si::metre / si::second>(q)
std::court << temp.value_ref_in(si::metre / si::second); That is a lot of typing to ensure that the unit is correct, and still, we have this repetition that I meant initially. Previously, it was enough to type: std::cout << value_cast<si::metre / si::second>(q).value(); |
If you really need an non-const lvalue reference to the underlying number, Are you sure you shouldn't be using |
To make sure that everyone is on the same page, here are the important conversions we have in the library for similar cases:
|
I did one copy in the |
I actually think otherwise. This issue is how we should name the accessor (so the reference is implied) member function and not the one that potentially provides a scaled value that is not stored inside the current quantity. |
Oh, right, this issue is about |
I pushed the changes to #484 so everyone can see how it changes the existing code. Let's review that and decide if this is what we want. Maybe there is a way to improve it even more? |
This list is really helpful. I think one thing missing from here is a way to "force" or "coerce" a meaningful but potentially lossy conversion, and get the value out, all in one step. Using Au as a reference point, and assuming the new APIs coming in aurora-opensource/au#122, I thought it might be illustrative to make a few tables. This will give us a systematic accounting of the different operations. Note that I'm explicitly using Au's likely future APIs. In particular, here are the differences from what we actually have today:
The reason I'm using future APIs should become clear when we see the tables. These semantics would be more logically consistent and easier to learn, because they'd form a composable grammar, where vocabulary words such as "as", "in", and "coerce" have consistent meaning. Now for the tables. Comparison tablesReturning a quantity:
Note that in reality, Returning a raw number:
Note that in reality, Returning a raw number reference:
Takeaways / discussion
I don't know what set of APIs I would propose for mp-units, or even whether I would say that we should definitely strive for APIs that use vocabulary words consistently. But I found that writing out these tables helped me understand why this was easy for Au but currently hard for mp-units. |
We can add |
I agree that we lack the functionality to force a cast and get a value in one step. And I also do not have a good idea how to do it. Before the change in #484 it was straightforward |
Regarding Au's approach, I am afraid that it would not find an agreement in the ISO C++ Committee room. The C++ community is used to see |
Please remember that we also have a |
Yep --- this is definitely a downside. It was a huge improvement going from Aurora's old units library, with its
I'd love to see
Why would someone write this, instead of
I think this is a really tough question. Some worthy design goals:
One of these three is going to have to go, though. Maybe one factor could be figuring out how often Hmm... how bad would it be to have both? |
Assuming that we will add:
and we will leave
|
Another question is about the
Any other ideas? |
There's little chance of getting it wrong, whatever we choose, if we're not basing it on existing L(E)WG guidelines. |
Sure, but we still may vote on the thing that we like the most 😉 |
A "safe truncating cast" would be very convenient for integer reps where there is a risk of the multiplication overflowing before the division can occur. This could be implemented as a Currently this requires a double |
You can probably do that with a representation type with that behavior that is convertible to the integer rep. |
Actually this is only a problem for |
I do not think it is a good idea to do this in a generic code for several reasons: |
If your use case allows it, |
Thanks @mpusz for pointing out the importance of staying within the integer domain in embedded use cases. For every unit conversion of this type, there's some smallest value which would overflow in the multiplication step (i.e., an exclusive upper bound on the "friendly range" described here). We can compute this number at compile time. A safe truncating cast could reject values outside of the threshold. If users wanted a version that would accept anything in the "full range" --- i.e., values whose final result wouldn't overflow --- then we could perform the computations in the widest version of a given type: for signed or unsigned integers, this would be I don't know if we want both versions, but those are some ideas! |
I encountered this while converting a The overflow makes sense once we know that In traditional code this would be more obvious as the numerator would be used directly (or wouldn't be a problem at all since the ratio could be defined as a floating point to begin with). Since the library calculates the ratio on the fly, it is easy to miss that the numerator has gone beyond the acceptable range if you are not paying attention. Luckily my quantity values were always close to 2^39 so I could see something was clearly wrong, but it could very easily go unnoticed if most values don't overflow. I (wrongly) expected the only risk of Thus at a minimum I think something similar to what @chiphogg suggested should be included, distinguishing between precision loss only (
I somewhat fall under this category in that my application is embedded - and while It is hard to say how common this situation is, but I suspect there may be other applications where integers are preferable either for performance or storage reasons (the weird original quantity comes from a binary message format). So far this is the only friction I have encountered using the library, as I must do the intermediate cast and/or pay special attention to integer conversions. Could this behavior be enabled/disabled with a compiler flag, if an embedded application strictly doesn't want to use it? Perhaps with a choice to use
Wouldn't these types also fail the |
I did it, but didn't include the "why", since that'd be off-topic: hsutter/cppfront#658 (comment). |
That's just exchanging overflow for underflow. |
I should also add the main reason I started using this library is because the protocol in question only uses integer values but with very strange quantities. Most other applications give up immediately and convert everything to double as soon as the values are parsed, but it has been fantastic being able to preserve the original values until the later processing steps.
Only if the ratio is greater than 1 right? |
Please note that you can convert from a quantity of
No, we are providing specialization for them: mp-units/example/measurement.cpp Lines 120 to 121 in 507d5bc
mp-units/test/unit_test/runtime/linear_algebra_test.cpp Lines 37 to 38 in 507d5bc
|
This is how I am working around it currently, but that is a serious hindrance if your application makes use of |
You could write a non-intrusive free function template. Something like this (untested): template<class NewUnits, class U, class R>
constexpr auto convert_via_double(const mp_units::quantity<U, R> &q, NewUnits new_units) {
return q.in<double>(new_units).in<R>();
} (Please pardon my old school, pre-Concepts coding habits.) Of course, it's only a stopgap solution, but it shows the intent better at the callsite, and it'll be easily greppable in case we come up with a better mp-units-native idiom for this use case. |
At one point |
Right now mp-units is using a common type of the:
For scaling quantity values to a new unit. If source, destination, and ratio are expressed in integral types then indeed a truncation may happen. mp-units/src/core/include/mp-units/bits/sudo_cast.h Lines 76 to 83 in 507d5bc
|
While adding |
If I knew a better name than
|
|
Please note that in many cases it actually might not truncate the value. So maybe |
Additionally, truncation might not always be the only check which the library does. We may guard against overflow, as Au's overflow safety surface does. I'm not sure there's a use case for separately opting out of truncation and overflow checks (and the naming would be pretty unwieldy if there were). So mentioning truncation is probably not the way to go. |
In the discussion in #476, it was suggested that the library should only contain unit-safe interfaces. That is an excellent recommendation. The only problem we have is with naming two alternative interfaces of
quantity
.The first interface (possibly) converts the underlying number to a required unit:
This is why this interface always returns a value (not a reference to underlying data).
The second interface should return such a reference so users do not have to pay for a copy (if the representation type is expensive to copy) or can even change the underlying storage with external means (see #476 (comment) and #476 (comment)) for rationale. To make it unit safe it also should take the unit as an argument. As this function never changes underlying storage such an interface should be available only if the requested unit is equivalent (does not have to be exactly the same) to the current one.
raw_number_in
is just a placeholder name above. Do you have a better idea of how to name such two interfaces so everyone understands what they do?The text was updated successfully, but these errors were encountered: