-
Notifications
You must be signed in to change notification settings - Fork 29
Avoid implicit conversion when converting Quantity to std::complex #445
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
Avoid implicit conversion when converting Quantity to std::complex #445
Conversation
chiphogg
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What a subtle and tricky error! Thanks for root causing it and fixing it.
|
|
||
| return make_quantity<AssociatedUnitT<NewUnit>>( | ||
| static_cast<NewRep>(detail::apply_magnitude(static_cast<Common>(value_), Factor{}))); | ||
| static_cast<NewRep>(static_cast<Intermediate>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole implementation will get a lot cleaner once the #387 tsunami (not yet out for review) lands. It'll be back to just 1 line of code in this file. And it'll be simpler on the implementation side, too: it will generate exactly the static_cast calls that we need, and no others. (That set of static_cast calls will change slightly based on what I'm learning from this PR, but that change will be trivially easy.)
In the meantime, we'll be calling an extra layer of static_cast here for most types. I'm fine with that because we're going to fast-follow with the improvement. Not to mention, I suspect the compiler will optimize it out anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice. I don't particularly love that this PR is necessary, but agreed, I also expect the performance impact to be minimal and I'm glad to hear this will be refactored soonish.
Reorganize the codebase such as the code is located at `//au/*` rather
than `//au/code/au/*`.
The primary motivation here is to conform more closely with the
canonical header path style that Bazel likes by avoiding
[`cc_library.includes`][includes]. Previously, we had Bazel targets
that looked like
```
cc_library(
hdrs = ["code/au/au.hh"],
includes = ["code"],
...
)
```
It is unusual and somewhat problematic to do this. The `hdrs` suggests
that code should `#include "PACKAGE_PATH/code/au/au.hh"`, but we
actually want people to just use the more typical
`#include "PACKAGE_PATH/au.hh"`. Using `includes` to permit this
essentially sidesteps Bazel's normal include path logic, and typically
causes the headers to be added to the system header include path
(`-isystem`) rather than the normal include path (`-I`). This can have
undesirable side effects such as suppressing warnings in these headers.
One alternative would be to use `strip_include_prefix = "code/au"`. This
brings the include path management back into Bazel's domain, but results
in a bunch of ugly and unnecessary virtual includes.
We should just simplify our include paths by dropping the extra
`code/au` directory entirely.
Note that we originally added this subdir in #266 to work around some
symlink concerns in our CMake build. Since then, our CMake handling has
matured considerably, to the point where I believe this subdir is no
longer required.
[includes]: https://bazel.build/reference/be/c-cpp#cc_library.includes
---
Note that doing this actually revealed a couple additional warnings that
were previously suppressed by the -isystem. These have been addressed
in #444 and #445.
Fundamentally, this is all about the tests. We're just adding a few more test cases, and making them pass. What those tests showed us is interesting. The first test is pretty straightforward. It showed that we weren't doing the same multiply-then-divide operation for `std::complex<int>` that we would do for `int`. That was very easy to fix. The rest of the tests cover the tricky and subtle case that #445 handled. In the parlance of our new `:abstract_operations` target, if you try to `StaticCast<double, std::complex<int>>`, it will _compile_, but you'll get a warning. `std::complex<int>` has an `int` constructor, and the `double` will get _implicitly_ converted to `int` --- even though the static cast itself is an _explicit_ conversion! To fix this, we need to add two abstractions into our strategy generation. First, instead of calling it the "promoted common" type (which was always properly an implementation detail), we now call it what it is: the _"application rep"_ --- that is, the rep we should use for applying the conversion factor. Next, we replace `StaticCast` with `StaticCastSequence`, which allows us to detect problematic single-hop conversions and replace them with a more manageable sequence. For now, we're only handling the "real to complex" case, but these abstractions feel like the right ones. In general, we need to get our initial type to the conversion type, perform the conversion, and then get the result to the final type. Helps #349.
Fundamentally, this is all about the tests. We're just adding a few more test cases, and making them pass. What those tests showed us is interesting. The first test is pretty straightforward. It showed that we weren't doing the same multiply-then-divide operation for `std::complex<int>` that we would do for `int`. That was very easy to fix. The rest of the tests cover the tricky and subtle case that #445 handled. In the parlance of our new `:abstract_operations` target, if you try to `StaticCast<double, std::complex<int>>`, it will _compile_, but you'll get a warning. `std::complex<int>` has an `int` constructor, and the `double` will get _implicitly_ converted to `int` --- even though the static cast itself is an _explicit_ conversion! To fix this, we need to add two abstractions into our strategy generation. First, instead of calling it the "promoted common" type (which was always properly an implementation detail), we now call it what it is: the _"application rep"_ --- that is, the rep we should use for applying the conversion factor. Next, we replace `StaticCast` with `StaticCastSequence`, which allows us to detect problematic single-hop conversions and replace them with a more manageable sequence. For now, we're only handling the "real to complex" case, but these abstractions feel like the right ones. In general, we need to get our initial type to the conversion type, perform the conversion, and then get the result to the final type. Helps #349.
Avoid implicit conversions when explicitly converting a
Quantitytostd::complex<T>(ie withQuantity<T1>::as<std::complex<T2>>()).This warning was previously suppressed when building with Bazel due
to au headers being included as system headers (See
#440.)
Without this change, this function fails the
Quantitytest suite(
au/code/au/quantity_test.cc) with-Wfloat-conversion. This isbecause we're calling
std::complex<int>(const int&, const int&)with adouble. The
static_cast<Common>(value_)akastatic_cast<std::complex<int>>(value_)doesn't help us here, we'restill performing an implicit conversion when invoking the constructor.
More generally, when the rep type of the
std::complexdoes not matchthe rep type of the
Quantity, we're not explicitly casting theQuantity's rep to thestd::complex's, we're just calling thestd::complex<T>(const T&, const T&)constructor.GCC10:
Clang11:
Clang14: