Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::thread taking lambda with ref arg fails to compile

I'm reading C++ concurrency in action. Chapter 2.4 describes a parallell_accumulate algorithm.

I tried - as a learning experiment - to replace the functor used there, with a generic lambda.

I've distilled the compilation error down to:

#include <thread>

template <typename T>
struct f {
    void operator() (T& result) { result = 1;}
};

int main() {
    int x = 0;
    auto g = [](auto& result) { result = 1; };

    std::thread(f<int>(), std::ref(x));  // COMPILES
    std::thread(g, std::ref(x));         // FAILS TO COMPILE
}

The error message:

 In file included from /usr/include/c++/4.9/thread:39:0,
                 from foo.cpp:1:
/usr/include/c++/4.9/functional: In instantiation of ‘struct std::_Bind_simple<main()::<lambda(auto:1&)>(std::reference_wrapper<int>)>’:
/usr/include/c++/4.9/thread:140:47:   required from ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = main()::<lambda(auto:1&)>&; _Args = {std::reference_wrapper<int>}]’
foo.cpp:13:31:   required from here
/usr/include/c++/4.9/functional:1665:61: error: no type named ‘type’ in ‘class std::result_of<main()::<lambda(auto:1&)>(std::reference_wrapper<int>)>’
       typedef typename result_of<_Callable(_Args...)>::type result_type;
                                                             ^
/usr/include/c++/4.9/functional:1695:9: error: no type named ‘type’ in ‘class std::result_of<main()::<lambda(auto:1&)>(std::reference_wrapper<int>)>’
         _M_invoke(_Index_tuple<_Indices...>)
         ^

My compiler version

$ g++ --version
g++ (Ubuntu 4.9.1-16ubuntu6) 4.9.1

Why do the compilation fail for the lambda but not the functor?

EDIT: How can I achieve what the functor is doing (assigning to a ref) with a generic lambda?

like image 786
Daniel Näslund Avatar asked Jan 26 '15 16:01

Daniel Näslund


People also ask

Can you pass a reference to a thread C++?

In c++11 to pass a referenceto a thread, we have std::ref(). std::thread t3(fun3, std::ref(x)); In this statement we are passing reference of x to thread t3 because fun3() takes int reference as a parameter.

What will happen if passing reference through std :: thread?

If you have used std::thread or std::bind , you probably noticed that even if you pass a reference as parameter, it still creates a copy instead. From cppreference, The arguments to the thread function are moved or copied by value.

How do you use std threads?

std::thread is the thread class that represents a single thread in C++. To start a thread we simply need to create a new thread object and pass the executing code to be called (i.e, a callable object) into the constructor of the object.


2 Answers

Another variation on the same theme that template argument deduction doesn't look through conversions.

The operator() of f<int> is

void operator() (int& result);

when you pass a reference_wrapper<int> to it, the conversion function (operator int &) is called, yielding a reference that can be bound to result.

The operator() of your generic lambda is

template<class T> void operator() (T& result) const;

If it were passed a reference_wrapper lvalue, it would deduce T as a reference_wrapper and then fail to compile on the assignment. (Assignment to a reference_wrapper reseats the "reference" rather than affects the value.)

But it fails even before that, because the standard requires that what you pass to std::thread must be callable with prvalues - and a non-const lvalue reference doesn't bind to a prvalue. This is the error you see - result_of contains no type because your functor is not callable for the argument type. If you attempt to do g(std::ref(x));, clang produces a rather clear error:

main.cpp:16:5: error: no matching function for call to object of type '(lambda at main.cpp:11:14)'
    g(std::ref(x));
    ^
main.cpp:11:14: note: candidate function [with $auto-0-0 = std::__1::reference_wrapper<int>] not viable: expects an l-value for 1st argument
    auto g = [](auto& result) { result = 1; };         
    ^

You should probably consider just capturing the relevant local by reference:

auto g = [&x]() { x = 1; };

Or if, for whatever reason, you must use a generic lambda, then you might take a reference_wrapper by value (or by const reference), and then unwrap it using get():

 auto g = [](auto result) { result.get() = 1; };

or perhaps add a std::bind which will unwrap the reference_wrappers, which lets template argument deduction do the right thing (hat tip @Casey):

 std::thread(std::bind(g, std::ref(x)));

or perhaps dispense with this reference_wrapper nonsense and write your lambda to take a non-owning pointer instead:

auto g = [](auto* result) { *result = 1; };
std::thread(g, &x);
like image 186
T.C. Avatar answered Nov 15 '22 21:11

T.C.


There are all sorts of problems involved with passing arguments through the "INVOKE(...)" family of functions std::async, std::bind, std::thread::thread. If you want to use an overloaded function name, or pass an lvalue reference, or heaven forbid pass an rvalue by reference, you're going to have a hard time. You'll come here to SO and one of us who has learned the relevant incantation will pass it on to you. Hopefully you'll remember it the next time it comes up.

I think the best practice since C++14 is to avoid the argument passing weirdness altogether by handling the arguments yourself and always giving the INVOKE functions a zero-argument functor that encapsulates the arguments required by the actual target function. Doing it yourself allows you to get exactly the semantics you intend without having to know every quirk and workaround and the fine distinctions in the interfaces of the INVOKE family functions. C++14 generalized lambda capture makes it quite simple to encapsulate any kind of function and set of arguments.

In your case, this approach would result in:

#include <thread>

template <typename T>
struct f {
    void operator() (T& result) { result = 1;}
};

int main() {
    int x = 0;
    auto g = [](auto& result) { result = 1; };

    std::thread([&]{ return f<int>{}(x); });
    std::thread([&]{ return g(x); });
}

which performs exactly as intended and is more readable.

std::reference_wrapper was great in the TR1 days when we needed it to pass references through std::bind, but its glory days are past and I think it's best avoided in modern C++.

like image 27
Casey Avatar answered Nov 15 '22 21:11

Casey