编译时检查 lcm(a,b) 是否溢出

Compile time check that lcm(a,b) doesn't overflow

我想进行编译时检查,确保取两个数的最小公倍数不会溢出。难度:关于std::lcm,

The behavior is undefined if |m|, |n|, or the least common multiple of |m| and |n| is not representable as a value of type std::common_type_t<M, N>.

(来源: https://en.cppreference.com/w/cpp/numeric/lcm)

这是我想出的:

#include <type_traits>
#include <cstdint>
#include <numeric>

template<int8_t a, int8_t b,
  std::enable_if_t<
    (std::lcm(a,b) > 0 && (std::lcm(a,b) % a == 0) && (std::lcm(a,b) % b == 0)),
    std::nullptr_t> = nullptr>
void f() { }

这里的基本原理是条件检查 std::lcm(a,b) 产生了类型为 std::common_type_t<typeof(a), typeof(b)> 的正数,它是 ab 的倍数。鉴于 一些 公倍数 ab 适合 std::common_type_t<typeof(a), typeof(b)>,因此 最小 公倍数拟合,因此我们通过 std::lcm 的定义保证我们计算的实际上是 lcm。

我检查过这似乎能正常工作,例如

f<3, 5>();      // compiles
f<127, 127>();  // compiles
f<35, 48>();    // doesn't compile

但是我有几个问题。

  1. 文档说如果最小公倍数不可表示,行为是未定义的,而不仅仅是依赖于实现或其他东西。这是否意味着包含 f<35,48>() 之类内容的程序格式错误,欢迎编译器实际编译具有任意结果的所述代码?
  2. 有没有更简单的方法来完成我想做的事情?

我想我可以编写自己的 constexpr safe_lcm 函数来保证定义的行为和 return 0 在溢出的情况下,但这似乎是一个非常不优雅的解决方案,而且我'我必须非常努力地工作,以确保我涵盖了我可能提供给它的所有可能的算术类型组合。

更新:听起来常量表达式中不允许未定义的行为。因为我显然需要它是一个常量表达式才能在编译时使用它,这是否意味着我在这里安全?

更新 2:这似乎是对 no-undefined-behavior-in-constexpr 理论的明确打击:

template<int n> struct S {};

template<int8_t a, int8_t b>
S<std::lcm(a, b)> g()
{
  return S<std::lcm(a,b)>();
}

int main(int, char **)
{
  g<35, 48>(); // compiles :'(
  return 0;
}

假设a和b都是正数,则有关系

a * b == lcm(a,b) * gcd(a,b)

您可以利用它来发挥自己的优势。计算 gcd(a,b) 不会溢出。然后你可以检查是否

a / gcd(a,b) <= std::numeric_limits<CT>::max() / b

其中 CT 是 std::common_type_t<decltype(a),decltype(b)>.