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

622 Views Asked by At

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;
}
2

There are 2 best solutions below

3
康桓瑋 On BEST ANSWER

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

2
rturrado On

You could pass your lvalues using std::ref.

This way g would receive:

  • a std::unique_ptr<int>& for the lvalues.
  • a std::unique_ptr for the rvalues.

[Demo]

#include <functional>  // ref
#include <iostream>
#include <memory>

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

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

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

// Outputs:
//   5 6
//   5 7