Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Making a list of generators (vector of lambdas) leads to very strange behavior with capture-by-reference

Tags:

c++

lambda

The following code is fairly similar to my actual application. Basically, I am trying to create a vector of functions so that I can generate a very large output in segments. I don't fully understand how the capture by reference [&] is working / should be working, and it's leading to some weird behavior.

#include <iostream>
#include <functional>
#include <vector>

using namespace std;

template <typename T>
T add(const T& a, const T& b) {
  return a + b;
}

template <typename T>
T add(const T& a, const T& b, T x) {
  return (add<T>(a,b)*x);
}


int main() {
  std::cout << "Hello World!\n";

  vector<function<long ()>> funks;
  for (long i = 1; i < 12; ++i) {

    //auto funky = std::bind(add<int>, i, i*i);

    std::cout << "PROOF: " << add(i, i*i, 2L) << std::endl;


    function<long ()> funky = [&]() -> long {
      long V = i;
      return add(V, V*V, 2L);
      };

    funks.push_back(funky);
  }

  for (auto&& x : funks) {
    std::cout << x() << " ";
  }
}

The output of running each x in funks is: [312, 312, 312 ... 312] corresponding to i = 13

However, I don't understand why this is the case, as I reinitialize V for each lambda, and the output should be [4, 12, 24, 40, ... 264]

It works when I change the capture clause to [=], but in my actual application the inputs will be quite large so I'd prefer to copy as few times as possible.

EDIT: I should clarify exactly what I'm looking for. I'd like to make a vector of N functions, [f_0, f_1, ... f_N], such that when calling f_i(), it calls F(V_i) for some large (known) function F and large V_i.

The reason I want to capture by reference is that I don't want to copy the V_i even once, but the result of my implementation is that every f_i() ends up calling F(V_N)

like image 536
Andy Dienes Avatar asked Mar 03 '23 14:03

Andy Dienes


1 Answers

You are auto-capturing i by reference in your loop, but it's only a binding. The value is not actually used until after the loop, when calling the lambda. At that point, each call takes the captured "reference to i" (which is actually undefined behavior, given that i is no longer in scope), dereferences it and stores the value in V. You know the rest.

What is strange is that you are insisting on using references to integer values. It's likely the compiler is doing its best to inline these and just use plain copies, but you should consider that when you have a reference, you can often expect additional instructions to be generated to dereference that to a value. For primitive types, just copy.

Oh, and definitely capture i by value!!! As a matter of style, I prefer to be explicit about my captures:

function<long ()> funky = [i]() -> long {
    long V = i;
    return add(V, V*V, 2L);
};
like image 100
paddy Avatar answered Apr 24 '23 23:04

paddy