Home > database >  C 20 lambda capture variadic arguments with prvalue and non-copyable lvalue
C 20 lambda capture variadic arguments with prvalue and non-copyable lvalue

Time:07-08

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

CodePudding user response:

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

CodePudding user response:

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
  • Related