4

Consider the following snippet of code:

int main(){
    constexpr int x = -1;
    if(x >= 0){
        constexpr int y = 1<<x;
    }
}

GCC 7 (and probably other versions of GCC) refuses to compile this and says:

error: right operand of shift expression '(1 << -1)' is negative [-fpermissive]

I can guess where this may have come from: the constexpr declaration on y makes GCC evaluate y at compile time, where it might be negative. Removing the constexpr fixes the error.

However, is this undefined behavior by the standard? The condition is always false, so the value of y will never be used.

In my actual code, x is a template parameter, which may or may not be negative.

Bernard
  • 5,209
  • 1
  • 34
  • 64
  • Can you use `if constexpr`? – MikeMB Oct 01 '17 at 08:38
  • @MikeMB - Theoretically that might work. [But GCC still emits the same error](http://coliru.stacked-crooked.com/a/5302256c8fb5ccda). Probably a compiler bug. – StoryTeller - Unslander Monica Oct 01 '17 at 08:40
  • Negative leftshift is undefined behavior. During runtime this wouldn't be a problem, as the conditional prevents that from happening. But just as you said, the compiler has to evaluate the expression during compile time and is required to generate an error for any instance of UB. – MikeMB Oct 01 '17 at 08:40
  • @StoryTeller. Interesting. Any idea if that is supposed to happen? – MikeMB Oct 01 '17 at 08:41
  • @MikeMB - I've drawn a blank. My gut tells me an `if constexpr` should make the code well-formed. But I can't find verification for it. Maybe a better language lawyer will be able to. – StoryTeller - Unslander Monica Oct 01 '17 at 08:46
  • Clang and VC++ also complain about the expression being negative, even with `if constexpr`. – Bernard Oct 01 '17 at 08:54
  • @MikeMB - I asked. [`if constexpr`](https://stackoverflow.com/questions/46512248/why-doesnt-an-if-constexpr-make-this-core-constant-expression-error-dissappear) doesn't apply here. The answers on my question do make it clear how to apply it. – StoryTeller - Unslander Monica Oct 01 '17 at 13:19
  • @StoryTeller: Thanks, I'll have a look. Incedentially I just wrote an answer, that looks pretty much like what Dietmar Kühl suggested in your question. – MikeMB Oct 01 '17 at 13:27

2 Answers2

6

GCC complains because your definition of y is explicitly an ill-formed constexpr declaration. The initialzier violates [expr.const]/2, which specifies:

An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:

  • an operation that would have undefined behavior as specified in Clauses [intro] through [cpp] of this International Standard [ Note: including, for example, signed integer overflow (Clause [expr]), certain pointer arithmetic ([expr.add]), division by zero, or certain shift operations  — end note ] ;

So you can't use 1<<x to initialize y. It doesn't matter that the branch will never be executed and can be eliminated. GCC is still obligated to verify it's semantically correct.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
3

Just as StoryTeller explained, this is the expected behavior, as left shifting by a negative number is undefined behavior and an expression resulting in UB can't be used in a core constant expression (the fact that you don't try to access the result of that expression during runtime doesnT' change the fact that you require the compiler to evaluate it during compiletime).

If your branch actually depends on a template parameter you can work around this by using if constexpr:

template<int x>
constexpr int foo() {
    if constexpr (x >= 0) {
        constexpr int y = 1 << x;
        return y;
    }
    return 0;
}

Edit: As the answers to StoryTeller's question explain, this ONLY works inside a template and only if the conditional depends on the template parameter (more detailed explanation in the answers).

MikeMB
  • 20,029
  • 9
  • 57
  • 102