продвижение битового оператора на возвращаемое значение функции - PullRequest
3 голосов
/ 11 апреля 2019

С кодом:

#include <cstdint>

uint8_t a() { return 5; }

auto b() {
    uint8_t c = 6;
    c |= a();  // Warning here
    return c;
}

auto d() {
    uint8_t c = 6;
    uint8_t d = a();
    c |= d;
    return c;
}

g ++ предупреждает (используя -Wconversion):

<source>:7:12: warning: conversion from 'int' to 'uint8_t' {aka 'unsigned char'} may change value [-Wconversion]

Я предполагаю, что проблема связана с целочисленным повышением для битовых операций, но во второй функцииd () Сначала я присваиваю его переменной, а затем предупреждение не появляется.

(clang не предупреждает об этом, только g ++)

  • Может ли это быть решено путем приведения вместо этогоприсваивания переменной?
  • Почему при использовании функции она ведет себя по-разному?

Проводник компилятора с приведенным выше: https://godbolt.org/z/q9eMVT

1 Ответ

2 голосов
/ 12 апреля 2019

Я думаю, что это ошибка в GCC.На основе [expr.ass] / 7 выражение

x |= y

эквивалентно

x = x | y

, за исключением того, что a вычисляется только один раз.В побитовом ИЛИ, как и в других побитовых операциях, как также отмечалось в комментарии CoryKramer выше, обычные арифметические преобразования сначала будут выполняться для обоих операндов [expr.or] / 1 .Поскольку оба наших операнда имеют тип std::uint8_t, обычные арифметические преобразования представляют собой просто целые преобразования [expr.arith.conv] /1.5.Интегральное повышение на std::uint8_t должно означать, что оба операнда конвертируются в int [conv.prom] / 1 .Никаких дальнейших преобразований операндов не требуется, поскольку преобразованные типы обоих операндов одинаковы.Наконец, int, полученное из выражения |, затем преобразуется обратно в std::uint8_t и сохраняется в объекте, указанном в x [expr.ass] / 3 .Я предположил бы, что этот последний шаг - то, что вызывает предупреждение в некоторых случаях.Однако нет никакого способа, чтобы результат побитового логического ИЛИ между двумя std::uint8_t не мог быть представлен в std::uint8_t, независимо от того, преобразуем ли мы в int (который гарантированно будет больше *)1029 *) и обратно по пути.Таким образом, предупреждение здесь не нужно, и, вероятно, поэтому оно обычно не создается.

Единственное различие, которое я вижу между вашей первой и второй версией, заключается в том, что a() - это значение, а d - это значение.именующий.Однако категория значений не должна влиять на поведение обычных арифметических преобразований.Таким образом, предупреждение - ненужное или нет - должно, по крайней мере, производиться последовательноКак вы уже заметили, другие компиляторы, такие как clang, здесь не будут выдавать предупреждение.Кроме того, проблема, как представляется, странно специфична для участия вызовов функций в составном присваивании.Как отметил SergeyA в другом комментарии выше, GCC не будет выдавать предупреждение в эквивалентной форме c = c | a().Использование других типов значений вместо вызова функции, таких как, например, результат приведения литерала

c |= static_cast<std::uint8_t>(42);

, также не будет выдавать предупреждение в GCC .Но как только в правой части выражения появляется вызов функции, даже если результат вызова функции вообще не используется, например, в

c |= (a(), static_cast<std::uint8_t>(5));

предупреждениепоявляется .Таким образом, я бы пришел к выводу, что это ошибка в GCC, и было бы здорово, если бы вы написали отчет об ошибках ....

...