В моем коде ниже у меня есть функция, которая принимает «универсальную ссылку» (F&&
). Функция также имеет внутренний класс, который принимает объект F&&
в своем конструкторе. Является F&&
все еще универсальная ссылка на тот момент? То есть является F
все еще считается выведенным типом?
Другими словами, я должен использовать std::forward<F>
или же std::move
в списке инициализации конструктора?
#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();
}
Является
F&&
все еще универсальная ссылка на тот момент? То есть являетсяF
все еще считается выведенным типом?
Такого рода путаница — вот почему мне не нравится термин универсальная ссылка … нет такого понятия.
Я предпочитаю понимать код в терминах ссылок lvalue и ссылок rvalue, а также правил свертывания ссылок и вывода аргументов шаблона.
Когда функция вызывается с lvalue типа L
Аргумент F
будет выведено как L&
и по ссылке рушатся правила F&&
просто L&
, в Task
конструктор ничего не меняет, F&&
все еще L&
поэтому конструктор принимает ссылку на lvalue, которая привязана к lvalue, переданному в Async
и поэтому вы не хотите перемещать его, и forward
подходит, потому что это сохраняет категорию значения, пересылая lvalue как lvalue. (Переход от lvalue удивит вызывающего Async
, который не ожидал, что lvalue будет тихо перемещен.)
Когда функция вызывается со значением типа r R
Аргумент F
будет выведено как R
, так что F&&
является R&&
, в Task
конструктор ничего не меняет, F&&
все еще R&&
поэтому конструктор принимает ссылку на rvalue, которая привязана к rvalue, переданному в Async
и так ты мог переместить его, но forward
также целесообразно, потому что это сохраняет категорию значения, перенаправляя значение rvalue как значение rvalue.
На прошлой неделе на CppCon Херб Саттер объявил, что предпочтительный термин для «универсальной ссылки» теперь экспедиционная ссылка потому что это лучше описывает, для чего они используются.
Ктор не является универсальная ссылка, но стандартная ссылка rvalue или lvalue-ссылка. Проблема с вашей конструкцией в том, что вы понятия не имеете, просто она отражает Async
(что может быть достаточно)!
Чтобы быть универсальной ссылкой, тип должен быть выведен за этот звонок, и не раньше, чем-то связанным.
std::forward
я все еще уместен там, поскольку аргумент внешних функций действительно должен быть передан созданному объекту с сохраненной семантикой перемещения / копирования.