【问题标题】:Compound assignment in constexpr function: gcc vs. clangconstexpr 函数中的复合赋值:gcc 与 clang
【发布时间】:2019-05-03 09:12:13
【问题描述】:
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 /= ba = a / b 简单替换,那么每个人都接受它。为什么?

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

【问题讨论】:

  • 一定是cla​​ng的bug,a /= b应该等价于a = a / b
  • 我建议问问 Richard Smith,他在 clang 中实现了 constexpr 评估器。在我看来,他在实施这部分时有一个疏忽(这是罕见的!)。

标签: c++ language-lawyer constexpr


【解决方案1】:

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

E1 op= E2 形式的表达式的行为等价于 E1 = E1 op E2,只是 E1 只计算一次。 在 += 和 -= 中,E1 要么具有算术类型,要么是指向可能有 cv 限定的完全定义对象类型的指针。 在所有其他情况下,E1 应具有算术类型。

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

constexpr 函数的定义应满足以下要求:

  • (3.1) 它的返回类型应该是文字类型;
  • (3.2) 它的每个参数类型都应该是文字类型;
  • (3.3) 它的功能体不应包含。
  • (3.3.1) 一个 asm 定义,
  • (3.3.2) goto 语句,
  • (3.3.3) 一个标识符标签([stmt.label]),
  • (3.3.4) 非文字类型或静态或线程存储持续时间的变量的定义,或未执行初始化的变量。
    [ 注意: = delete 或 = default 的函数体不包含上述任何内容。 —— 尾注 ]

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

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

是的,这是一个错误;该代码没有正确考虑到 LHS 在计算之前可能需要转换为浮点数。

归档clang bug reportMSVC bug report

【讨论】:

    【解决方案2】:

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

    一些clang内部细节:

    在clang中,常量表达式的求值主要在lib/AST/ExprConstant.cpp中处理。特别是,整数的复合赋值由CompoundAssignSubobjectHandler::found(APSInt &amp;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() 案例添加一个分支来解决此问题。

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

    【讨论】:

    • 如您所说,判断意图始终是一项危险的工作。这种猜测背后的意图是什么?为什么不直接在 llvm bugzilla 上发布错误报告?
    • @Oliv 评论字面意思是为了表达意图,我认为 cpplearner 的解释很划算。为什么不只是发布错误报告?因为这是一个问答,而可能的解释使A更有趣。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-04
    • 2016-07-29
    • 2015-07-04
    • 2020-06-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多