How to pass jthread stop_token to functor?

Viewed 520

I'm trying to work with the new jthreads and have started a std::jthread where the operator() does get called, but I am unable to get it to stop. I want to create the thread by calling it as a function object.

my_thrd = std::make_shared<std::jthread>(&Test::operator(), &test);

If operater() of my Test class is written as the following, it compiles:

void Test::operator()()
{
    using namespace std::literals;
    
    while (!token.stop_requested()) {
        std::cout << "Working ...\n" << std::flush;

        std::this_thread::sleep_for(5s);
    }   
    
    close_connection();
}   

But since it wasn't stopping, I thought I needed to somehow pass std::stop_token to class Test, like so.

void Test::operator()(std::stop_token token) { ... }

But this doesn't compile. I did change the header as well.

/usr/lib/gcc/x86_64-pc-linux-gnu/10.3.0/include/g++-v10/thread: In instantiation of ‘static std::thread std::jthread::_S_create(std::stop_source&, _Callable&&, _Args&& ...) [with _Callable = void (Test::)(std::stop_token); _Args = {Test}]’: /usr/lib/gcc/x86_64-pc-linux-gnu/10.3.0/include/g++-v10/thread:450:28: required from ‘std::jthread::jthread(_Callable&&, _Args&& ...) [with _Callable = void (Test::)(std::stop_token); _Args = {Test}; = void]’ /usr/lib/gcc/x86_64-pc-linux-gnu/10.3.0/include/g++-v10/bits/stl_construct.h:97:14: required from ‘constexpr decltype (::new(void*(0)) _Tp) std::construct_at(_Tp*, _Args&& ...) [with _Tp = std::jthread; _Args = {void (Test::)(std::stop_token), Test}; decltype (::new(void*(0)) _Tp) = std::jthread*]’ /usr/lib/gcc/x86_64-pc-linux-gnu/10.3.0/include/g++-v10/bits/alloc_traits.h:514:4: required from ‘static constexpr void std::allocator_traitsstd::allocator<_CharT >::construct(std::allocator_traitsstd::allocator<_CharT >::allocator_type&, _Up*, _Args&& ...) [with _Up = std::jthread; _Args = {void (Test::)(std::stop_token), Test}; _Tp = std::jthread; std::allocator_traitsstd::allocator<_CharT >::allocator_type = std::allocatorstd::jthread]’

Thoughts?

3 Answers

You can use std::bind to bind &test to &Test::operator(), then use std::placeholders::_1 to reserve a place for unbound std::stop_token:

struct Test {
  void operator()(std::stop_token token) {
    using namespace std::literals;
    while (!token.stop_requested()) {
      std::cout << "Working ...\n" << std::flush;
      std::this_thread::sleep_for(5s);
    }
  }
};

int main() {
  Test test;
  auto my_thrd = std::make_shared<std::jthread>(
    std::bind(&Test::operator(), &test, std::placeholders::_1));
}

Demo.

As @Nicol Bolas commented, we can also use C++20 std::bind_front to do this, which is more lightweight and intuitive than std::bind:

auto my_thrd = std::make_shared<std::jthread>(
    std::bind_front(&Test::operator(), &test));

A simple, efficient solution is to use a lambda:

auto my_thrd = std::make_shared<std::jthread>(
  [&test] (std::stop_token token) { test(token); });

and spare the extra includes and library facilities

Demo

jthread (or thread, for that matter) accepts any callable object. That includes anything with an operator(), like...Test.

The default is to pass by value; use std::ref to pass by reference, but note that you are on the hook for ensuring that the referenced object outlives the thread in that case.

So this is just:

auto my_thrd = std::make_shared<std::jthread>(test);

or (to pass by reference like your original code):

auto my_thrd = std::make_shared<std::jthread>(std::ref(test));
Related