Universal references and local classes
In my code below, I have a function that accepts a "universal reference" ( F&&
). The function also has an inner class that takes an object F&&
in its constructor. Is there F&&
still a universal reference at this point? That is, is F
it still considered an inferred type?
In other words, should I use std::forward<F>
or std::move
in a constructor initialization list?
#include "tbb/task.h"
#include <iostream>
#include <future>
template<class F>
auto Async(F&& f) -> std::future<decltype(f())>
{
typedef decltype(f()) result_type;
struct Task : tbb::task
{
Task(F&& f) : f_(std::forward<F>(f)) {} // is forward correct here?
virtual tbb::task* execute()
{
f_();
return nullptr;
}
std::packaged_task<result_type()> f_;
};
auto task = new (tbb::task::allocate_root()) Task(std::forward<F>(f));
tbb::task::enqueue(*task);
return task->f_.get_future();
}
int main()
{
Async([]{ std::cout << "Hi" << std::endl; }).get();
}
source to share
Is there
F&&
still a universal reference at this point? That is, isF
it still considered an inferred type?
This kind of confusion is why I don't like the term universal link ... there is no such thing .
I prefer to understand the code in terms of lvalue references and rvalue references, and the rules for reference collapse and template argument deduction.
When a function is called with an lvalue of type L
, the argument F
will be inferred as L&
, and according to the link F&&
- dropping rules , it's easy L&
. In the constructor, Task
nothing has changed, F&&
still L&
, so the constructor takes a reference lvalue, tied to the lvalue, transmitted to Async
, and so you do not want it to move, and forward
because it keeps the value category, mailing lvalue as an lvalue. (Moving from an lvalue would surprise a caller Async
who wouldn't expect the lvalue to move silently.)
When a function is called with an rvalue of type R
, the argument F
will be inferred as R
, and therefore F&&
is R&&
. In the constructor, Task
nothing has changed, F&&
still R&&
, so the constructor takes rvalue reference tied to the rvalue, passed in Async
, so you can move it, but it forward
is also appropriate because it saves the category values, redirecting both rvalue rvalue.
At CppCon last week, Herb Sutter announced that the preferred term for "universal link" is now referenced links because it better describes what they are used for.
source to share
The ctor value is not a generic reference, but the standard rvalue-reference or bvalue-reference. The problem with your design is that you don't know what exactly the mirror is Async
(which might be enough)!
To be a universal reference, the type must be inferred for this call, and not sometime before for some associated call.
std::forward
I'm still pertinent there, since the argument to the external functions should indeed be passed to the created object with the preserved move / copy semantics.
source to share