Forward variadic argument to thread with lambda

369 views Asked by At

I'm having trouble finding how use std::thread() with lambdas. Namely, with having a variadic argument lambda receive the arguments by forwarding. As an example:

template<typename... T> 
auto foo(T&&... t){
    [](T&&... t){}(std::forward<T>(t)...); // (1)

    return std::thread( // (2) 
        [](T&&... t){},
        std::forward<T>(t)...
    );
}

auto bar(){
    int n=1;
    foo(1); (A)
    foo(n); (B)
}

A.1: compiles

A.2: compiles

B.1: compiles

B.2: doesn't compile

I don't understand:

  • Why the std::thread() (2) version using (B) doesn't compile and (A.2) does?
  • Why are there differences between (B.1) and (B.2)
2

There are 2 answers

0
max66 On BEST ANSWER

Try with

template<typename... T> 
auto foo(T&&... t){
    [](T&&... u){ }(std::forward<T>(t)...); // (1)

    return std::thread( // (2) 
        [](auto &&... u){ },
        std::forward<T>(t)...
    );
}

I mean: in the lambda you pass to std::thread(), auto && ... instead of T && .... Or, maybe, T const & ....

I'm not a language layer, and maybe someone can correct me, but it seems to me that there is a clash between universal references and r-value references. And the fact that std::thread() pass copies of the following arguments to the first one.

When you write

template<typename... T> 
auto foo(T&&... t)

the && are universal-references and T... become int, when you call foo(1), and int &, when you call foo(n).

Inside the function you get

[](int){ }(std::forward<int>(t)); // (1)

return std::thread( // (2) 
    [](int){ },
    std::forward<int>(t)...
);

in case f(0).

And this works because both lambda are waiting a int by copy and this ever works.

But when you call f(n), inside foo() you get

[](int &){ }(std::forward<int>(t)); // (1)

return std::thread( // (2) 
    [](int &){ },
    std::forward<int>(t)...
);

and this works for the first call, because the lambda wait a int left-reference variable (int &) and get a int left-reference variable, but doesn't works for the second call because std::thread pass a copy of std::forward<int>(t) (so a right-reference, int &&) to the lambda that wait for a left-reference.

0
Oktalist On

std::thread can't simply forward its arguments to the lambda by reference, because that would mean two threads potentially having simultaneous access to the arguments without synchronisation. So instead, std::thread creates temporary copies of the arguments, and passes those to the lambda. Because they are temporaries, they are rvalues. This works in A.2 because the lambda's parameter is an rvalue reference (because T is int, so T&& is int&&). It doesn't work in B.2, because the lambda's parameter is an lvalue reference (because T is int&, so T&& is int&). Like max66 says, you probably want to use auto&&... in your lambda so it can accept whatever is passed to it.