Как написать общую пересылку 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 становятся значениями.