Skip to content

Capture by value with reference parameter modifies captured variable outside expression?

An answer to this question on Stack Overflow.

Question

When capturing hello as a reference, I expect to be able to modify hello outside my lambda function, but the following code ends with the same hello.

#include <iostream>
#include <algorithm>
using namespace std;
int main(){
  char hello[] = {"Hello, World!"};
  auto up = [&, hello] (char c) {
      if (!isupper(c))
      {
          c = toupper(c);
      }
  };
  for_each(hello, hello + sizeof(hello), up);
  cout<<hello<<endl;
}
Hello, World!

When passing hello by value with c as a reference parameter, I get my expected result.

#include <iostream>
#include <algorithm>
using namespace std;
int main(){
  char hello[] = {"Hello, World!"};
  auto up = [hello] (char& c) {
      if (!isupper(c))
      {
          c = toupper(c);
      }
  };
  for_each(hello, hello + sizeof(hello), up);
  cout<<hello<<endl;
}
cout<<hello<<endl;
HELLO, WORLD!

My understanding is when hello is captured by value, up will get a local copy of hello. Implying that whether c is a reference or not hello will not be modified. But in my example c is acting as a reference to the non-local copy of hello. I feel as if I'm missing something fundamental with references.

Answer

When compiled, your lambda expressions aren't actually capturing anything, so you might as well write:

auto up = [] (char c) {
  if (!isupper(c))
  {
      c = toupper(c);
  }
};

But, even if you do use [&], how do you think the compile knows that c is part of hello? Couldn't c just as easily be part of goodbye?

You can think of using &c as telling the compiler which array c belongs to.

Also, you're working in C++, these raw arrays are not recommended there. This is, perhaps, a better way:

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main(){
  std::string hello = {{"Hello, World!"}};
  auto up = [hello] (char& c) { c = toupper(c); };
  for_each(hello.begin(), hello.end(), up);
  cout<<hello<<endl;
}