The lambda defines a closure, which is an anonymous function together with an environment composed of captured variables.
The binding of captured variables happens at the moment the lambda is defined and uses the variables of the scope in which it was defined. This is early binding. As you said, early binding of a value and of a reference in your example.
As a consequence of early binding, if you would enclose your lambda call in its own scope with another x and y variable, the capture would still use the original x value and the reference to the y of the scope of the definition:
#include <iostream>
int main() {
auto x = 0, y = 0;
auto lambda = [x, &y]() { std::cout << x << ' ' << y; };
x = 1, y = 1;
{
auto x = 27, y = 2;
lambda(); // still outputs: 0 1
}
return 0;
}
Online demo
Another consequence is that if the variable captured by reference no longer exists when you invoke the lambda you have undefined behaviour.
Late binding would work differently: the capture would not bind to y in main(). It would bind to an identifier corresponding to y. The binding would be done when the lambda would be invoked. In my example, the binding would be done with the inner y, and the output would be 0 2.