In the below code we essentially assign the same value to 4 unsigned char variables in 4 different ways - but one of them using the initialization list issues a warning:

warning: narrowing conversion of '~(int)a' from 'int' to 'unsigned char' [-Wnarrowing]

#include <iostream>
#include <vector>

int main() {
    unsigned char a = 0b10101010u;      

    unsigned char b = ~a;      
    
    unsigned char c(~a);

    auto test = std::vector<unsigned char>{};
    test.emplace_back(~a);

    auto test2 = std::vector<unsigned char>{~a}; // THIS IS THE LINE GCC COMPLAINS ABOUT

    std::cout << std::hex 
        << size_t(b) << std::endl
        << size_t(c) << std::endl
        << size_t(test[0]) << std::endl
        << size_t(test2[0]) << std::endl;

    return 0;
}

Can someone explain why? Is this a bug in GCC? A language limitation?

You can play with the code here :) https://godbolt.org/z/To5ezrecc

1

There are 1 best solutions below

0
On

The warning is correct. In list-initialization (i.e. with braces) narrowing conversions are ill-formed, also in initialization of elements of a std::initializer_list. GCC is rather permissive here, since it only produces a warning instead of a hard error (as for example Clang does).

In ~a integral promotions are applied to a. In this case a is promoted to int, since it can represent all values of unsigned char.

Therefore ~a also has type int and because it is also not a constant expression, the conversion required to unsigned char for the std::initializer_list<unsigned char> constructor parameter is narrowing.

If you really intend to do the narrowing conversion cast explicitly:

auto test2 = std::vector<unsigned char>{static_cast<unsigned char>(~a)};

The same applies to other initialization methods using list-initialization (braces):

unsigned char d{~a};
unsigned char e = {~a};

Both are ill-formed as well.