1

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.

doug
  • 3,840
  • 1
  • 14
  • 18

2 Answers2

1

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

Jason
  • 36,170
  • 5
  • 26
  • 60
  • It is not obvious whether `n` inside the lambda body refers to the `n` outside the lambda or a member of the closure type. In the latter case MSVC is correct to reject. – user17732522 Sep 19 '22 at 04:58
  • @user17732522 It is clear that `n` inside the lambda body refers to the `n` outside the lambda and **not** the member of the closure type. See my [answer here](https://stackoverflow.com/a/73273317/12002570). In particular, case 2 in the above mentioned link. Basically, the member is **unnamed**. – Jason Sep 19 '22 at 05:17
  • It is only clear to a reader if they know that `n` is not odr-used in `const int nn {n+1};` and that a reference to an entity that is not an odr-use is not rewritten to a reference to the closure member (if any). My intention was that this should be explained in the answer. I do not see how your linked answer applies though. Yes, the member is unnamed, but in most cases (when it is an odr-use) mentioning the captured entity inside the lambda will still cause it to refer to the (unnamed) closure member. – user17732522 Sep 19 '22 at 05:23
  • @user17732522 I've added it in my answer now. Check out my updated answer. – Jason Sep 19 '22 at 05:24
1

[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;
    };
Artyer
  • 31,034
  • 3
  • 47
  • 75