Скрытое сужающее преобразование из int в uint8_t

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

#include <cstdint>

class A
{
public:

    explicit A(uint8_t p_a){ m_a = p_a; };
    uint8_t get_a() const {return m_a;}

private:

    uint8_t m_a;
};

int main()
{
    A a {0x21U};
    A aa{0x55U};

    uint8_t mask{a.get_a() | aa.get_a()};

    return 0;
}

Когда я пытаюсь скомпилировать это (gcc 5.4.0), я получаю следующую ошибку:

main.cpp: In function ‘int main()’:
main.cpp:20:28: warning: narrowing conversion of ‘(int)(a.A::get_a() | aa.A::get_a())’ from ‘int’ to ‘uint8_t {aka unsigned char}’ inside { } [-Wnarrowing]
     uint8_t mask{a.get_a() | aa.get_a()};

Я не очень понимаю, почему вообще есть какое-то сужение. Тип int нигде в моем коде не используется, все написано в терминах unsigned chars. Даже если я явно приведу к unsigned char, я получаю сообщение об ошибке:

uint8_t mask{static_cast<uint8_t>(a.get_a()) | static_cast<uint8_t>(aa.get_a())};

На самом деле, чтобы решить эту проблему, мне нужно удалить {}-инициализацию. Тогда это работает:

uint8_t mask = a.get_a() | aa.get_a();

Почему это необходимо?


person BobMorane    schedule 17.03.2018    source источник
comment
Возможный дубликат Целочисленное продвижение - каковы шаги   -  person Passer By    schedule 17.03.2018


Ответы (4)


Вы были близки с этим:

uint8_t mask{static_cast<uint8_t>(a.get_a()) | static_cast<uint8_t>(aa.get_a())};

Но a.get_a() и aa.get_a() уже uint8_t, поэтому приведение ничего не делает.

Это операция |, которая:

  • преобразует оба операнда в int (после того, как вы сможете с этим что-то сделать)
  • оценивается как int

Итак, это все выражение, которое вам теперь нужно преобразовать:

uint8_t mask{static_cast<uint8_t>(a.get_a() | aa.get_a())};

Вы также были правы, пытаясь отказаться от {}-инициализации, что, вероятно, сделал бы и я. Просто здесь не нужна его строгость.

uint8_t mask = a.get_a() | aa.get_a();

Это ясно, кратко и правильно.

person Lightness Races in Orbit    schedule 17.03.2018

Большинство двоичных арифметических операций, включая | побитовое ИЛИ, которое появляется здесь, заставляют их подвыражения повышаться, то есть они будут иметь по крайней мере int или unsigned int ранг.

С++ 17 [выражение], параграф 11:

Многие бинарные операторы, которые ожидают операнды арифметического или перечислительного типа, вызывают преобразования и возвращают типы результатов аналогичным образом. Цель состоит в том, чтобы получить общий тип, который также является типом результата. Этот шаблон называется обычными арифметическими преобразованиями, которые определяются следующим образом:

  • Если любой из операндов относится к типу перечисления с ограниченной областью действия,...

  • Если любой из операндов имеет тип long double,...

  • В противном случае, если любой из операндов равен double,...

  • В противном случае, если любой из операндов равен float,...

  • В противном случае интегральные преобразования должны выполняться для обоих операндов. Затем к продвинутым операндам должны применяться следующие правила: ...

встроенные рекламные акции вызывают изменение значений get_a() с uint8_t до int. Таким образом, результатом выражения | также является int, и его сужение для инициализации другого uint8_t является некорректным.

person aschepler    schedule 17.03.2018

Пример интегрального продвижения:

prvalues ​​ малых целочисленных типов (например, char) могут быть преобразованы в prvalue больших целочисленных типов (таких как int).

a.get_a() | aa.get_a() - это выражение prvalue

person Smit Ycyken    schedule 17.03.2018
comment
Это не отвечает на вопрос - person Passer By; 17.03.2018
comment
@PasserBy, почему бы и нет? Это определенно комплексное продвижение с выражением ценности. - person Smit Ycyken; 17.03.2018
comment
Это отвечает на вопрос - person anatolyg; 17.03.2018
comment
Повышение происходит по операндам |, а не по результату. - person aschepler; 17.03.2018
comment
Это не ответ на вопрос, но это первая половина ответа, который мог бы. - person Lightness Races in Orbit; 17.03.2018

Инициализация списка более строгая, поэтому вы получаете предупреждение.

uint8_t mask{a.get_a() | aa.get_a()};

Выражение внутри фигурных скобок

auto i = a.get_a() | aa.get_a(); // i is int

повышается до int, а поскольку int не может полностью вписаться в uint8_t, выдается предупреждение о сужении на основе это правило:

Если предложение инициализатора является выражением, неявные преобразования разрешены в соответствии с инициализацией копированием, за исключением случаев, когда они сужаются (как в инициализации списка) (начиная с C++11).

person Killzone Kid    schedule 17.03.2018
comment
Повышение происходит по операндам |, а не по результату. - person aschepler; 17.03.2018
comment
@aschepler: В некотором смысле это и то, и другое - ключевой момент в том, что результатом является int, а не uint8_t. - person Lightness Races in Orbit; 17.03.2018
comment
@aschepler Да, что приводит к тому, что i является int - person Killzone Kid; 17.03.2018