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

Gen*_*Bug 17 c++ language-lawyer constexpr

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() {}
Run Code Online (Sandbox Code Playgroud)

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

error: constexpr variable 'z' must be initialized by a constant expression
Run Code Online (Sandbox Code Playgroud)

MSVC坠毁(根据godbolt)并且gcc工作正常.如果a /= b简单地被替换,a = a / b则每个人都接受它.为什么?

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

Sha*_*our 8

这只是一个铿锵的错误,如果我们看一下复合赋值[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-definition,
  • (3.3.2)goto声明,
  • (3.3.3)标识符标签([stmt.label]),
  • (3.3.4)非文字类型或静态或线程存储持续时间或用于不进行初始化的变量的定义.
    [注意:函数体= = delete或= default不包含上述任何内容. - 结束说明]

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

我离线联系理查德史密斯,他同意这是一个错误并说:

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

提交的clang bug报告MSVC错误报告


cpp*_*ner 6

我已经提交了一个 clang 补丁,它应该修复clang bug.

一些铿锵的内部细节:

在clang中,常量表达式的评估主要在lib/AST/ExprConstant.cpp中处理.特别是,整数的复合赋值由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;
    }
Run Code Online (Sandbox Code Playgroud)

我的补丁通过为RHS.isFloat()案例添加分支来修复此问题.

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

  • @Oliv评论的字面意思是表达意图,我认为cpplearner的解释就是钱.为什么不发布错误报告?因为这是一个Q&A,可能的_explanation_使A更有趣. (3认同)
  • 正如你所说,判断一个意图总是一个危险的事业.这种猜测背后的意图是什么?你为什么不发布关于llvm bugzilla的bug报告? (2认同)