Skip to content

Commit 6ba04f8

Browse files
mpuszclaude
andcommitted
fix: MSVC constant evaluator rejects the optional-returning rho recursion
All three failing MSVC 14.5 CI legs die identically in `prime_factorization::first_base` with a bogus "failure was caused by a read of an uninitialized symbol: std::_Optional_destruct_base<uint64_t>::_Has_value" the moment `find_first_factor` reaches the Pollard's rho path (first hit: the codata2014 alpha particle-proton mass ratio mantissa). GCC and Clang evaluate the same code fine. `smallest_prime_factor_of_hard_composite` and `smallest_prime_in` now return a plain `std::uint64_t` with `0` (never a valid prime factor) as the every-parameterization-failed sentinel instead of `std::optional`. Verified on gcc-15 (full build, all tests) and covered by the existing `find_first_factor` static assertions, which exercise the rho path directly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 1b30694 commit 6ba04f8

1 file changed

Lines changed: 17 additions & 15 deletions

File tree

  • src/core/include/mp-units/ext

src/core/include/mp-units/ext/prime.h

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -532,34 +532,36 @@ constexpr auto first_n_primes_result = first_n_primes<N>();
532532

533533
// The smallest prime factor of a composite `n` all of whose prime factors exceed the
534534
// trial-division cutoff of `find_first_factor` below. Pollard's rho splits `n` recursively, with
535-
// Baillie-PSW certifying the leaves; the smallest leaf is the answer. Returns `std::nullopt` in
536-
// the (never yet observed) case that every rho parameterization fails for some split.
537-
// The two functions are mutually recursive free functions rather than a local lambda: only C++23
535+
// Baillie-PSW certifying the leaves; the smallest leaf is the answer. Returns `0` (never a valid
536+
// factor) in the (never yet observed) case that every rho parameterization fails for some split.
537+
// A sentinel is used instead of `std::optional` because MSVC's constant evaluator rejects the
538+
// optional-returning recursion with a bogus "read of an uninitialized symbol" error, and the two
539+
// functions are mutually recursive free functions rather than a local lambda because only C++23
538540
// consteval propagation would let a plain lambda call `consteval` functions with its parameter.
539-
[[nodiscard]] consteval std::optional<std::uint64_t> smallest_prime_factor_of_hard_composite(std::uint64_t n);
541+
[[nodiscard]] consteval std::uint64_t smallest_prime_factor_of_hard_composite(std::uint64_t n);
540542

541543
// `m` itself when it is prime, otherwise its smallest prime factor found via Pollard's rho.
542-
[[nodiscard]] consteval std::optional<std::uint64_t> smallest_prime_in(std::uint64_t m)
544+
[[nodiscard]] consteval std::uint64_t smallest_prime_in(std::uint64_t m)
543545
{
544-
return baillie_psw_probable_prime(m) ? std::optional{m} : smallest_prime_factor_of_hard_composite(m);
546+
return baillie_psw_probable_prime(m) ? m : smallest_prime_factor_of_hard_composite(m);
545547
}
546548

547-
[[nodiscard]] consteval std::optional<std::uint64_t> smallest_prime_factor_of_hard_composite(std::uint64_t n)
549+
[[nodiscard]] consteval std::uint64_t smallest_prime_factor_of_hard_composite(std::uint64_t n)
548550
{
549551
std::uint64_t factor = n;
550552
for (std::uint64_t c = 1u; factor == n; ++c) {
551553
if (c > 100u) {
552-
return std::nullopt;
554+
return 0u;
553555
}
554556
factor = pollard_rho_attempt(n, c);
555557
}
556558

557-
const auto lhs = smallest_prime_in(factor);
558-
const auto rhs = smallest_prime_in(n / factor);
559-
if (!lhs || !rhs) {
560-
return std::nullopt;
559+
const std::uint64_t lhs = smallest_prime_in(factor);
560+
const std::uint64_t rhs = smallest_prime_in(n / factor);
561+
if (lhs == 0u || rhs == 0u) {
562+
return 0u;
561563
}
562-
return *lhs < *rhs ? *lhs : *rhs;
564+
return lhs < rhs ? lhs : rhs;
563565
}
564566

565567
[[nodiscard]] consteval std::uintmax_t find_first_factor(std::uintmax_t n)
@@ -584,8 +586,8 @@ constexpr auto first_n_primes_result = first_n_primes<N>();
584586
// finds one in ~n^(1/4) steps where trial division needs up to ~n^(1/2) iterations and can
585587
// exceed compiler constexpr-loop limits (a semiprime of two ~500'000-sized primes needs ~266k
586588
// iterations, over GCC's default limit of 262144; hit by real CODATA values).
587-
if (const auto factor = smallest_prime_factor_of_hard_composite(n)) {
588-
return *factor;
589+
if (const std::uint64_t factor = smallest_prime_factor_of_hard_composite(n); factor != 0u) {
590+
return factor;
589591
}
590592

591593
// Pollard's rho failed on every tried parameterization (not observed in practice for any

0 commit comments

Comments
 (0)