C++11: "narrowing conversion inside { }" with modulus

3.3k views Asked by At

I try to compile the following code with gcc and C++11 enabled:

unsigned int id = 100;
unsigned char array[] = { id % 3, id % 5 };

I get these warnings:

narrowing conversion of ‘(id % 3u)’ from ‘unsigned int’ to ‘unsigned char’ inside { } [-Wnarrowing]

see demo online

Is there a way to help the compiler find out that the result of id % 3 fits into an unsigned char?

4

There are 4 answers

0
Shafik Yaghmour On BEST ANSWER

In this specific case making id const or constexpr will fix the problem:

constexpr unsigned int id = 100;

since there is an exception for the case where you have a constant expression whose result after conversion will fit into the target type.

In the more general case you may also use static_cast to cast the result to unsigned char:

{ static_cast<unsigned char>( id % 3), static_cast<unsigned char>( id % 5) }
  ^^^^^^^^^^^                          ^^^^^^^^^^^

We can find he exception for constant expressions and narrowing conversions in the draft C++ standard section 8.5.4 List-initialization which says:

A narrowing conversion is an implicit conversion

and include the following bullet (emphasis mine):

  • from an integer type or unscoped enumeration type to an integer type that cannot represent all the values of the original type, except where the source is a constant expression whose value after integral promotions will fit into the target type.

Note, the wording changed from the original draft C++11 standard to what I quote above due to defect report 1449.

0
Jarod42 On

You may use:

unsigned char array[] = {
    static_cast<unsigned char>(id % 3),
    static_cast<unsigned char>(id % 5)
};
0
Bathsheba On

As id is an unsigned int, the type of id % 3 will also be an unsigned int.

Your compiler is helpfully warning you that unsigned char (which is 8 bits by the standard), might be too small to receive the unsigned int (which is at least 16 bits by the standard).

Of course you know better in this particular instance. Use static_cast<unsigned char>(id % ...) to tell the compiler that the narrowing conversion is safe.

2
Yakk - Adam Nevraumont On

It is a quirk of C++ that almost all mathematical operations convert their arguments to int.

Here is a sketch of a non-widening %mod% operator:

template<class T, class U,class=void> struct smallest{using type=T;};
template<class T, class U>
struct smallest<T,U,std::enable_if_t<(sizeof(T)>sizeof(U))>>{using type=U;};
template<class T,class U>using smallest_t=typename smallest<T,U>::type;

constexpr struct mod_t {} mod;
template<class LHS>struct half_mod { LHS lhs; };
template<class LHS>
constexpr half_mod<std::decay_t<LHS>> operator%( LHS&& lhs, mod_t ) { return {std::forward<LHS>(lhs)}; }
template<class LHS, class RHS>
constexpr smallest_t<LHS, std::decay_t<RHS>> operator%( half_mod<LHS>&& lhs, RHS&& rhs ) {
  return std::move(lhs.lhs) % std::forward<RHS>(rhs);
}

The result of a mod b should be the smallest of the two types, as it cannot be larger. Possibly some work should be done for signed/unsigned, but I'll punt and take the first.

So id %mod% 3 ends up being char.