Search code examples
c++templateslambdac++20constexpr

In GCC, inside a lambda, I can get constexpr variable from a non-constexpr template lambda, but not in Visual C++


This code compiles fine in gcc, but fail in Visual C++.

MCVE = https://godbolt.org/z/K7d5PEs65

int main(){
      int localVariable=0; //some local variable
      auto lamb=[&]<int s>() {
          if constexpr(s==5){localVariable=7;}
          ///^ may modify some local variables
          return 8;
      };
      constexpr int gOk=lamb.operator()<2>();
      [&]<int s2>() {
          ///vvv can edit only here
          constexpr int gFail=lamb.operator()<s2>();
          ///^^^ can edit only here
      }.operator()<2>();
}

Error C2131 expression did not evaluate to a constant

I need gFail to be a constexpr variable, while lamb sometimes create run-time side effect, depending on the value of template variable s.

Please give reference and workaround for Visual C++ case.

Edit: By the way, the reason that I mix between constexpr and non-constexpr inside the same lamb is to make calling convenient. By design, in user mind, he must know in advance that the instance of function lamb is constexpr or not.


Solution

  • I believe the problem you experience it is a bug/deficiency of MSVC, but I would let language lawyers provide the exact reasons (or refutation of my statement). Typically what I do in these situations is to pack the constexpr value inside an integral constant. This is something MSVC can chew:

    #include <iostream>
    #include <cstdlib>
    int main(){
          int localVariable=0; //some local variable
          auto lamb=[&]<int s>() {
              if constexpr(s==5){localVariable=7;}
              return std::integral_constant<int, 8>{};
          };
          constexpr int gOk=lamb.operator()<2>().value;
          [&]<int s2>() {
              auto tmp = lamb.operator()<s2>();
              constexpr int gFail=tmp; //tmp implicitly casted from integral_constant to int.
          }.operator()<2>();
    }
    

    tmp needs to be explicitly spelled out for MSVC. If you assign directly to gFail you get the same "not evaluate to a constant".

    Unfortunately this does not follow your "can edit only here" guide.


    Update: I am concerned that what you do won't actually work in any compiler. If you try using 5 as a template argument, you do actually get an error in all three compilers:

    #include <iostream>
    #include <cstdlib>
    int main(){
          int localVariable=0; //some local variable
          auto lamb=[&]<int s>() {
              if constexpr(s==5){localVariable=7;} //error
              ///^ may modify some local variables
              return 8;
          };
          constexpr int gOk=lamb.operator()<5>();
          std::cout << localVariable;
    }
    

    gcc gives:

    <source>: In function 'int main()':
    <source>:12:43:   in 'constexpr' expansion of 'lamb.main()::<lambda()>()'
    <source>:8:43: error: the value of 'lamb' is not usable in a constant expression
        8 |           if constexpr(s==5){localVariable=7;}
          |                              ~~~~~~~~~~~~~^~
    <source>:7:12: note: 'lamb' was not declared 'constexpr'
        7 |       auto lamb=[&]<int s>() {
          |            ^~~~
    Compiler returned: 1