0

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 Answers2

2

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

康桓瑋
  • 33,481
  • 5
  • 40
  • 90
  • Beautiful. Although I can't see where in `std::tuple` is the _magic_ to store unique pointer lvalues as references. I mean, `... args = std::forward(args)` would fail to copy unique pointer lvalues; but `args_tuple = std::tuple(std::forward(args)...)` would move rvalues and store them as values, but keep references to lvalues, wouldn't it? – rturrado Jul 07 '22 at 16:15
  • 1
    If `Args&&` is an lvalue, `Args` is actually an lvalue reference, and if `Args&&` is an rvalue, `Args` is not a reference type but a value type, so `tuple` just explicitly specify the storage type. – 康桓瑋 Jul 07 '22 at 16:25
  • Cool, thanks, I see. And that's something you lose if you do `... args = std::forward(args)`, because, for an `Args&`, `args =` will just do a copy (and a move for `Args&&`). – rturrado Jul 07 '22 at 16:42
0

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
rturrado
  • 7,699
  • 6
  • 42
  • 62