编译时检查 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)>
的正数,它是 a
和 b
的倍数。鉴于 一些 公倍数 a
和 b
适合 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
但是我有几个问题。
- 文档说如果最小公倍数不可表示,行为是未定义的,而不仅仅是依赖于实现或其他东西。这是否意味着包含
f<35,48>()
之类内容的程序格式错误,欢迎编译器实际编译具有任意结果的所述代码?
- 有没有更简单的方法来完成我想做的事情?
我想我可以编写自己的 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)>
.
我想进行编译时检查,确保取两个数的最小公倍数不会溢出。难度:关于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)>
的正数,它是 a
和 b
的倍数。鉴于 一些 公倍数 a
和 b
适合 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
但是我有几个问题。
- 文档说如果最小公倍数不可表示,行为是未定义的,而不仅仅是依赖于实现或其他东西。这是否意味着包含
f<35,48>()
之类内容的程序格式错误,欢迎编译器实际编译具有任意结果的所述代码? - 有没有更简单的方法来完成我想做的事情?
我想我可以编写自己的 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)>
.