20

With a function, one can write:

template <class T> void f(T&& x) {myfunction(std::forward<T>(x));}

but with a lambda, we don't have T:

auto f = [](auto&& x){myfunction(std::forward</*?*/>(x));}

How to do perfect-forwarding in a lambda? Does decltype(x) work as the type in std::forward?

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
Vincent
  • 57,703
  • 61
  • 205
  • 388
  • Related question: In this case `static_cast` can be used, but when `T` is used it's not possible. [c++11 - C++ std::forward vs static_cast - Stack Overflow](https://stackoverflow.com/questions/32806533/c-stdforwardt-vs-static-castt) – user202729 Jan 22 '22 at 13:44

3 Answers3

29

The canonical way to forward a lambda argument that was bound to a forwarding reference is indeed with decltype:

auto f = [](auto&& x){
  myfunction(std::forward<decltype(x)>(x));
} //                      ^^^^^^^^^^^
Columbo
  • 60,038
  • 8
  • 155
  • 203
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • 1
    There is a [proposal for C++2a](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0428r0.pdf) to make lambdas with call operator templates a bit easier to write. – Kerrek SB Mar 15 '17 at 00:50
  • Interesting link. Wouldn't it worth it putting the link directly in the answer? – skypjack Mar 15 '17 at 07:34
  • @skypjack: Given the tenuous and speculative nature of early stage proposals, and fact that the question is about C++14, I thought that'd best be left as a comment. Maybe once there's an actual decision to accept this we can update this (July?). – Kerrek SB Mar 15 '17 at 09:28
  • It makes sense actually. Thank you anyway. ;-) – skypjack Mar 16 '17 at 13:23
  • @KerrekSB what about variadic number of arguments (like in vector's `emplace`) with lambda? – KeyC0de Sep 21 '18 at 21:11
  • 1
    @Nik-Lz: You can make a pack: `[](auto&&... xs)`. Then just expand. – Kerrek SB Sep 21 '18 at 21:49
  • 2
    I may be wrong, but as I understand it `decltype(x)` is not the same as the underlying (hidden) template parameter since x is itself always an lvalue. Therefore `std::forward(x)` would never result in a prvalue due to this. – Araeos Nov 30 '18 at 20:06
  • @Araeos: `std::forward` never produces prvalues, period. "Perfect forwarding" never "forwards as prvalues" (because that would imply copying of some sort). It's not quite as perfect as one might hope; in particular, prvalues simply cannot in general be "forwarded" arbitrarily in the language. (I think that's what the "lazy parameters" proposals are addressing.) – Kerrek SB Dec 01 '18 at 21:28
  • @KerrekSB thanks for clearing that up. At that moment I thought of decltype specifier in the form of `decltype((x))` where the expression `(x)` is an lvalue which, of course, is not what your example does. – Araeos Dec 01 '18 at 22:40
  • `std::forward(x)` is of course the canonical solution. Let's see, from which C++ standard on, we can just write `std::forward(x)` and omit the `decltype(x)`. – Kai Petzke Mar 02 '20 at 01:50
  • 1
    @KaiPetzke: That seems extremely unlikely, since the point of `forward` is to do something that cannot be inferred (from values, I mean, not from lexemes) and thus needs explicit information. You can of course write a macro (and many libraries do), but I don't suppose that's what you meant. But if you did, then I suppose a reflection-based solution might appear at some point, once we have reflection. – Kerrek SB Mar 10 '20 at 12:40
  • 1
    Wait, given a function argument `int&& x`, isn't `decltype(x)` an lvalue type? – Violet Giraffe Feb 01 '21 at 10:35
  • @VioletGiraffe (and future readers) I think the answer below https://stackoverflow.com/a/42799658/5267751 has a pretty good explanation. – user202729 Jan 22 '22 at 12:49
8

With C++20, you can now specify a template parameter list for a lambda. The following was taken directly from https://en.cppreference.com/w/cpp/language/lambda

auto f = []<typename ...Ts>(Ts&& ...ts) {
    return foo(std::forward<Ts>(ts)...);
};
pathfinderelite
  • 3,047
  • 1
  • 27
  • 30
6

My favorite idiom for this is:

auto f = [](auto&& x){myfunction(decltype(x)(x));}

which I read as "x as the type x was declared as".

To see how this works, examine what happens when x is an int&&. decltype(x)(x) is (int&&)(x), which produces an rvalue reference to x. If x is an int&, then we get (int&)(x) which is a noop cast to a reference. Remember, decltype(x) includes the reference category.

Now, for auto&& parameters this is shorter but equivalent to:

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

the alternative.

For auto parameters:

auto f = [](auto x){myfunction(decltype(x)(x));}

it induces an extra copy, while

auto f = [](auto x){myfunction(std::forward<decltype(x)>(x));}

instead moves-from x.

While I usually treat C-style casts as being too dangerous, decltype(x)(x) can at worst make a type-correct copy of x if x is not an auto&& variable. And there is something to be said for the brevity of it.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 3
    The decltype cast is a neat trick, but what I like about `std::forward` is that it creates a readable (and greppable) hint that forwarding is going on and that a nearby `&&` is a forwarding reference. – Kerrek SB Mar 15 '17 at 01:56
  • 2
    I agree on that one; brevity comes at the cost of meaning, IMO. Nice trick though, +1! – Columbo Mar 15 '17 at 14:13
  • Could you elaborate with an example where the copy is made? for `auto` parameters:... e.g. `int a; f(a);` -> `auto == int` , first copy when `auto x` is initialized (probably optimized out), then in the first case: copy because of `int(x)` (certainly optimized out) in the second case: a move (if myFunction is implemented as such) because std::forward casts to `int&&`. Is that correct? – Gabriel Nov 08 '17 at 23:12
  • @Gabriel Moving copying `int` is uninteresting. Use `struct noisy{ noisy(noisy&&){std::cout << "&&";} /* etc */}`. Move semantics are *not useful to analyze* on primitive types. Other than a different address, there are zero side effects from copying or moving them, so *the moves and copies don't exist unless you take an address* or you have to obey a calling convention for whatever reason. – Yakk - Adam Nevraumont Nov 09 '17 at 03:10
  • jeah right, should have replaced this in my example. – Gabriel Nov 13 '17 at 09:45
  • Why not `static_cast(x)`? In practice that would do the same thing as the C-style cast, but be clearer (IMO). – Arthur Tacca Apr 16 '18 at 08:16
  • @arthur the only advantage is brevity: but yours is worse than `std::forward(x)`, as it is similar length and less clesr and worse corner cases. – Yakk - Adam Nevraumont Apr 16 '18 at 11:32
  • @yakk Clarity is subjective, but I would argue static_cast is clearer than std::forward because its implementation is slightly more complex than a straight cast in that it adds a `&&` qualifier, but that reference qualifier has no effect. That will not be obvious to every reader, and the fact you have gone to the effort of adding it in (by using std::forward) may well cause them to believe that extra `&&` is important and so thoroughly confuse them. – Arthur Tacca Apr 16 '18 at 13:02
  • Comparison with `std::forward` is a digression though; my point was just that `static_cast` is better than a C-style cast. Here are the differences I can think of: Their effect in this situation is the same; that's a draw. In a more general situation a C-style cast could do something other than just change type (e.g reinterpret bits); that's a win for `static_cast`. And visually a `static_cast` takes much less effort to parse for a human than a C-style cast; that's a win for `static_cast` too (the brevity of a C-style cast is a *dis*advantage). – Arthur Tacca Apr 16 '18 at 13:03
  • @ArthurTacca No, you are wrong. Not much else to say. We are forwarding; `std::forward` is highly appropriate. It handles corner cases (if the variable is `auto x` instead of `auto&& x`) better than `static_cast` does. I was explicit above that the advantage of `decltype(x)(x)` is brevity; if you don't want brevity, then don't use it. Often I find lambdas are best used with brevity, however. And no, `decltype(x)(x)` **cannot** reinterpret bits, no matter what type `x` is. It can cause a temporary to be created, which is its biggest flaw in my opinion, but so can your static cast soltn. – Yakk - Adam Nevraumont Apr 16 '18 at 13:05
  • @Yakk For the C-style cast: Again, you are not thinking of the reader of your code (possibly you from the future!). You are right that the cast can never reinterpret bits here, but C-style casts are such an oddity in modern C++ code that any reader is likely to stop and question whether that is the case and, if not, why you used it over a static cast. This is in addition to the extra time taken to even recognise it as a cast, which as I already mentioned is not obvious on first glance. – Arthur Tacca Apr 16 '18 at 14:08
  • The failure of a `static_cast`, as compared to `std::forward` to handle the `auto` parameter is not really a disadvantage because you wouldn't use it in that case; if you wanted an analogously explicit solution for that case you would use `std::move`. It probably is better to use `std::forward` overall but I still contend it's subjective which is clearer. "No, you are wrong. Not much else to say" is a pretty silly thing to write. – Arthur Tacca Apr 16 '18 at 14:08
  • @ArthurTacca Well, that was my original response. Because I figured writing the rest of the comment wouldn't actually help. Turned out I was right; I should have stopped there. I find that explaining the logic of coding style rarely persuades. The bit about `auto x`? I find code that is *locally* correct is better than contextually correct; `static_cast(x)` is correct if `x` is a reference type and you want to forward it; `std::forward(x)` is correct if you want to forward `x`. The latter has less contextual dependencies. And we are forwarding. So it is better. – Yakk - Adam Nevraumont Apr 16 '18 at 14:29
  • Mind the parentheses: `decltype(x)(x)` is a *functional cast*; `(decltype(x))x` is a *C-style cast*. – bit2shift Apr 24 '18 at 17:59
  • @bit2shift A one-argument functional style cast behaves identically to a C-style cast, and more people know about C-style cast dangers than one-argument functional style cast dangers, so I'd rather call it a C-style cast to be clear about the danger. – Yakk - Adam Nevraumont Apr 24 '18 at 18:09