Skip to content

Conversation

@matthew-reynolds
Copy link
Contributor

@matthew-reynolds matthew-reynolds commented Jul 8, 2025

Avoid implicit conversions when explicitly converting a Quantity to
std::complex<T> (ie with Quantity<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 Quantity test suite
(au/code/au/quantity_test.cc) with -Wfloat-conversion. This is
because we're calling std::complex<int>(const int&, const int&) with a
double. The static_cast<Common>(value_) aka
static_cast<std::complex<int>>(value_) doesn't help us here, we're
still performing an implicit conversion when invoking the constructor.
More generally, when the rep type of the std::complex does not match
the rep type of the Quantity, we're not explicitly casting the
Quantity's rep to the std::complex's, we're just calling the
std::complex<T>(const T&, const T&) constructor.

GCC10:

In file included from au/quantity_test.cc:15:
./au/quantity.hh: In instantiation of 'constexpr auto au::Quantity<UnitT, RepT>::as(NewUnit) const [with NewRep = std::complex<int>; NewUnit = au::QuantityMaker<au::Miles>; <template-parameter-2-3> = void; UnitT = au::Feet; RepT = double]':
au/quantity_test.cc:528:49:   required from here
./au/quantity.hh:184:77: error: conversion from 'au::Quantity<au::Feet, double>::Rep' {aka 'double'} to 'int' may change value [-Werror=float-conversion]
  184 |             static_cast<NewRep>(detail::apply_magnitude(static_cast<Common>(value_), Factor{})));
      |                                                                             ^~~~~~
cc1plus: all warnings being treated as errors

Clang11:

In file included from au/quantity_test.cc:15:
./au/quantity.hh:184:77: error: implicit conversion turns floating-point number into integer: 'const au::Quantity<au::Feet, double>::Rep' (aka 'const double') to 'const std::__1::complex<int>::value_type' (aka 'const int') [-Werror,-Wfloat-conversion]
            static_cast<NewRep>(detail::apply_magnitude(static_cast<Common>(value_), Factor{})));
                                                        ~~~~~~~~~~~         ^~~~~~
au/quantity_test.cc:528:22: note: in instantiation of function template specialization 'au::Quantity<au::Feet, double>::as<std::__1::complex<int>, au::QuantityMaker<au::Miles>, void>' requested here
    const auto b = a.as<std::complex<int>>(miles);
                     ^
1 error generated.

Clang14:

In file included from au/quantity_test.cc:15:
./au/quantity.hh:184:77: error: implicit conversion turns floating-point number into integer: 'const au::Quantity<au::Feet, double>::Rep' (aka 'const double') to 'const std::complex<int>::value_type' (aka 'const int') [-Werror,-Wfloat-conversion]
            static_cast<NewRep>(detail::apply_magnitude(static_cast<Common>(value_), Factor{})));
                                                        ~~~~~~~~~~~         ^~~~~~
au/quantity_test.cc:528:22: note: in instantiation of function template specialization 'au::Quantity<au::Feet, double>::as<std::complex<int>, au::QuantityMaker<au::Miles>, void>' requested here
    const auto b = a.as<std::complex<int>>(miles);
                     ^
1 error generated.

@CLAassistant
Copy link

CLAassistant commented Jul 8, 2025

CLA assistant check
All committers have signed the CLA.

Copy link
Member

@chiphogg chiphogg left a 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>(
Copy link
Member

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.

Copy link
Contributor Author

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.

@chiphogg chiphogg merged commit ab2123e into aurora-opensource:main Jul 8, 2025
14 checks passed
@matthew-reynolds matthew-reynolds deleted the avoid_implicit_float_conversion branch July 8, 2025 17:31
chiphogg pushed a commit that referenced this pull request Jul 8, 2025
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.
chiphogg added a commit that referenced this pull request Jul 18, 2025
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.
chiphogg added a commit that referenced this pull request Jul 18, 2025
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants