18

Why the compiler complains if the the thread function delaration is changed to void thr(std::shared_ptr<Base>& p).Complie error:

gcc-10.1.0/include/c++/10.1.0/thread: In instantiation of 'std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(std::shared_ptr&); _Args = {std::shared_ptr&}; = void]': gcc-10.1.0/include/c++/10.1.0/thread:136:44: error: static assertion failed: std::thread arguments must be invocable after conversion to rvalues

136 | typename decay<_Args>::type...>::value,

Can someone explain me, step by step.

I would be grateful for any hint on this question.

#include <iostream>
#include <memory>
#include <thread>
#include <chrono>
#include <mutex>

struct Base
{
    Base() { std::cout << "  Base::Base()\n"; }
    // Note: non-virtual destructor is OK here
    ~Base() { std::cout << "  Base::~Base()\n"; }
};

struct Derived: public Base
{
    Derived() { std::cout << "  Derived::Derived()\n"; }
    ~Derived() { std::cout << "  Derived::~Derived()\n"; }
};

void thr(std::shared_ptr<Base> p)
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::shared_ptr<Base> lp = p; // thread-safe, even though the
                                  // shared use_count is incremented
    {
        static std::mutex io_mutex;
        std::lock_guard<std::mutex> lk(io_mutex);
        std::cout << "local pointer in a thread:\n"
                  << "  lp.get() = " << lp.get()
                  << ", lp.use_count() = " << lp.use_count() << '\n';
    }
}

int main()
{
    std::shared_ptr<Base> p = std::make_shared<Derived>();

    std::cout << "Created a shared Derived (as a pointer to Base)\n"
              << "  p.get() = " << p.get()
              << ", p.use_count() = " << p.use_count() << '\n';
    std::thread t1(thr, p), t2(thr, p), t3(thr, p);
    p.reset(); // release ownership from main
    std::cout << "Shared ownership between 3 threads and released\n"
              << "ownership from main:\n"
              << "  p.get() = " << p.get()
              << ", p.use_count() = " << p.use_count() << '\n';
    t1.join(); t2.join(); t3.join();

    std::cout << "after joining the threads\n" <<
     "  p.get() = " << p.get() << ", p.use_count() " <<p.use_count() << std::endl;
    std::cout << "All threads completed, the last one deleted Derived\n";
}

The outputs:

Base::Base()
  Derived::Derived()
Created a shared Derived (as a pointer to Base)
  p.get() = 0x57be80, p.use_count() = 1
Shared ownership between 3 threads and released
ownership from main:
  p.get() = 0, p.use_count() = 0
local pointer in a thread:
  lp.get() = 0x57be80, lp.use_count() = 4  
local pointer in a thread:
  lp.get() = 0x57be80, lp.use_count() = 3
local pointer in a thread:
  lp.get() = 0x57be80, lp.use_count() = 2
  Derived::~Derived()
  Base::~Base()
after joining the threads
  p.get() = 0, p.use_count() 0
All threads completed, the last one deleted Derived
sunshilong369
  • 646
  • 1
  • 5
  • 17
  • 5
    You do account for each thread function having *two* instances of the shared pointer? – Some programmer dude May 24 '20 at 12:27
  • 1
    Created +1. Caller passes as an argument to the function parameter +1, +1, +1 (for each of the 3 thread instances). The main routine does a reset, -1. The thr function makes a local copy, +1, +1, +1. – Eljay May 24 '20 at 12:31
  • @ Some programmer dude Why each thread function having two instance?I could not get the idea.I think it is one. – sunshilong369 May 24 '20 at 12:33
  • One instance as a parameter `std::shared_ptr p`. Another instance as a local variable `std::shared_ptr lp = p;`. – Eljay May 24 '20 at 12:34
  • @sunshilong369 first instance is the argument that is passed as value `void thr(std::shared_ptr p)`, second one is `std::shared_ptr lp = p`. – t.niese May 24 '20 at 12:34
  • @ Eljay Do you notice the `sleep_for()` in thread function? – sunshilong369 May 24 '20 at 12:35
  • Also, the behaviour is undefined, since `main()` resets `p` without synchronising, and the threads access and copy the passed `shared_ptr` (the copies and originals all refer to the same instance of `Derived`) both before and after locking the mutex. – Peter May 24 '20 at 12:35
  • 1
    If you want the threads to only have one single instance of the pointer, then either skip the `lp` variable, or move `p` into `lp`. – Some programmer dude May 24 '20 at 12:38
  • The count also depends a lot on the order of which the four threads run (including the main thread) and how they are preempted by each other. – Some programmer dude May 24 '20 at 12:39
  • @Some programmer dude Thank you.I see. Each thread function has two instances of the shared pointer indeed. **But I have saw the output is "lp.use_count() =5".How to explain it?** – sunshilong369 May 24 '20 at 12:40
  • 2
    What you see for `lp.use_count()` depends on how the threads a scheduled, the numbers shown in your current code for `lp.use_count()` in your threads are not predictable. – t.niese May 24 '20 at 12:42
  • 2
    Yes, I noticed the `sleep_for`. The "How to explain it?" for the use_count of 5 is because there is a race condition amongst all 3 of the spawned threads. – Eljay May 24 '20 at 12:45
  • You would need `t1(thr, std::ref(p))` for your edited question. – Jarod42 May 24 '20 at 12:51
  • @Eljay I see.Thank you.Why the compiler complains if the the thread function declaration is changed to `void thr(std::shared_ptr& p)` – sunshilong369 May 24 '20 at 12:51
  • @Jarod42 Could you explain it detailly?Why `std::shared_ptr& p` is not right? – sunshilong369 May 24 '20 at 12:52
  • thread arguments are copied. to pass reference, you have to wrap it in `std::reference_wrapper`. `std::ref`/`std::cref` does that. – Jarod42 May 24 '20 at 12:54
  • @sunshilong369, you posted *part* of the error message you’re asking about. What is the rest of the compiler error message? – NicholasM May 24 '20 at 12:55
  • @NicholasM All error messages were posted just now. – sunshilong369 May 24 '20 at 12:59

2 Answers2

33

The arguments passed to the std::thread constructor will be copied and then forwarded as rvalues to the function that runs in the new thread. So when you create a std::thread like this:

std::thread t1(thr, p)

the argument p will be copied, then forwarded as an rvalue. If the function thr expects an lvalue reference then it can't be called with an rvalue.

The static assertion is telling that you that you can't call thr(shared_ptr<Base>&) with an rvalue shared_ptr<Base>. (Before I added the static assertion you just got a horrible template instantiation error from deep inside the guts of std::thread, now the idea is that it tells you what's wrong in English).

The solution to passing a reference into the function is to use the std::ref function to create a reference_wrapper object:

std::thread t1(thr, std::ref(p))

This will create a std::reference_wrapper<std::shared_ptr<Base>> which gets copied and forwarded to thr as an rvalue, and then that rvalue can be converted to shared_ptr<Base>& to initialize the parameter of the thr function.

This is also clearly explained at https://en.cppreference.com/w/cpp/thread/thread/thread#Notes

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • "the argument p will be copied, then **forwarded as an rvalue**. " Why forwarded the arguments as rvalues? For what purpose? – John Apr 10 '22 at 12:12
  • Because new copies of the arguments have just been made, so it would be misleading and unhelpful to pass them as lvalues. Why would you want to get passed a reference to some temporary variable created somewhere on the heap, that no other thread has a reference to? – Jonathan Wakely Apr 11 '22 at 19:03
  • “ Why would you want to get ***passed a reference to some temporary*** variable created somewhere on the heap, that no other thread has a reference to”. Sorry, I still **partially** understand you. What messes me is that even if I pass a temporary object as the parameter of `std::thread`, it still would be copied(i.e. by move constructor) since `std::thread`'s parameters are passed by value, and the parameter used in the implementation of the `std::thread` **is not temporary objects any more** (i.e. they are objects with names indeed ). – John Apr 12 '22 at 02:45
  • _"since `std::thread`'s parameters are passed by value"_ no they aren't, they're passed by forwarding reference. _"(i.e. they are objects with names indeed)"_ but that's an implementation detail that is invisible to the user. The point is that the `thread` constructor arguments are on the caller's stack when the `std::thread` is created, and they might not still exist when the supplied function runs in a new thread of execution. To avoid dangling references, `std::thread` makes **rvalue copies** of all its arguments (so that you must be explicit and careful if you want to pass by reference). – Jonathan Wakely Apr 13 '22 at 11:21
  • The call to the supplied function in the new thread of execution is passed **temporary objects** i.e. rvalues, that are created by copying the arguments passed to the `std::thread` constructor. The fact that the copies actually have to be stored somewhere by the implementation and then forwarded to the function is an implementation detail. The observable API is that the arguments are copied to create temporaries and those temporaries are passed to the function in the new thread. It's not that hard to understand IMHO. Forwarding them as lvalues would not make any sense. – Jonathan Wakely Apr 13 '22 at 11:25
  • Although this doesn't apply to this particular question (but it shows up high enough in searches for me to leave a comment), checking the number of arguments passed is a good idea if either references aren't involved, or it still appears after adding `std::ref` to all references involved. I switched to `std::bind` before I got a useful error message. I even switched the function call to all references and used `std::ref` for science. The entire function call consisted of pointers, so `std::ref` wasn't initially applicable to my case. I was missing an argument from the function call instead – Zoe Nov 02 '22 at 20:48
2

Summary: how to properly pass references to the std::thread constructor via std::ref() and std::cref() wrappers

Wrap reference parameters with std::ref() and const reference parameters with std::cref():

// Function prototypes
void foo(Data& data);           // takes a reference parameter
void foo2(const Data& data);    // takes a const reference parameter

// std::thread constructor calls with proper `std::ref()` and `std::cref()` 
// wrappers
std::thread t1 = std::thread(foo, std::ref(data));
std::thread t2 = std::thread(foo2, std::cref(data));

Problem

I also couldn't seem to get reference parameters to work with the std::thread() constructor. I was about to make this a separate Q&A but discovered this question here instead. I'd like to post the full error output from g++ below to make this question more easily searchable.

My example:

#include <cstdint>  // For `uint8_t`, `int8_t`, etc.
#include <cstdio>   // For `printf()`
#include <iostream>  // For `std::cin`, `std::cout`, `std::endl`, etc.
#include <thread>

struct Data
{
    int i = 7;
};

// Accepts a reference
void foo(Data& data)
{
    printf("data.i = %i\n", data.i);
}

int main()
{
    printf("`std::thread` test\n");

    Data data;
    std::thread t1 = std::thread(foo, data);  // <========= here
    t1.join();

    return 0;
}

Sample build command and output:

eRCaGuy_hello_world/cpp$ g++ -Wall -Wextra -Werror -O3 -std=c++17 -pthread std_thread__pass_parameter_by_reference_to_constructor.cpp -o bin/a && bin/a
In file included from std_thread__pass_parameter_by_reference_to_constructor.cpp:87:
/usr/include/c++/8/thread: In instantiation of ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(Data&); _Args = {Data&}; <template-parameter-1-3> = void]’:
std_thread__pass_parameter_by_reference_to_constructor.cpp:105:43:   required from here
/usr/include/c++/8/thread:120:17: error: static assertion failed: std::thread arguments must be invocable after conversion to rvalues
  static_assert( __is_invocable<typename decay<_Callable>::type,
                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
           typename decay<_Args>::type...>::value,
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/8/thread: In instantiation of ‘struct std::thread::_Invoker<std::tuple<void (*)(Data&), Data> >’:
/usr/include/c++/8/thread:132:22:   required from ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(Data&); _Args = {Data&}; <template-parameter-1-3> = void]’
std_thread__pass_parameter_by_reference_to_constructor.cpp:105:43:   required from here
/usr/include/c++/8/thread:250:2: error: no matching function for call to ‘std::thread::_Invoker<std::tuple<void (*)(Data&), Data> >::_M_invoke(std::thread::_Invoker<std::tuple<void (*)(Data&), Data> >::_Indices)’
  operator()()
  ^~~~~~~~
/usr/include/c++/8/thread:241:4: note: candidate: ‘template<long unsigned int ..._Ind> decltype (std::__invoke((_S_declval<_Ind>)()...)) std::thread::_Invoker<_Tuple>::_M_invoke(std::_Index_tuple<_Ind ...>) [with long unsigned int ..._Ind = {_Ind ...}; _Tuple = std::tuple<void (*)(Data&), Data>]’
    _M_invoke(_Index_tuple<_Ind...>)
    ^~~~~~~~~
/usr/include/c++/8/thread:241:4: note:   template argument deduction/substitution failed:
/usr/include/c++/8/thread: In substitution of ‘template<long unsigned int ..._Ind> decltype (std::__invoke(_S_declval<_Ind>()...)) std::thread::_Invoker<std::tuple<void (*)(Data&), Data> >::_M_invoke<_Ind ...>(std::_Index_tuple<_Ind ...>) [with long unsigned int ..._Ind = {0, 1}]’:
/usr/include/c++/8/thread:250:2:   required from ‘struct std::thread::_Invoker<std::tuple<void (*)(Data&), Data> >’
/usr/include/c++/8/thread:132:22:   required from ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(Data&); _Args = {Data&}; <template-parameter-1-3> = void]’
std_thread__pass_parameter_by_reference_to_constructor.cpp:105:43:   required from here
/usr/include/c++/8/thread:243:29: error: no matching function for call to ‘__invoke(std::__tuple_element_t<0, std::tuple<void (*)(Data&), Data> >, std::__tuple_element_t<1, std::tuple<void (*)(Data&), Data> >)’
    -> decltype(std::__invoke(_S_declval<_Ind>()...))
                ~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~
In file included from /usr/include/c++/8/tuple:41,
                 from /usr/include/c++/8/bits/unique_ptr.h:37,
                 from /usr/include/c++/8/memory:80,
                 from /usr/include/c++/8/thread:39,
                 from std_thread__pass_parameter_by_reference_to_constructor.cpp:87:
/usr/include/c++/8/bits/invoke.h:89:5: note: candidate: ‘template<class _Callable, class ... _Args> constexpr typename std::__invoke_result<_Functor, _ArgTypes>::type std::__invoke(_Callable&&, _Args&& ...)’
     __invoke(_Callable&& __fn, _Args&&... __args)
     ^~~~~~~~
/usr/include/c++/8/bits/invoke.h:89:5: note:   template argument deduction/substitution failed:
/usr/include/c++/8/bits/invoke.h: In substitution of ‘template<class _Callable, class ... _Args> constexpr typename std::__invoke_result<_Functor, _ArgTypes>::type std::__invoke(_Callable&&, _Args&& ...) [with _Callable = void (*)(Data&); _Args = {Data}]’:
/usr/include/c++/8/thread:243:29:   required by substitution of ‘template<long unsigned int ..._Ind> decltype (std::__invoke(_S_declval<_Ind>()...)) std::thread::_Invoker<std::tuple<void (*)(Data&), Data> >::_M_invoke<_Ind ...>(std::_Index_tuple<_Ind ...>) [with long unsigned int ..._Ind = {0, 1}]’
/usr/include/c++/8/thread:250:2:   required from ‘struct std::thread::_Invoker<std::tuple<void (*)(Data&), Data> >’
/usr/include/c++/8/thread:132:22:   required from ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(Data&); _Args = {Data&}; <template-parameter-1-3> = void]’
std_thread__pass_parameter_by_reference_to_constructor.cpp:105:43:   required from here
/usr/include/c++/8/bits/invoke.h:89:5: error: no type named ‘type’ in ‘struct std::__invoke_result<void (*)(Data&), Data>’

There's a lot to look at there, but the first part of the error that stuck out to me was:

/usr/include/c++/8/thread:120:17: error: static assertion failed: std::thread arguments must be invocable after conversion to rvalues

What's the problem?

Solution

You can't pass references directly to the std::thread() constructor. You must wrap them in std::ref().

See the std::thread::thread() constructor reference pg here: https://en.cppreference.com/w/cpp/thread/thread/thread:

The arguments to the thread function are moved or copied by value. If a reference argument needs to be passed to the thread function, it has to be wrapped (e.g., with std::ref or std::cref).

So, wrap reference parameters with std::ref() and const reference parameters with std::cref(), like this:

std::thread t1 = std::thread(foo, std::ref(data));
std::thread t2 = std::thread(foo2, std::cref(data));

Full example:

std_thread__pass_parameter_by_reference_to_constructor_via_std_ref.cpp:

// C & C++ includes
#include <cstdint>  // For `uint8_t`, `int8_t`, etc.
#include <cstdio>   // For `printf()`
#include <iostream>  // For `std::cin`, `std::cout`, `std::endl`, etc.
#include <thread>

struct Data
{
    int i = 7;
};

// Accepts a reference
void foo(Data& data)
{
    printf("data.i = %i\n", data.i);
}

// Accepts a const reference
void foo2(const Data& data)
{
    printf("data.i = %i\n", data.i);
}

// int main(int argc, char *argv[])  // alternative prototype
int main()
{
    printf("`std::thread` test\n");

    Data data;
    std::thread t1 = std::thread(foo, std::ref(data));
    std::thread t2 = std::thread(foo2, std::cref(data));
    t1.join();
    t2.join();

    return 0;
}

References

  1. Where I first learned this: C++11 std::thread accepting function with rvalue parameter; see my comments under this answer too
  2. https://en.cppreference.com/w/cpp/thread/thread/thread

    The arguments to the thread function are moved or copied by value. If a reference argument needs to be passed to the thread function, it has to be wrapped (e.g., with std::ref or std::cref).

Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265