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
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:
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