Home > Blockchain >  rvalue reference forwarding
rvalue reference forwarding

Time:03-03

I am writing a wrapper around std::jthread and some surrounding infrastructure. I cannot wrap my head around why the following won't compile:

#include <iostream>
#include <map>
#include <functional>
#include <thread>

// two random functions
void foo(int i) { std::cout << "foo " << i << std::endl; }

void bar(int i) { std::cout << "bar " << i << std::endl; }

// mechanism to identify them
enum function_kind {
    foo_kind, bar_kind
};
std::map<function_kind, std::function<void(
        int)>> kind_to_function{{foo_kind, foo},
                                {bar_kind, bar}};

// wrapper around jthread
// (additional functionality ommitted for brevity)
template<typename Callable, typename... Args>
class MyThread {
public:
    explicit MyThread(Callable &&function, Args &&...args) : m_thread{
            std::forward<Callable>(function),
            std::forward<Args>(args)...} {}

private:
    std::jthread m_thread;
};

int main() {
    std::jthread t1(kind_to_function[foo_kind], 3); // works
    MyThread t2(kind_to_function[foo_kind], 3); // complains
    return 0;
}


I am really just trying to mimic whatever std::jthread is doing with my own class.

The IDE (clion) complains, that the first argument to t2 is not an rvalue. The compiler complains a little more complicated:

main.cpp: In function ‘int main()’:
main.cpp:29:46: error: class template argument deduction failed:
   29 |     MyThread t2(kind_to_function[foo_kind], 3); // complains
      |                                              ^
main.cpp:29:46: error: no matching function for call to ‘MyThread(std::map<function_kind, std::function<void(int)> >::mapped_type&, int)’
main.cpp:20:14: note: candidate: ‘MyThread(Callable&&, Args&& ...)-> MyThread<Callable, Args> [with Callable = std::function<void(int)>; Args = {int}]’ (near match)
   20 |     explicit MyThread(Callable &&function, Args &&...args) : m_thread{std::forward<Callable>(function),
      |              ^~~~~~~~
main.cpp:20:14: note:   conversion of argument 1 would be ill-formed:
main.cpp:29:46: error: cannot bind rvalue reference of type ‘std::function<void(int)>&&’ to lvalue of type ‘std::map<function_kind, std::function<void(int)> >::mapped_type’ {aka ‘std::function<void(int)>’}
   29 |     MyThread t2(kind_to_function[foo_kind], 3); // complains
      |                                              ^
main.cpp:18:7: note: candidate: ‘template<class Callable, class ... Args> MyThread(MyThread<Callable, Args>)-> MyThread<Callable, Args>’
   18 | class MyThread {
      |       ^~~~~~~~
main.cpp:18:7: note:   template argument deduction/substitution failed:
main.cpp:29:46: note:   ‘std::function<void(int)>’ is not derived from ‘MyThread<Callable, Args>’
   29 |     MyThread t2(kind_to_function[foo_kind], 3); // complains
      |                                              ^

In any case, the arguments work for std::jthread, which also just takes rvalues... So what am I missing?

CodePudding user response:

The parameters of the MyThread constructor are not forwarding references because the constructor is not a template. Do not make the class a template, but only the constructor:

class MyThread {
public:
    template<typename Callable, typename... Args>
    explicit MyThread(Callable &&function, Args &&...args) :
        m_thread{
            std::forward<Callable>(function),
            std::forward<Args>(args)...} {}

private:
    std::jthread m_thread;
};

CodePudding user response:

In any case, the arguments work for std::jthread, which also just takes rvalues... So what am I missing?

jthread is not a template, its constructor is a template. Which makes the rvalue references to template parameters into forwarding references, not plain rvalue references.

However, since MyThread is itself a template, and its constructor is not a template constructor, the behavior is not the same. After instantiation, it's a regular constructor that accepts only rvalues.

Forwarding references are contingent on template argument deduction happening for the function template they are a part of. So a non-template constructor means no forwarding references.

Okay, but you didn't specify template arguments to MyThread, why was there seemingly no error? Because class template argument deduction allows you to omit those. And CTAD happens in its own overload resolution step, completely disjoint from actually choosing a constructor to initialize the object. One step can be ill-formed while the other is not.

  • Related