4

This is a silly question partly for fun.

I have a "well-defined" (or "saturated"?) bit-mask function

template <unsigned N>
uint32_t mask(uint32_t x) {
  const uint32_t MASK = N >= 32 ? ~uint32_t(0) : (uint32_t(1) << N) - 1;
  return x & MASK;
}

Expected behavior:

uint32_t x = ~uint32_t(0); // 0xFFFFFFFF
mask<8>(x) => 0x000000FF
mask<24>(x) => 0x00FFFFFF
mask<32>(x) => 0xFFFFFFFF
mask<1234>(x) => 0xFFFFFFFF

But I don't like to have an undefined code uint32_t(1) << 1234 within mask<1234>() though it is 100% harmless (it shouldn't be evaluated.) I don't want to see compiler warnings.

Please suggest me some bit-twiddling tricks (and template meta-programming?) to get rid of uint32_t(1) << 1234.

I have GCC 4.9 that (partially) supports C++14 and is smart enough to do constant folding etc

Update

Quoted from the N4140 draft of the C++14 spec:

5.8 Shift operators [expr.shift]

The behavior is undefined if the right operand is negative, or greater than or equal to the length in bits of the promoted left operand.

Do you folks have any non-template solution?

Community
  • 1
  • 1
nodakai
  • 7,773
  • 3
  • 30
  • 60
  • 1
    Wait for C++17 or later until you get [`static_if`](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3613.pdf). – nwp Aug 18 '15 at 08:51
  • There is no warning on newer GCC, it takes the `N >= 32 ?` into account. – ElderBug Aug 18 '15 at 09:23
  • @ElderBug Oops, my whole question might have been irrelevant... – nodakai Aug 18 '15 at 09:43
  • @ElderBug In fact my actual code involves something like `N >= 8*sizeof(in)` and then GCC cannot see that the bitshift won't be too large. – nodakai Aug 18 '15 at 09:51
  • Actually the linked question is not really that relevant here. That question is about shifting signed integers, but this is about shifting unsgingned integers. As I interpret the C++ standard left shifting unsigned integers is defined anyway. Maybe this GCC warning is bogus and it's only that which is fixed in newer GCC versions? – skyking Aug 18 '15 at 10:09
  • @skyking I checked (drafts of) the C++03 and C++14 specs myself and actually found this statement in the preceding section of one quoted in the above SO post: "The behavior is undefined if the right operand is negative, or greater than or equal to the length in bits of the promoted left operand." So `uint32_t(1) << 32` certainly causes an UB. – nodakai Aug 19 '15 at 02:23
  • @nodakai Thank's for pointing that out, I stand corrected. But better to quote that part than referring to the next paragraph. – skyking Aug 19 '15 at 05:14

2 Answers2

3

You could perhaps use partial specialization for that (if the warning bothers you), with inspiration from boost enable_if:

template <bool, unsigned N>
struct umask32 {
    static const uint32_t val = (uint32_t(1) << N) - 1;
};

template <unsigned N>
struct umask32<true, N> {
    static const uint32_t val = ~uint32_t(0);
};

template <unsigned N>
uint32_t mask(uint32_t x) {
    const uint32_t MASK = umask32<(N >= 32), N>::val;
    return x & MASK;
}

If N>=32 the second umask32 struct will be used and otherwise the first and consequently avoid the code where you shift by >=32 bits.

skyking
  • 13,817
  • 1
  • 35
  • 57
0

You could define a version for admitted N, and another for not admitted ones:

using my_unsigned = unsigned;

template <my_unsigned N, typename std::enable_if<(N < 8 * sizeof(my_unsigned))>::type* = nullptr >
uint32_t mask(uint32_t x) {
  const uint32_t MASK = (uint32_t(1) << N) - 1;
  return x & MASK;
}

template <my_unsigned N, typename std::enable_if<(N >= 8 * sizeof(my_unsigned))>::type* = nullptr >
uint32_t mask(uint32_t x) {
  return x;
}

Thanks to SFINAE, when N < 2^sizeof(unsigned), the first version will be instantiated, otherwise the compiler will pick the (dumb) second version.

Paolo M
  • 12,403
  • 6
  • 52
  • 73