Составное назначение в функции constexpr: gcc vs. clang - PullRequest
0 голосов
/ 01 декабря 2018
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 работает?

Ответы [ 2 ]

0 голосов
/ 02 декабря 2018

Это просто ошибка лязга, если мы посмотрим на составное присваивание [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,
  • (3.3.2) оператор goto,
  • (3.3.3) метка идентификатора ([stmt.label]),
  • (3.3.4) определение переменной не литерального типа или статической или длительности хранения потока, для которой не выполняется инициализация.
    [Примечание: тело функции, котороеis = delete или = default не содержит ничего из вышеперечисленного.- конец примечания]

и ничего в [expr.const] добавляет ограничения для этого конкретного случая.

Я связался с Ричардом Смитом в автономном режиме, и онсогласился, что это ошибка, и сказал:

Да, это ошибка;этот код неверно принимает во внимание, что LHS может потребоваться преобразование в число с плавающей запятой перед вычислением.

0 голосов
/ 01 декабря 2018

Я передал патч для clang, который должен исправить ошибку clang.

Некоторые внутренние детали clang:

В 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;
    }

Мой патч исправляет это, добавляя ветвь для случая RHS.isFloat().

Обратите внимание, что подобная проблема неслучается, когда LHS - это число с плавающей точкой, а RHS - целое число, даже если CompoundAssignSubobjectHandler обрабатывает только случай с плавающей точкой = случай с плавающей точкой, поскольку в этом случае RHS всегда переводится в число с плавающей точкой.

...