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.
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
这只是一个 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.