Search code examples
c++lambdafunctional-programmingmetaprogramming

C++ Lambdas Capture Only Primitives By Value


I'm writing a function that returns a partial function, where passing in only some parameters of a function returns a new function with some of the parameters filled in.

I currently have this setup:

template< typename T, typename... Ts>
auto partial(auto func, T t)
{
    return [func,t](Ts... stuff) mutable {
      return func(t,stuff...);
    };
}

This works, but the problem is when t is passed by value vs by reference. If t is a temporary, this is great!

//lambda to add 3 numbers together
auto lambda = [](int x, int y, int z){return x + y + z;};

//supply one parameter, giving us a lambda that adds two numbers to 1
auto lamb = partial<int,int,int>(lambda,1);

//prints 6, great!
std::cout << lamb(2,3) << "\n"; 

Thing is, what if I want to pass a large parameter that is by reference?

std::vector<int> nums;

//lambda is supposed to add an int to a vector
auto lambda = [](std::vector<int>& vec, int y){vec.push_back(y);};

//return partial function that is supposed add an int to "nums"
//instead, it returns a partial function that adds an int to a copy of "nums"
auto lamb = partial<std::vector<int>&,int>(lambda,nums);

lamb(100); //should add 100 to my vector but doesn't
std::cout << nums[0] << "\n"; //definitely not 100

Even though I specified std::vector<int>& as my type, the lambda still copies the vector as opposed to copying the reference. I'm aware that I can capture by reference in the lambda, but that causes the previous example where I want to copy the primitive to fail.

So yeah, is there a workaround for this? I thought about writing an almost identical partial function that specifically only deals with references but having a function for values and one for references that both do similar things seems kind of cringe.


Solution

  • You could split your function into two depending on whether T is a reference type or not:

    template <typename T, typename Func>
    requires (!std::is_reference_v<T>)
    auto partial(Func&& func, T t)
    {
        return [func=std::forward<Func>(func),t](auto&&... stuff) mutable {
          return func(t, std::forward<decltype(stuff)>(stuff)...);
        };
    }
    
    template <typename T, typename Func>
    requires std::is_reference_v<T>
    auto partial(Func&& func, T&& t)
    {
        return [func=std::forward<Func>(func),&t=std::forward<T>(t)](auto&&... stuff) mutable {
          return func(std::forward<T>(t), std::forward<decltype(stuff)>(stuff)...);
        };
    }
    

    Demo


    I wouldn't though. As mentioned in the comments, the standard library already has std::bind_front which works almost exactly like your existing partial function. You can combine it with std::reference_wrapper to get the behavior you want:

    #include <iostream>
    #include <vector>
    #include <functional>
    
    
    int main() {
        std::vector<int> nums;
        
        //lambda is supposed to add an int to a vector
        auto lambda = [](std::vector<int>& vec, int y){vec.push_back(y);};
        
        auto lamb = std::bind_front(lambda, std::ref(nums));
        
        lamb(100);
        std::cout << nums[0] << "\n";
    }
    

    Demo