constexpr中允许的不确定行为 - 编译器错误?

发布于 2025-02-04 16:42:49 字数 983 浏览 1 评论 0原文

我的理解是:

  • C ++中签名的整数溢出是不确定的行为
  • 常数表达式不允许包含不确定的行为。

似乎以下内容不应该编译,实际上在我的编译器上也没有编译。

template<int n> struct S { };

template<int a, int b>
S<a * b> f()
{
  return S<a * b>();
}

int main(int, char **)
{
  f<50000, 49999>();
  return 0;
}

但是,现在我尝试以下内容:

#include <numeric>

template<int n> struct S { };

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

int main(int, char **)
{
  g<50000, 49999>();
  return 0;
}

尽管事实是

如果| m |,| n |或最不常见的倍数,则行为是不确定的 | M |和| n |不能表示类型的值 std :: common_type_t&lt; m,n&gt;

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

这是所有三个编译器中的错误吗?还是cppReference违反了LCM的行为,如果不能表示结果?

My understanding is that:

  • Signed integer overflow in C++ is undefined behavior
  • Constant expressions are not allowed to contain undefined behavior.

It seems to follow that something like the following should not compile, and indeed on my compiler it doesn't.

template<int n> struct S { };

template<int a, int b>
S<a * b> f()
{
  return S<a * b>();
}

int main(int, char **)
{
  f<50000, 49999>();
  return 0;
}

However, now I try the following instead:

#include <numeric>

template<int n> struct S { };

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

int main(int, char **)
{
  g<50000, 49999>();
  return 0;
}

Each of g++, clang, and MSVC will happily compile this, despite the fact that

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>.

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

Is this a bug in all three compilers? Or is cppreference wrong about lcm's behavior being undefined if it can't represent the result?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

清晰传感 2025-02-11 16:42:49

根据 [expr.const]/5 , [Into]通过[CPP]中指定的未定义行为“在恒定评估过程中不允许使用::

如果 e 满足核心常数表达的约束,但是评估 e 将评估通过[trene]在[库]中指定的未定义行为的操作,或va_start的调用宏([[cstdarg.syn]),是否未指定是否是 e 是核心常数表达式。

我们通常将其概括为“必须在需要恒定表达式的上下文中诊断出语言UB,但是库UB不一定需要诊断出来”。

该规则的原因是导致库UB的操作可能会或可能不会引起语言UB,并且即使在不引起语言UB的情况下,编译器也很难始终如一地诊断库UB。 (实际上,即使是某些形式的语言UB也没有被当前实现始终如一地诊断。)

有些人还将语言UB称为“硬” UB,而Library ub则为“软” UB,但我不喜欢这种术语,因为((我认为)它鼓励用户思考“不指定语言UB是否出现的代码”,因为它比“明确具有语言ub的代码”少了。但是在这两种情况下,结果是程序员都无法编写执行此类代码的程序并期望任何内容 都可以正常工作。

According to [expr.const]/5, "an operation that would have undefined behavior as specified in [intro] through [cpp]" is not permitted during constant evaluation, but:

If E satisfies the constraints of a core constant expression, but evaluation of E would evaluate an operation that has undefined behavior as specified in [library] through [thread], or an invocation of the va_­start macro ([cstdarg.syn]), it is unspecified whether E is a core constant expression.

We usually summarize this as "language UB must be diagnosed in a context that requires a constant expression, but library UB does not necessarily need to be diagnosed".

The reason for this rule is that an operation that causes library UB may or may not cause language UB, and it would be difficult for compilers to consistently diagnose library UB even in cases when it doesn't cause language UB. (In fact, even some forms of language UB are not consistently diagnosed by current implementations.)

Some people also refer to language UB as "hard" UB and library UB as "soft" UB, but I don't like this terminology because (in my opinion) it encourages users to think of "code for which it's unspecified whether language UB occurs" as somehow less bad than "code that unambiguously has language UB". But in both cases, the result is that the programmer cannot write a program that executes such code and expect anything to work properly.

垂暮老矣 2025-02-11 16:42:49

问题是std :: lcm()正在使用未签名进行计算
任何类型的参数是。它使用使用_up = make_unsigned_t&lt; common_type_t&lt; _mn,_nn&gt;;;;;;; 在我的stl中,然后将所有参数转换为_UP50000 * 49999 = 2499950000&lt; 4294967296 = 2^32不会导致溢出,并且在任何情况下都不是UB。

但是,如果您有gcdlcm这样的模板代码,而没有更改类型: 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;
}

然后,编译器失败了:

<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>();
      |                                           ^

在Debian std下的GCC-10 in debian std :: lcm被定义为:

  // 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));
    }

cast to cast to <<<<代码> _up 并返回__ absu的类型会导致UB消失。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文