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

4

5

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?

Zugzwang answered 17/11, 2014 at 14:21 Comment(2)
static_cast<unsigned char>(id % 3) ?Pryer
Can't you store the result in an array of ints? What will you use it for.Starbuck
F
9

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.

Fuddle answered 17/11, 2014 at 14:29 Comment(0)
O
3

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.

Ointment answered 17/11, 2014 at 15:0 Comment(2)
I guess there is a small mistake: operator%( LHS&& lhs, mod ) needs to be operator%( LHS&& lhs, mod_t ). Aside from that: This only works for me if I cast to char: id %mod% (char) 3, otherwise the 3 is also interpreted as a int by the compiler. Any way around that?Zugzwang
@Zugzwang bah, I figured that the constant was a char not an int. As horrible as it is, '\3' is the character constant 3. :/ Probably your best bet is constexpr on the id really, if it is really a constant.Ointment
C
2

You may use:

unsigned char array[] = {
    static_cast<unsigned char>(id % 3),
    static_cast<unsigned char>(id % 5)
};
Centriole answered 17/11, 2014 at 14:23 Comment(0)
R
2

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.

Rescript answered 17/11, 2014 at 14:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.