Reputation: 12562
How do I write a generic forwarding lambda in C++14?
[](auto&& x) { return x; }
Inside the function body, x
is an lvalue, so this doesn't work.
[](auto&& x) { return std::forward<decltype(x)>(x); }
This properly forwards references inside the lambda, but it will always return by value (unless the compiler elides the copy).
[](auto&& x) -> decltype(x) { return std::forward<decltype(x)>(x); }
This returns the same type as the argument (probably -> auto&&
would work as well) and appears to work properly.
[](auto&& x) noexcept -> decltype(x) { return std::forward<decltype(x)>(x); }
Does adding noexcept
make this lambda more applicable and thus strictly better than #3?
Upvotes: 17
Views: 1485
Reputation: 28873
A return type of decltype(x)
is insufficient.
Local variables and function parameters taken by value can be implicitly moved into the return value, but not function parameters taken by rvalue reference (x is an lvalue, even though decltype(x)
== rvalue reference if you pass an rvalue). The reasoning the committee gave is that they wanted to be certain that when the compiler implicitly moves, no one else could possibly have it. That is why we can move from a prvalue (a temporary, a non-reference qualified return value) and from function-local values. However, someone could do something silly like
std::string str = "Hello, world!";
f(std::move(str));
std::cout << str << '\n';
And the committee didn't want to silently invoke an unsafe move, figuring that they should start out more conservative with this new "move" feature. Note that in C++20, this issue will be resolved, and you can simply do return x
and it will do the right thing. See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0527r0.html
For a forwarding function, I would attempt to get the noexcept
correct. It is easy here since we are just dealing with references (it is unconditionally noexcept
). Otherwise, you break code that cares about noexcept
.
This makes the final ideal forwarding lambda look as follows:
auto lambda = [](auto && x) noexcept -> auto && { return std::forward<decltype(x)>(x); };
A return type of decltype(auto)
will do the same thing here, but auto &&
better documents that this function always returns a reference. It also avoids mentioning the variable name again, which I suppose makes it slightly easier to rename the variable.
As of C++17, the forwarding lambda is also implicitly constexpr where possible.
Upvotes: 8
Reputation: 275740
The fewest characters, yet fully featured, version I can generate is:
[](auto&&x)noexcept->auto&&{return decltype(x)(x);}
this uses an idiom I find useful -- when forwarding auto&&
parameters in a lambda, do decltype(arg)(arg)
. Forwarding the decltype
through forward
is relatively pointless if you know that the arg is of reference type.
If x
was of value type, decltype(x)(x)
actually produces a copy of x
, while std::forward<decltype(x)>(x)
produces an rvalue reference to x
. So the decltype(x)(x)
pattern is less useful in the general case than forward
: but this isn't the general case.
auto&&
will allow references to be returned (matching the incoming references). Sadly, reference lifetime extension won't work properly with the above code -- I find that forwarding rvalue references into rvalue references is often the wrong solution.
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);}
gives you that behavior. lvalue references become lvalue references, rvalue references become values.
Upvotes: 2
Reputation: 303377
Your first two tries don't work because return type deduction drops top-level cv-qualifiers and references, which makes generic forwarding impossible. Your third is exactly correct just unnecessarily verbose, the cast is implicit in the return type. And noexcept
is unrelated to forwarding.
Here are a few more options worth throwing out for completeness:
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
will compile and look as if it returns the correct type, but will fail at compilation if you call it with an rvalue reference due to the requested implicit conversion from lvalue reference (x
) to rvalue reference (decltype(x)
). This fails on both gcc and clang.
v1
will always return an lvalue reference. If you call it with temporary, that gives you a dangling reference.
Both v2
and v3
are correct, generic forwarding lambdas. Thus, you have three options for your trailing return type (decltype(auto)
, auto&&
, or decltype(x)
) and one option for the body of your lambda (a call to std::forward
).
Upvotes: 6