The problem is that std::lcm()
is doing computations using unsigned
of whatever type the arguments are. It uses using _Up = make_unsigned_t<common_type_t<_Mn, _Nn>>;
in my STL and converts all arguments to _Up
first. 50000 * 49999 = 2499950000 < 4294967296 = 2^32
does not cause an overflow and unsigned overflow would not be UB in any case.
But if you have template code for gcd
and lcm
like this without changing types: https://godbolt.org/z/zoxzsr45x
// GCD implementation
template<typename T, T m, T n>
constexpr T
gcd()
{
if constexpr (m == 0) {
return n;
} else if constexpr (n == 0) {
return m;
} else {
return gcd<T, n, T(m % n)>();
}
}
// LCM implementation
template<typename T, T m, T n>
constexpr T
lcm()
{
if constexpr (m != 0 && n != 0) {
return (m / gcd<T, m, n>()) * n;
} else {
return 0;
}
}
constinit auto t = lcm<int, 50000, 49999>();
int main(int, char **)
{
return 0;
}
Then the compiler fails with:
<source>: In instantiation of 'constexpr T lcm() [with T = int; T m = 50000; T n = 49999]':
<source>:27:42: required from here
<source>:21:37: warning: integer overflow in expression of type 'int' results in '-1795017296' [-Woverflow]
21 | return (m / gcd<T, m, n>()) * n;
| ~~~~~~~~~~~~~~~~~~~~~^~~
<source>:27:16: error: 'constinit' variable 't' does not have a constant initializer
27 | constinit auto t = lcm<int, 50000, 49999>();
| ^
<source>:27:42: in 'constexpr' expansion of 'lcm<int, 50000, 49999>()'
<source>:27:43: error: overflow in constant expression [-fpermissive]
27 | constinit auto t = lcm<int, 50000, 49999>();
| ^
In gcc-10 under Debian std::lcm
is defined as:
// std::abs is not constexpr, doesn't support unsigned integers,
// and std::abs(std::numeric_limits<T>::min()) is undefined.
template<typename _Up, typename _Tp>
constexpr _Up
__absu(_Tp __val)
{
static_assert(is_unsigned<_Up>::value, "result type must be unsigned");
static_assert(sizeof(_Up) >= sizeof(_Tp),
"result type must be at least as wide as the input type");
return __val < 0 ? -(_Up)__val : (_Up)__val;
}
/// Least common multiple
template<typename _Mn, typename _Nn>
constexpr common_type_t<_Mn, _Nn>
lcm(_Mn __m, _Nn __n) noexcept
{
static_assert(is_integral_v<_Mn>, "std::lcm arguments must be integers");
static_assert(is_integral_v<_Nn>, "std::lcm arguments must be integers");
static_assert(_Mn(2) == 2, "std::lcm arguments must not be bool");
static_assert(_Nn(2) == 2, "std::lcm arguments must not be bool");
using _Up = make_unsigned_t<common_type_t<_Mn, _Nn>>;
return __detail::__lcm(__detail::__absu<_Up>(__m),
__detail::__absu<_Up>(__n));
}
The cast to _Up
and return type of __absu
causes the UB to go away.