Gena Bug
Gena Bug

Reputation: 255

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() {}

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?

Upvotes: 17

Views: 353

Answers (2)

cpplearner
cpplearner

Reputation: 15813

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.

Upvotes: 6

Shafik Yaghmour
Shafik Yaghmour

Reputation: 158469

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

Upvotes: 8

Related Questions