31

In reference to this question. The core constant expression that is used to initialize the constexpr variable y is ill-formed. So much is a given.

But if I try to turn the if into an if constexpr:

template <typename T>
void foo() {
    constexpr int x = -1;
    if constexpr (x >= 0){
        constexpr int y = 1 << x;
    }
}

int main(){
    foo<int>();
}

The error persists. With GCC 7.2 still giving:

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

But I thought that the semantic check should be left unpreformed on a discarded branch.

Making an indirection via a constexpr lambda does help, however:

template <typename T>
void foo(){
    constexpr int x = -1;
    constexpr auto p = []() constexpr { return x; };
    if constexpr (x >= 0){
        constexpr int y = 1<<p();
    }
}

The constexpr specifier on y seems to alter how the discarded branch is checked. Is this the intended behavior?


@max66 was kind enough to check other implementations. He reports that the error is reproducible with both GCC (7.2.0 / Head 8.0.0) and Clang (5.0.0 / Head 6.0.0).

Cœur
  • 37,241
  • 25
  • 195
  • 267
StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • Sounds like a compiler bug. Have you tried another implementation? – Shachar Shemesh Oct 01 '17 at 12:34
  • 1
    @ShacharShemesh - I myself haven't. But the OP of the post I linked reports that [Clang and MSVC behave the same](https://stackoverflow.com/questions/46510531/undefined-behavior-when-constexpr-evaluating-negative-bitshift#comment79975550_46510531). – StoryTeller - Unslander Monica Oct 01 '17 at 12:35
  • 2
    I confirm the problem with clang++ 3.8.1 – max66 Oct 01 '17 at 12:35
  • 3
    from wanbox I see that the problem still persist with both g++ (7.2.0 and Head 8.0.0) and clang++ (5.0.0 and Head 6.0.0). – max66 Oct 01 '17 at 12:45
  • I don't think that "The constexpr specifier on y seems to alter how the discarded branch is checked" because removing that `constexpr` (defining simply "int y = 1 << x;" I don't see errors anymore (I suppose is right, if `y` is initialized runtime) but I see a warning (unused variable `y`); so (if I'm not wrong) that branch is compiled. Or better, I think is right in the meaning that with `constexpr` `y` is initialized compile-time (so `1 << x` is immediately checked) and without is initialized run-time. – max66 Oct 01 '17 at 12:52
  • @max66 `int y = 1 << x;` is merely undefined behaviour. Adding `constexpr` makes it ill-formed because UB is not allowed in a constant expression. – Oktalist Oct 01 '17 at 19:01
  • When the question is of the sort "why did the compiler diagnose this ill-formed code that I hoped it won't diagnose", the answer is 90% of the time [temp.res]/8. – T.C. Oct 02 '17 at 03:31
  • @T.C. - Funny coincidence. I actually quoted that very paragraph under Dietmar's answer. Right about the time his amswer made me go "d'oh!". – StoryTeller - Unslander Monica Oct 02 '17 at 03:48

3 Answers3

20

The standard doesn't say much about the discarded statement of an if constexpr. There are essentially two statements in [stmt.if] about these:

  1. In an enclosing template discarded statements are not instantiated.
  2. Names referenced from a discarded statement are not required ODR to be defined.

Neither of these applies to your use: the compilers are correct to complain about the constexpr if initialisation. Note that you'll need to make the condition dependent on a template parameter when you want to take advantage of the instantiation to fail: if the value isn't dependent on a template parameter the failure happens when the template is defined. For example, this code still fails:

template <typename T>
void f() {
    constexpr int x = -1;
    if constexpr (x >= 0){
        constexpr int y = 1<<x;
    }
}

However, if you make x dependent on the type T it is OK, even when f is instantiated with int:

template <typename T>
void f() {
    constexpr T x = -1;
    if constexpr (x >= 0){
        constexpr int y = 1<<x;
    }
}
int main() {
    f<int>();
}
Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • As I comment to Kerrek, the example in my post is poor. I did verify that it persists after moving the code to a template. The base case still emits an error. Indirection does not. I've update the MCVE. Sorry for the inconvenience. – StoryTeller - Unslander Monica Oct 01 '17 at 13:00
  • 2
    @StoryTeller: I updated my response to clarify that only the *instantiation* of the template is affected. The error you get happens during *definition* of the template! – Dietmar Kühl Oct 01 '17 at 13:04
  • @DietmarKühl: Very good point -- the checking of non-dependent constructions is part of the template definition, not of any one template instantiation, so the non-instantiation of the discarded arm is actually a red herring. – Kerrek SB Oct 01 '17 at 13:06
  • 2
    Mmm.. Yes, I'm starting to see where my understanding went awry. [I also forgot that if no template specialization is valid, for any argument, the program is ill-formed no diagnostic required](https://timsong-cpp.github.io/cppwp/n4659/temp#res-8). This addressed my confusion in full. – StoryTeller - Unslander Monica Oct 01 '17 at 13:10
11

Note that for the statement discarded by Constexpr If:

the discarded statement can't be ill-formed for every possible specialization:

To fix the issue you can make the statement depending on the template parameter, e.g.

template<typename T, int X> struct dependent_value { constexpr static int V = X; };

template <typename T>
void foo() {
    constexpr int x = -1;
    if constexpr (x >= 0){
        constexpr int y = 1 << dependent_value<T, x>::V;
    }
}

LIVE

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
6

I'm not sure why you expect the branch to not be checked. The only time an if branch is "not checked" is when it is part of a template and not instantiated, as per [stmt.if]p2:

During the instantiation of an enclosing templated entity (Clause 17), if the condition is not value-dependent after its instantiation, the discarded substatement (if any) is not instantiated.

Your code doesn't seem to be in a situation where this applies.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • 1
    I admit to my [mcve] being poor. [But the error persists after moving the code into a template](https://wandbox.org/permlink/ASFlaJjyMbXqfRtt) (not with a lambda though). So I'll be updating my question. Sorry for invalidating this answer. – StoryTeller - Unslander Monica Oct 01 '17 at 12:56
  • 3
    @StoryTeller: No worries. It's good to explore new features like this in depth. – Kerrek SB Oct 01 '17 at 13:04