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() {}
The code doesn't compiled in clang, it produces the following diagnostic:
error: constexpr variable 'z' must be initialized by a constant expression
MSVC crashed (according to godbolt) and gcc works fine. If a /= b
is simply replaced by a = a / b
then everybody accepts it. Why?
Who is right? Seems it's related to implicit narrowing conversion, but then why a = a / b
works?
This is simply a clang bug, if we look at compound assignment [expr.ass]p7 it is equivalent to assignment where E1
is evaluated only once:
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.
If we look in the restrictions on constant expression function requirement in [dcl.constexpr]p3 we don't have any restrictions on assignment:
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 ]
and nothing in [expr.const] adds restrictions for this specific case.
I contacted Richard Smith offline and he agreed this was a bug and said:
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.
Filed clang bug report and MSVC bug report
I've committed a patch to clang, which should fix the clang bug.
Some clang internal details:
In clang, the evaluation of constant expression is mostly handled in lib/AST/ExprConstant.cpp. In particular, compound assignment of integer is handled by CompoundAssignSubobjectHandler::found(APSInt &Value, QualType SubobjType)
. Before my patch, this function incorrectly rejected any non-integer RHS:
if (!SubobjType->isIntegerType() || !RHS.isInt()) {
// We don't support compound assignment on integer-cast-to-pointer
// values.
Info.FFDiag(E);
return false;
}
My patch fixes this by adding a branch for the RHS.isFloat()
case.
Note that similar problem does not happen when LHS is a floating-point and RHS is an integer, even though CompoundAssignSubobjectHandler
only handles the float <op>= float case, because the RHS is always promoted to a floating-point in this case.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With