constexpr 函数中的复合赋值:gcc 与 clang

Compound assignment in constexpr function: gcc vs. clang

template<class A, class B> constexpr int f(A a, B b) {
    a /= b;
    return a;
}

constexpr int x = f(2, 2);   // a, b: int
constexpr int y = f(2., 2.); // a, b: double
constexpr int z = f(2, 2.);  // a: int, b: double //<-- BOOM!
constexpr int w = f(2., 2);  // a: double, b: int

int main() {}

代码未在 clang 中编译,它产生以下诊断信息:

error: constexpr variable 'z' must be initialized by a constant expression

MSVC 崩溃(根据 godbolt)而 gcc 工作正常。如果 a /= b 被简单地替换为 a = a / b 那么每个人都会接受它。为什么?

谁是对的?似乎与隐式缩小转换有关,但为什么 a = a / b 有效?

我已经 committed a patch 到 clang,这应该可以修复 clang 错误。

一些 clang 内部细节:

在clang中,常量表达式的求值主要在lib/AST/ExprConstant.cpp. In particular, compound assignment of integer is handled by CompoundAssignSubobjectHandler::found(APSInt &Value, QualType SubobjType)中处理。在我的补丁之前,这个函数错误地拒绝了任何非整数 RHS:

    if (!SubobjType->isIntegerType() || !RHS.isInt()) {
      // We don't support compound assignment on integer-cast-to-pointer
      // values.
      Info.FFDiag(E);
      return false;
    }

我的补丁通过为 RHS.isFloat() 案例添加一个分支来解决这个问题。

请注意,即使 CompoundAssignSubobjectHandler 仅处理 float = float 情况,当 LHS 为浮点数而 RHS 为整数时,也不会发生类似问题,因为 RHS 始终被提升为在这种情况下是一个浮点数。

这只是一个 clang 错误,如果我们查看复合赋值 [expr.ass]p7,它相当于 E1 仅计算一次的赋值:

The behavior of an expression of the form E1 op= E2 is equivalent to E1 = E1 op E2 except that E1 is evaluated only once. In += and -=, E1 shall either have arithmetic type or be a pointer to a possibly cv-qualified completely-defined object type. In all other cases, E1 shall have arithmetic type.

如果我们查看 [dcl.constexpr]p3 中对常量表达式函数要求的限制,我们对赋值没有任何限制:

The definition of a constexpr function shall satisfy the following requirements:

  • (3.1) its return type shall be a literal type;
  • (3.2) each of its parameter types shall be a literal type;
  • (3.3) its function-body shall not contain.
  • (3.3.1) an asm-definition,
  • (3.3.2) a goto statement,
  • (3.3.3) an identifier label ([stmt.label]),
  • (3.3.4) a definition of a variable of non-literal type or of static or thread storage duration or for which no initialization is performed.
    [ Note: A function-body that is = delete or = default contains none of the above. — end note  ]

[expr.const] 中没有任何内容针对此特定情况添加限制。

我离线联系了 Richard Smith,他同意这是一个错误并说:

Yes, it's a bug; that code is not correctly taking into account that the LHS could need conversion to floating point before the computation.

归档clang bug report and MSVC bug report