Передача аргументов в std:: async по ссылке не выполняется
Я заметил, что невозможно передать неконстантную ссылку в качестве аргумента std::async
.
#include <functional>
#include <future>
void foo(int& value) {}
int main() {
int value = 23;
std::async(foo, value);
}
Мой компилятор (GCC 4.8.1) дает следующую ошибку для этого примера:
error: no type named ‘type’ in ‘class std::result_of<void (*(int))(int&)>’
Но если я переношу значение, переданное в std::async
в std::reference_wrapper
, все в порядке. Я предполагаю, что это потому, что std::async
принимает аргументы по значению, но я до сих пор не понимаю причины ошибки.
Ответы
Ответ 1
Это преднамеренный выбор дизайна/компромисс.
Во-первых, не обязательно выяснить, переносится ли функциональный объект в async
его аргументы по ссылке или нет. (Если это не простая функция, а объект функции, у нее может быть, например, перегруженный оператор вызова функции.) Таким образом, async
не может сказать: "Эй, позвольте мне просто проверить, чего хочет целевая функция, и я сделаю правильная вещь."
Итак, вопрос о дизайне: действительно ли он принимает все аргументы по ссылке (т.е. если они являются lvalues) или всегда делает копии? Создание копий - это безопасный выбор: копия не может обернуться, и копия не может отображать условия гонки (если это не очень странно). Так что выбор, который был сделан: все аргументы копируются по умолчанию.
Но тогда механизм записывается так, что он фактически не может передать аргументы не-const lvalue reference parameter. Это еще один выбор для безопасности: в противном случае функция, которую вы ожидали бы изменить исходную lvalue, вместо этого изменит копию, что приведет к ошибкам, которые очень трудно отследить.
Но что, если вы действительно, действительно хотите не константный ссылочный параметр lvalue? Что, если вы обещаете следить за обвисшими ссылками и условиями гонки? Для этого нужен std:: ref. Это явный отказ от опасной ссылочной семантики. Это ваш способ сказать: "Я знаю, что я здесь делаю".
Ответ 2
std::async
(и другие функции, которые делают идеальную переадресацию), посмотрите на тип аргумента, который вы передаете, чтобы выяснить, что делать. Они не рассматривают, как этот аргумент будет в конечном итоге использоваться. Итак, чтобы передать объект по ссылке, вам нужно сообщить std::async
, что вы используете ссылку. Однако просто передать ссылку не будет. Вы должны использовать std::ref(value)
для передачи value
по ссылке.
Ответ 3
Сама проблема лишь незначительно связана с std::async()
: при определении результата операции std::async()
использует std::result_of<...>::type
со всеми аргументами std::decay<...>::type
'ed. Это разумно, потому что std::async()
принимает произвольные типы и пересылает их для хранения в каком-либо месте. Чтобы сохранить их, необходимы значения как для объекта функции, так и для аргументов. Таким образом, std::result_of<...>
используется аналогично этому:
typedef std::result_of<void (*(int))(int&)>::type result_type;
... и так как int
не может быть привязан к int&
(int
не является значением типа lvalue для привязки к int&
), это терпит неудачу. Отказ в этом случае означает, что std::result_of<...>
не определяет вложенный type
.
Следующий вопрос может быть следующим: что этот тип используется для создания экземпляра std::result_of<...>
? Идея заключается в том, что используется синтаксис вызова функции, состоящий из ResultType(ArgumentTypes...)
: вместо типа результата передается тип функции и std::result_of<...>
определяет тип функции, вызываемой при вызове этого типа функции с заданным списком аргументов называется. Для типов указателей функций это не очень интересно, но тип функции также может быть объектом функции, в котором необходимо учитывать перегрузку. Итак, в основном, std::result_of<...>
используется следующим образом:
typedef void (*function_type)(int&);
typedef std::result_of<function_type(int)>::type result_type; // fails
typedef std::result_of<function_type(std::reference_wrapper<int>)>::type result_type; //OK