Как написать общую пересылку lambda в С++ 14?

Как написать общую пересылку lambda в С++ 14?

Попробуйте # 1

[](auto&& x) { return x; }

Внутри тела функции x является lvalue, поэтому это не работает.

Попробуйте # 2

[](auto&& x) { return std::forward<decltype(x)>(x); }

Это правильно пересылает ссылки внутри лямбда, но всегда будет возвращаться по значению (если компилятор не возвращает копию).

Попробуйте # 3

[](auto&& x) -> decltype(x) { return std::forward<decltype(x)>(x); }

Возвращает тот же тип, что и аргумент (возможно, -> auto&& тоже работает) и работает нормально.

Попробуйте # 4

[](auto&& x) noexcept -> decltype(x) { return std::forward<decltype(x)>(x); }

Добавляет ли noexcept эту лямбду более применимую и, следовательно, строго лучше, чем # 3?

Ответы

Ответ 1

Недопустимый тип возврата decltype(x).

Локальные переменные и параметры функции, взятые по значению, могут быть неявно перемещены в возвращаемое значение, но не являются параметрами функции, взятыми с помощью ссылки rvalue (x - это значение lvalue, хотя ссылка decltype(x) == rvalue, если вы передаете rvalue). Обоснование, данное комитетом, состоит в том, что они хотели быть уверенными, что, когда компилятор неявно движется, никто другой не может его иметь. Вот почему мы можем перейти от prvalue (временное, не ссылочное квалифицированное возвращаемое значение) и из локально-локальных значений. Однако кто-то мог сделать что-то глупое, как

std::string str = "Hello, world!";
f(std::move(str));
std::cout << str << '\n';

И комитет не хотел молча прибегать к небезопасному движению, полагая, что они должны начать более консервативно с этой новой функцией "перемещения". Обратите внимание, что в С++ 20 эта проблема будет решена, и вы можете просто сделать return x, и она пойдет правильно. См. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0527r0.html

Для функции пересылки я попытался бы получить правильное значение noexcept. Здесь легко, так как мы имеем дело со ссылками (это безоговорочно noexcept). В противном случае вы нарушаете код, который заботится о noexcept.

Это делает окончательный идеальный переход лямбда следующим образом:

auto lambda = [](auto && x) noexcept -> auto && { return std::forward<decltype(x)>(x); };

Тип возврата decltype(auto) будет делать то же самое здесь, но auto && лучшие документы, которые эта функция всегда возвращает ссылку. Он также избегает упоминания имени переменной еще раз, что, я полагаю, немного облегчает переименование переменной.

Как и на С++ 17, пересылающая лямбда также неявно является constexpr, где это возможно.

Ответ 2

Ваши первые два попытки не работают, потому что возврат типа возврата снижает верхние уровни cv-квалификаторов и ссылок, что делает невозможным общее переадресацию. Ваша третья абсолютно правильная просто излишне подробная, приведение неявно в обратном типе. И noexcept не имеет отношения к пересылке.

Вот еще несколько вариантов, которые нужно выбрасывать для полноты:

auto v0 = [](auto&& x) -> decltype(x) { return x; };
auto v1 = [](auto&& x) -> auto&& { return x; };
auto v2 = [](auto&& x) -> auto&& { return std::forward<decltype(x)>(x); };
auto v3 = [](auto&& x) -> decltype(auto) { return std::forward<decltype(x)>(x); };

v0 будет компилироваться и выглядеть так, как если бы он возвращал правильный тип, но сбой при компиляции, если вы вызываете его с помощью ссылки rvalue из-за запрошенного неявного преобразования из lvalue reference (x) в rvalue reference (decltype(x)). Это не работает как для gcc, так и для clang.

v1 всегда будет возвращать ссылку lvalue. Если вы назовете его временным, это даст вам болтающуюся ссылку.

Оба v2 и v3 являются правильными, общая пересылка lambdas. Таким образом, у вас есть три варианта для возвращаемого возвращаемого типа (decltype(auto), auto&& или decltype(x)) и один параметр для тела вашей лямбды (вызов std::forward).

Ответ 3

Наименьшее количество символов, но полнофункциональная версия, которую я могу создать, это:

[](auto&&x)noexcept->auto&&{return decltype(x)(x);}

это использует идиому, которую я нахожу полезной - при пересылке параметров auto&& в лямбда, decltype(arg)(arg). Пересылка decltype через forward относительно бессмысленна, если вы знаете, что arg имеет ссылочный тип.

Если x имел тип значения, decltype(x)(x) фактически создает копию x, а std::forward<decltype(x)>(x) создает ссылку rvalue для x. Поэтому шаблон decltype(x)(x) менее полезен в общем случае, чем forward: но это не общий случай.

auto&& позволит возвращать ссылки (соответствующие входящие ссылки). К сожалению, эталонное продление жизни не будет работать должным образом с вышеуказанным кодом - я считаю, что перенаправление ссылок rvalue в ссылки rvalue часто является неправильным решением.

template<class T>struct tag{using type=T;};
template<class Tag>using type=typename Tag::type;
template<class T> struct R_t:tag<T>{};
template<class T> struct R_t<T&>:tag<T&>{};
template<class T> struct R_t<T&&>:tag<T>{};
template<class T>using R=type<R_t<T>>;

[](auto&&x)noexcept->R<decltype(x)>{return decltype(x)(x);}

дает вам такое поведение. Ссылки lvalue становятся ссылками lvalue, значения rvalue становятся значениями.