Категория значений условного оператора - PullRequest
0 голосов
/ 16 ноября 2018

Рассмотрим следующий код:

int x;
int& f() {
  return x ? x : throw 0;
}

С gcc version 7.3.0 (Ubuntu 7.3.0-27ubuntu1~18.04) Я получаю следующую ошибку компиляции:

cannot bind non-const lvalue reference of type ‘int&’ to an rvalue of type ‘int’

Обратите внимание, что это прекрасно компилируется в clang. Вот (что я считаю) соответствующее утверждение из стандарта:

N4659 [8.16.2.1] (условный оператор):
Второй или третий операнд (но не оба) является (возможно, заключенным в скобки) throw-выражением (8.17); результат относится к типу и значению другой категории.

Насколько я понимаю, x - это lvalue, поэтому мне кажется, что clang - это правильно. Я ошибся?


Если бы мне пришлось сделать предположение, преобразование l-to-rvalue происходит потому, что два выражения в условном выражении не имеют один и тот же тип, а поскольку второе является броском, это преобразование должно быть прервано. Я не знаком с отправкой отчетов об ошибках, но, возможно, это был бы лучший форум для этого.
Вот некоторые (возможно) более полезные вопросы об условном операторе:
Почему эта функция возвращает ссылку на lvalue с аргументами rvalue?
Ошибка: lvalue требуется в этом простом C-коде? (Троичный с присвоением?)

1 Ответ

0 голосов
/ 16 ноября 2018

clang здесь верен, старое поведение состояло в том, чтобы безоговорочно преобразовывать значение в prvalue , которое, похоже, все еще реализует gcc.

Это был предмет DR 1560 , который был исправлен разрешением DR 1550 . DR 1560 говорит:

glvalue, появляющийся как один операнд условного выражения в который другой операнд является бросающим выражением, преобразуется в prvalue, независимо от того, как используется условное выражение:

Если второй или третий операнд имеет тип void, то lvalue-to-rvalue (7.1 [conv.lval]), массив-указатель (7.2 [conv.array]) и стандартная функция-указатель (7.3 [conv.func]) преобразования выполняются на втором и третьем операндах, и один из должно соблюдаться следующее:

  • Второй или третий операнд (но не оба) является выражением броска (18.1 [кроме. Броска]); результат имеет тип другой и является prvalue.

Это кажется неожиданным и неожиданным.

и DR 1550 изменили формулировку в [expr.cond] на то, что мы имеем сейчас:

Второй или третий операнд (но не оба) является (возможно, заключенным в скобки) бросающим выражением; результат имеет тип и значение категории другого. Условное выражение является битовым полем, если этот операнд является битовым полем.

Похоже, что gcc реализует старое поведение, в то время как clang реализует DR.

Это патч , который применил DR 1560 для лягушки . В него добавлен следующий тест:

namespace DR1560 { // dr1560: 3.5
  void f(bool b, int n) {
    (b ? throw 0 : n) = (b ? n : throw 0) = 0;
  }
  class X { X(const X&); };
  const X &get();
  const X &x = true ? get() : throw 0;
}

который на Годболте мы видим, что это не работает для gcc из-за:

error: lvalue required as left operand of assignment
4 |     (b ? throw 0 : n) = (b ? n : throw 0) = 0;
  |                    

У нас есть отчет об ошибке gcc для очень похожей проблемы со следующим сокращенным тестовым примером:

Я хотел устранить эту ошибку и предоставить более простой тестовый пример:

void blah(int&) {}

int main() {
    int i{};
    blah(true ? i : throw);
}

результат с gcc 6.0:

prog.cc: In function 'int main()':
prog.cc:6:15: error: invalid initialization of non-const reference of type 'int&' from an rvalue of type 'int'
     blah(true ? i : throw 0);
          ~~~~~^~~~~~~~~~~~~
...