Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 237
Gena Bug Avatar asked Dec 01 '18 12:12

Gena Bug


2 Answers

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

like image 68
Shafik Yaghmour Avatar answered Sep 30 '22 07:09

Shafik Yaghmour


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.

like image 21
cpplearner Avatar answered Sep 30 '22 05:09

cpplearner