fix: silence MSVC C4146/C4459/C4702/C4245 warnings exposed by build:all=True

CI builds the MSVC matrix with `user.mp-units.build:all=False` so neither tests nor examples
are compiled there.  Building with `all=True` on MSVC reveals a backlog of warnings that get
promoted to errors by `-WX`:

* `unit_magnitude.h(793)` C4245 — `find_first_factor` takes `uintmax_t`; cast `intmax_t N`
  to it explicitly before calling.
* `framework/unit_magnitude.h::mag_ratio` (MSVC ICE workaround) — `prime_factorization` only
  accepts positive arguments, but the workaround was passing the original `D` directly, so
  `mag_ratio<3, -4>` instantiated `prime_factorization<-4>`.  Normalize both halves to be
  positive and reconstruct the sign from the parity of `(N < 0) != (D < 0)`.
* `safe_int.h::div_overflows` / `neg_overflows` / `int_in_range` C4702 — MSVC's reachability
  analysis does not see the `if constexpr (is_signed_v<T>) return …;` branch as
  unconditional for signed instantiations, so the trailing fallback return looks unreachable.
  Restructure as `if constexpr (…) … else …;` so both branches share an explicit `else`.
* `safe_int.h::operator-` and `constrained.h::operator-` C4146 — unary minus on an unsigned
  operand is well-defined in C++ but MSVC flags it.  Suppress locally with the new
  `MP_UNITS_DIAGNOSTIC_IGNORE_UNARY_MINUS_UNSIGNED` macro added to `bits/hacks.h`
  (no-op on GCC/Clang, `#pragma warning(disable : 4146)` on MSVC).
* `framework/quantity.h` C4459 — the erased-handler trampolines used `void* p`, which shadows
  `si::unit_symbols::p` (pico).  Renamed to `void* self`, which is also the more accurate
  name for a `this`-trampoline.
* `bits/format.h` / `bits/text_tools.h` C4459 — short loop variables in trivial
  character-copy loops shadow various single-letter unit-symbol aliases brought into scope
  by `using namespace si::unit_symbols`.  These are genuinely benign; wrap each loop in
  `MP_UNITS_DIAGNOSTIC_IGNORE_SHADOW` rather than chasing the next colliding short name.
* `test/static/bounded_quantity_point_test.cpp` C4459 — local `auto t = time_of_day(...)`
  shadows `non_si::unit_symbols::t` (tonne).  Renamed to `auto tod = …` — the longer name
  reads better in any case.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mateusz Pusz
2026-05-24 00:00:56 +02:00
parent 4ec5b655b0
commit 2a4c34253b
9 changed files with 62 additions and 27 deletions
+6
View File
@@ -86,7 +86,13 @@ constexpr Out write_padded(Out out, std::basic_string_view<Char> s, int width, f
for (std::size_t j = 0; j < fill.size(); ++j) *out++ = fill[j];
};
write_fill(lpad);
MP_UNITS_DIAGNOSTIC_PUSH
// Loop variable shadows a `si::unit_symbols` short name when `using namespace si::unit_symbols`
// is in scope at the call site (MSVC C4459); the local variable here is the documented
// canonical name for this trivial loop.
MP_UNITS_DIAGNOSTIC_IGNORE_SHADOW
for (Char c : s) *out++ = c;
MP_UNITS_DIAGNOSTIC_POP
write_fill(rpad);
return out;
}
+2
View File
@@ -53,6 +53,7 @@
#define MP_UNITS_DIAGNOSTIC_IGNORE_MISSING_BRACES MP_UNITS_DIAGNOSTIC_IGNORE("-Wmissing-braces")
#define MP_UNITS_DIAGNOSTIC_IGNORE_NON_TEMPLATE_FRIEND MP_UNITS_DIAGNOSTIC_IGNORE("-Wnon-template-friend")
#define MP_UNITS_DIAGNOSTIC_IGNORE_SHADOW MP_UNITS_DIAGNOSTIC_IGNORE("-Wshadow")
#define MP_UNITS_DIAGNOSTIC_IGNORE_UNARY_MINUS_UNSIGNED
#define MP_UNITS_DIAGNOSTIC_IGNORE_UNREACHABLE
#define MP_UNITS_DIAGNOSTIC_IGNORE_ZERO_AS_NULLPOINTER_CONSTANT \
MP_UNITS_DIAGNOSTIC_IGNORE("-Wzero-as-nullpointer-constant")
@@ -79,6 +80,7 @@
#define MP_UNITS_DIAGNOSTIC_IGNORE_MISSING_BRACES
#define MP_UNITS_DIAGNOSTIC_IGNORE_NON_TEMPLATE_FRIEND
#define MP_UNITS_DIAGNOSTIC_IGNORE_SHADOW MP_UNITS_DIAGNOSTIC_IGNORE(4459)
#define MP_UNITS_DIAGNOSTIC_IGNORE_UNARY_MINUS_UNSIGNED MP_UNITS_DIAGNOSTIC_IGNORE(4146)
#define MP_UNITS_DIAGNOSTIC_IGNORE_UNREACHABLE MP_UNITS_DIAGNOSTIC_IGNORE(4702)
#define MP_UNITS_DIAGNOSTIC_IGNORE_ZERO_AS_NULLPOINTER_CONSTANT
#define MP_UNITS_DIAGNOSTIC_IGNORE_DEPRECATED
@@ -110,7 +110,12 @@ template<typename CharT, std::size_t N, std::size_t M, std::output_iterator<Char
// fallback to portable mode
return ::mp_units::detail::copy(txt.portable().begin(), txt.portable().end(), out);
#endif
MP_UNITS_DIAGNOSTIC_PUSH
// Short names like `ch` / `byte` shadow unit symbols (`imperial::ch`, `iec::byte`) when
// `using namespace …::unit_symbols` is in scope at the call site (MSVC C4459).
MP_UNITS_DIAGNOSTIC_IGNORE_SHADOW
for (const char8_t ch : txt.utf8()) *out++ = static_cast<char>(ch);
MP_UNITS_DIAGNOSTIC_POP
return out;
} else
MP_UNITS_THROW(std::invalid_argument("UTF-8 text can't be copied to CharT output"));
@@ -790,7 +790,7 @@ template<std::intmax_t N>
struct prime_factorization {
[[nodiscard]] static consteval std::intmax_t get_or_compute_first_factor()
{
return static_cast<std::intmax_t>(find_first_factor(N));
return static_cast<std::intmax_t>(find_first_factor(static_cast<std::uintmax_t>(N)));
}
static constexpr std::intmax_t first_base = get_or_compute_first_factor();
+6
View File
@@ -232,11 +232,17 @@ public:
return +x.value_;
}
MP_UNITS_DIAGNOSTIC_PUSH
// Generic operator- is intentionally instantiated with unsigned T as well (where unary minus
// returns the same unsigned type by C++ rules; MSVC C4146 flags this even though it is the
// documented behavior).
MP_UNITS_DIAGNOSTIC_IGNORE_UNARY_MINUS_UNSIGNED
[[nodiscard]] friend constexpr auto operator-(const constrained& x)
-> constrained<decltype(-std::declval<T>()), ErrorPolicy>
{
return -x.value_;
}
MP_UNITS_DIAGNOSTIC_POP
// -- Increment / decrement --
constexpr constrained& operator++()
@@ -990,13 +990,14 @@ class MP_UNITS_STD_FMT::formatter<mp_units::quantity<Reference, Rep>, Char> {
void on_text(const Char* begin, const Char* end) { mp_units::detail::copy(begin, end, out); }
// Static trampolines for erased_handler — tiny, do not drag in parse_quantity_specs.
static void on_number_erased(void* p) { static_cast<quantity_formatter*>(p)->on_number(); }
static void on_maybe_space_erased(void* p) { static_cast<quantity_formatter*>(p)->on_maybe_space(); }
static void on_unit_erased(void* p) { static_cast<quantity_formatter*>(p)->on_unit(); }
static void on_dimension_erased(void* p) { static_cast<quantity_formatter*>(p)->on_dimension(); }
static void on_text_erased(void* p, const Char* b, const Char* e)
// (Param renamed from `p` so it doesn't shadow `si::unit_symbols::p` (pico) on MSVC C4459.)
static void on_number_erased(void* self) { static_cast<quantity_formatter*>(self)->on_number(); }
static void on_maybe_space_erased(void* self) { static_cast<quantity_formatter*>(self)->on_maybe_space(); }
static void on_unit_erased(void* self) { static_cast<quantity_formatter*>(self)->on_unit(); }
static void on_dimension_erased(void* self) { static_cast<quantity_formatter*>(self)->on_dimension(); }
static void on_text_erased(void* self, const Char* b, const Char* e)
{
static_cast<quantity_formatter*>(p)->on_text(b, e);
static_cast<quantity_formatter*>(self)->on_text(b, e);
}
erased_handler erase()
@@ -78,8 +78,11 @@ template<std::intmax_t N, std::intmax_t D>
requires(N != 0)
constexpr UnitMagnitude auto mag_ratio = []() consteval {
constexpr auto abs_n = N < 0 ? -N : N;
constexpr auto abs_mag = detail::prime_factorization_v<abs_n> / detail::prime_factorization_v<D>;
if constexpr (N < 0)
constexpr auto abs_d = D < 0 ? -D : D;
// prime_factorization is only defined for positive N, so normalize both halves to be
// positive and reconstruct the overall sign from N and D independently.
constexpr auto abs_mag = detail::prime_factorization_v<abs_n> / detail::prime_factorization_v<abs_d>;
if constexpr ((N < 0) != (D < 0))
return detail::unit_magnitude<detail::negative_tag{}>{} * abs_mag;
else
return abs_mag;
+18 -6
View File
@@ -171,16 +171,20 @@ template<integral T>
[[nodiscard]] constexpr bool div_overflows(T lhs, T rhs) noexcept
{
if (rhs == 0) return true;
if constexpr (is_signed_v<T>) return lhs == std::numeric_limits<T>::min() && rhs == T{-1};
return false;
if constexpr (is_signed_v<T>)
return lhs == std::numeric_limits<T>::min() && rhs == T{-1};
else
return false;
}
// Returns true if -lhs overflows (only INT_MIN for signed).
template<integral T>
[[nodiscard]] constexpr bool neg_overflows(T v) noexcept
{
if constexpr (is_signed_v<T>) return v == std::numeric_limits<T>::min();
return v != T{0}; // negation of any non-zero unsigned overflows
if constexpr (is_signed_v<T>)
return v == std::numeric_limits<T>::min();
else
return v != T{0}; // negation of any non-zero unsigned overflows
}
// Extracts the underlying integral type from an arithmetic wrapper:
@@ -233,8 +237,10 @@ template<integral To, integral From>
return v <= static_cast<From>(std::numeric_limits<To>::max());
} else {
if (v < From{0}) {
if constexpr (!is_signed_v<To>) return false; // negative -> unsigned: never
return v >= static_cast<From>(std::numeric_limits<To>::min());
if constexpr (!is_signed_v<To>)
return false; // negative -> unsigned: never
else
return v >= static_cast<From>(std::numeric_limits<To>::min());
}
return v <= static_cast<From>(std::numeric_limits<To>::max());
}
@@ -391,11 +397,17 @@ public:
// -- Unary arithmetic --
[[nodiscard]] constexpr auto operator+() const -> safe_int<decltype(+value_), ErrorPolicy> { return +value_; }
MP_UNITS_DIAGNOSTIC_PUSH
// Generic operator- is intentionally instantiated with unsigned T as well (where unary minus
// returns the same unsigned type by C++ rules; MSVC C4146 flags this even though it is the
// documented behavior).
MP_UNITS_DIAGNOSTIC_IGNORE_UNARY_MINUS_UNSIGNED
[[nodiscard]] constexpr auto operator-() const -> safe_int<decltype(-value_), ErrorPolicy>
{
if (detail::neg_overflows(+value_)) handle_overflow("safe_int: negation overflow");
return -value_;
}
MP_UNITS_DIAGNOSTIC_POP
// -- Increment / decrement (use add/sub overflow check when T is integral) --
constexpr safe_int& operator++()
+12 -12
View File
@@ -837,9 +837,9 @@ static_assert(time_of_day(-3600.0 * s, midnight).quantity_from(midnight) == 8280
// Arithmetic: adding a duration that crosses midnight wraps correctly.
consteval bool time_of_day_wrap_arithmetic()
{
auto t = time_of_day(82800.0 * s, midnight); // 23:00
t += 7200.0 * s; // +2 h -> 01:00 next day
return t.quantity_from(midnight) == 3600.0 * s;
auto tod = time_of_day(82800.0 * s, midnight); // 23:00
tod += 7200.0 * s; // +2 h -> 01:00 next day
return tod.quantity_from(midnight) == 3600.0 * s;
}
static_assert(time_of_day_wrap_arithmetic());
@@ -894,17 +894,17 @@ static_assert(time_of_day(-3'600'000.0 * time_of_day_qs[ms], midnight).quantity_
consteval bool time_of_day_hour_increment()
{
auto t = time_of_day(23.0 * time_of_day_qs[h], midnight); // 23:00 = 82800 s
t += 2.0 * time_of_day_qs[h]; // +2 h → 25:00 = 90000 s → wraps to 3600 s
return t.quantity_from(midnight) == 3600.0 * s;
auto tod = time_of_day(23.0 * time_of_day_qs[h], midnight); // 23:00 = 82800 s
tod += 2.0 * time_of_day_qs[h]; // +2 h → 25:00 = 90000 s → wraps to 3600 s
return tod.quantity_from(midnight) == 3600.0 * s;
}
static_assert(time_of_day_hour_increment());
consteval bool time_of_day_minute_increment()
{
auto t = time_of_day(23.0 * time_of_day_qs[h] + 30.0 * time_of_day_qs[min], midnight); // 23:30 = 84600 s
t += 45.0 * time_of_day_qs[min]; // +45 min → 24:15 = 87300 s → wraps to 900 s
return t.quantity_from(midnight) == 900.0 * s;
auto tod = time_of_day(23.0 * time_of_day_qs[h] + 30.0 * time_of_day_qs[min], midnight); // 23:30 = 84600 s
tod += 45.0 * time_of_day_qs[min]; // +45 min → 24:15 = 87300 s → wraps to 900 s
return tod.quantity_from(midnight) == 900.0 * s;
}
static_assert(time_of_day_minute_increment());
@@ -1079,9 +1079,9 @@ static_assert(time_of_day(-3 * 86400.0 * s + 82800.0 * s, midnight).quantity_fro
// operator+=: start at 23:00 (82800 s), add 3 days + 2 hours (= 266400 s) → lands at 01:00 (3600 s).
consteval bool time_of_day_multiday_assign()
{
auto t = time_of_day(82800.0 * s, midnight); // 23:00
t += 3 * 86400.0 * s + 2 * 3600.0 * s; // +3 days 2 hours
return t.quantity_from(midnight) == 3600.0 * s;
auto tod = time_of_day(82800.0 * s, midnight); // 23:00
tod += 3 * 86400.0 * s + 2 * 3600.0 * s; // +3 days 2 hours
return tod.quantity_from(midnight) == 3600.0 * s;
}
static_assert(time_of_day_multiday_assign());