3

Is it legal to do the following in C11, C++11 and C++14?

static_assert(((-4) >> 1) == -2, "my code assumes sign-extending right shift");

or the C equivalent:

_Static_assert(((-4) >> 1) == -2, "my code assumes sign-extending right shift");

I don't know the rules for constant-expressions regarding whether you can use implementation-defined operations like the above.

I'm aware that the opposite, signed shift left of negative numbers, is undefined regardless of machine type.

Myria
  • 3,372
  • 1
  • 24
  • 42
  • Consider using `/` in your code to avoid this issue entirely. `(-4) / 2` is always `-2` regardless of shift definition. – M.M Oct 20 '14 at 20:31
  • @Matt: Although, it has round-toward-zero, whereas shift hardware frequently has round-down. So it easily could end up being very slow, and perhaps the round-down is what is desired. – Ben Voigt Oct 20 '14 at 20:33
  • @MattMcNabb: The reason my code is abusing signed `>>` is so that I can build a mask in the high bits. For example, `x >> 31` is equivalent to `x < 0 ? -1 : 0`. Sure, compilers already do that optimization for you, but sometimes it *doesn't* see what it's doing. Also, this is being done in an extremely-hot portion of my code. – Myria Oct 20 '14 at 20:36
  • @Myria try inspecting the assembly that your compiler is generating at maximum optimization level , to see if this is really an issue. On my system with `gcc -O3`, it does `sarl $31, %eax` for both. Maximum portability and maximum speed are often conflicting goals. – M.M Oct 20 '14 at 20:44
  • For what it's worth, it's trivial to write a macro to perform arithmetic, twos-complement right-shift even if the compiler does not support it, in a way that any good optimizing compiler will collapse down to just a normal shift on targets that naturally have the behavior you want. – R.. GitHub STOP HELPING ICE Oct 20 '14 at 22:21
  • The C equivalent remains `static_assert(...`, as long as `` is included. – Cubbi Oct 21 '14 at 03:25

2 Answers2

6

Yes. The C++11 standard says in [expr.shift]/3:

The value of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a non-negative value, the value of the result is the integral part of the quotient of E1/2^E2. If E1 has a signed type and a negative value, the resulting value is implementation-defined.

And nowhere in [expr.const]/2 it is said that such a shift, or expressions with implementation-defined values in general, are not constant expressions. You will thus get a constant expression that has an implementation-defined value.

Columbo
  • 60,038
  • 8
  • 155
  • 203
3

This is legal, insofaras it doesn't cause undefined behaviour.

The behaviour of right-shift of negative values is implementation-defined. The C and C++ standards do not guarantee it to be either arithmetic or logical; although so far as I know there has never been a CPU that didn't pick one or the other.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • 2
    It's only implementation-defined if the value starts negative. – chris Oct 20 '14 at 20:29
  • That's the key, you should have more than just one test to be sure you are getting the behavior you need. – Ben Voigt Oct 20 '14 at 20:29
  • @chris: In this question, it does. – Ben Voigt Oct 20 '14 at 20:29
  • 1
    @BenVoigt, It was just the phrasing of the answer, which made it sound like it was ID for every input. – chris Oct 20 '14 at 20:31
  • I guess adding in AND(the last bit shifted out, sign bit) wouldn't be unreasonable, it would cause round-toward-zero instead of round-toward-minus-infinity. – Ben Voigt Oct 20 '14 at 20:32
  • @BenVoigt I'm not sure adding more tests is too useful. *Technically* implementation-defined allows things like the compiler doing arithmetic shift in the morning and logical shift in the evening, although in practical terms one has to make the assumption that the compiler will document something sensible, otherwise nobody would use that compiler. – M.M Oct 20 '14 at 20:37