Home > Net >  Lambda const capture by value bug in msvc?
Lambda const capture by value bug in msvc?

Time:09-19

This code fails to compile with MSVC but compiles with GCC and CLANG. The problem appears to be that capture by value is not a compile time const in MSVC.

#include <array>
int main()
{
    const int n = 41;
    auto lambda = [n] {  // Both n and nn should be compile time const
        const int nn {n 1};
        std::array<int,nn> h{};
        return h;
    };
    auto xarray = lambda();
    return xarray.size();
}

Error: error C2971: 'std::array': template parameter '_Size': 'nn': a variable with non-static storage duration cannot be used as a non-type argument

Compiler Explorer

Here's a C 20 standard discussion of lambda value capture showing compile time evaluation:

//[Example 6:
void f1(int i) {
  int const N = 20;
  auto m1 = [=]{
    int const M = 30;
    auto m2 = [i]{
      int x[N][M];          // OK: N and M are not odr-used
      x[0][0] = i;          // OK: i is explicitly captured by m2 and implicitly captured by m1
    };
  };
};

So this is a MSVC bug? Sure looks like it to me.

CodePudding user response:

[expr.prim.lambda]p11:

Every id-expression within the compound-statement of a lambda-expression that is an odr-use of an entity captured by copy is transformed into an access to the corresponding unnamed data member of the closure type.
[Note 7: An id-expression that is not an odr-use refers to the original entity, never to a member of the closure type. However, such an id-expression can still cause the implicit capture of the entity. — end note]

So, in const int nn {n 1};, if n is odr-used then it refers to the member of the lambda (which obviously isn't useable in a constant expression), and if it's not an odr-use, then it refers to the entity in main.

This isn't an ODR-use, because [basic.def.od]p4:

A variable x whose name appears as a potentially-evaluated expression E is odr-used by E unless

  • [...]
  • x is a variable of non-reference type that is usable in constant expressions and has no mutable subobjects, and E is an element of the set of potential results of an expression of non-volatile-qualified non-class type to which the lvalue-to-rvalue conversion ([conv.lval]) is applied, or
  • [...]

And n is usable in a constant expression. The n in the lambda refers to the n in main, and not the non-static data member of lambda that was initialized with 41, so it should work.

It looks like MSVC first checks if a variable is ODR-used and then substitutes all instances with the member access if it is (even where it is not ODR-used). The following also fails to compile with MSVC:

#include <array>

int main() {
    constexpr int n = 41;
    auto lambda = [=] {
        std::array<int, n> h{};
        return h;
        int i = *&n;  // ODR-use of n. Compiles if line is removed.
    };
}

This one also fails to compile for I suspect similar reasons:

    constexpr int n = 41;
    auto lambda = [n] {  // ODR-use of n to initialize the member
        std::array<int, n> h{};  // Not an ODR-use
        return h;
    };

CodePudding user response:

MSVC is wrong in rejecting the code(1st snippet) because n is a compile time constant and so nn is also a compile time constant as it is initialized with n.

Note that n inside the lambda body refers to the n outside the lambda and not the member of the closure type because that member is unnamed and isn't ord-used. This is also described here Why visual c (latest) and gcc 12.1 accepted hiding this init capture for lambda, while clang 14.0.0 not? (c 20).

Thus, n is a compile time constant and so is usable in constexpr context. That is, std::array<int, nn> h{}; is valid because nn is a compile time constant as it was initialized by another compile time constant n.


Here is the msvc bug report:

MSVC rejects valid code involving use of constexpr variable as initializer

  •  Tags:  
  • c
  • Related