Search code examples
c++c++20delayed-execution

C++20 lambda capture variadic arguments with prvalue and non-copyable lvalue


I am trying to make a function that can stage arbitrary computation with lambda. This function takes in any function and parameter pack and stages the computation in a lambda function. A conflict occurs when Args contains both non-copyable lvalue reference and prvalue. The prvalue parameter will become dangling reference if we capture by reference. If we capture by value, we will try to call copy constructor of the lvalue reference. Neither will compile.

Is there a correct way to do it in C++20? I've read many related posts. Many of them give the capture by value + forwarding approach as below.

#include <iostream>
#include <memory>

using namespace std;

void g(std::unique_ptr<int>& a, std::unique_ptr<int> b) {
    if (a) {
        cout << *a << " ";
    }
    if (b) {
        cout << *b;
    }
}

template <typename F, typename ... Args>
auto f(F&& func, Args&& ... args) {
    // by reference
    auto lamb = [func, &args...]() {
        g(std::forward<Args>(args)...);
    };
    
    // by value
    /*auto lamb = [func, ... args = std::forward<Args>(args)]() mutable {
        g(std::forward<Args>(args)...);
    };*/
    return lamb;
}

int main()
{
    auto ptr = std::make_unique<int>(5);
    auto ptr2 = std::make_unique<int>(6);
    f(g, ptr, std::move(ptr2));
    auto l = f(g, ptr, std::make_unique<int>(7));
    l();
    return 0;
}

Solution

  • You can use a tuple to store args... and decide whether to store a value or a reference depending on whether the args... are lvalue or rvalue. Then use std::apply to forward parameters

    template <typename F, typename ... Args>
    auto f(F&& func, Args&& ... args) {
      // by value or by reference
      auto lamb = [func, args_tuple = 
        std::tuple<Args...>(std::forward<Args>(args)...)]() mutable {
        std::apply([func](auto&... args) {
          func(std::forward<Args>(args)...);
        }, args_tuple);
      };
      return lamb;
    }
    

    Demo