38

GCC 4.7 in C++11 mode is letting me define a function taking a lambda two different ways:

// by value
template<class FunctorT>
void foo(FunctorT f) { /* stuff */ }

And:

// by r-value reference
template<class FunctorT>
void foo(FunctorT&& f) { /* stuff */ }

But not:

// by reference
template<class FunctorT>
void foo(FunctorT& f) { /* stuff */ }

I know that I can un-template the functions and just take std::functions instead, but foo is small and inline and I'd like to give the compiler the best opportunity to inline the calls to f it makes inside. Out of the first two, which is preferable for performance if I specifically know I'm passing lambdas, and why isn't it allowed to pass lambdas to the last one?

Joseph Garvin
  • 20,727
  • 18
  • 94
  • 165

3 Answers3

33

FunctorT&& is a universal reference and can match anything, not only rvalues. It's the preferred way to pass things in C++11 templates, unless you absolutely need copies, since it allows you to employ perfect forwarding. Access the value through std::forward<FunctorT>(f), which will make f an rvalue again if it was before, or else will leave it as an lvalue. Read more here about the forwarding problem and std::forward and read here for a step-by-step guide on how std::forward really works. This is also an interesting read.

FunctorT& is just a simple lvalue reference, and you can't bind temporaries (the result of a lambda expression) to that.

Community
  • 1
  • 1
Xeo
  • 129,499
  • 52
  • 291
  • 397
  • Interesting reads. Say for the moment I don't pass f on to any other functions so std::forward is moot (that make it moot right?). If I take f by value, then any lambda passed in that actually has size (it captures some variables) will end up being copied, right? That is, if my lambda captures something by value that has a copy constructor with a visible side effect, and I pass the lambda to foo, I will see a copy made. But if instead foo takes an rvalue reference, the lambda will be moved into foo (I can see move if I have side effectful move constructor)? – Joseph Garvin Sep 23 '12 at 00:25
  • 1
    @Joseph: First, if you pass a temporary to your first function, the temporary will be moved into the parameter, not copied. It will only be copied in case you pass an lvalu. For the universal reference one, you will get neither copy nor move, since it's a *reference* that directly binds to the temporary (or lvalue or whatever is passed). Many times, you will have copy elision anyways, and won't see any copy / move at all (copy / move ctors with side effects can be elided and no side effects will occur, this is specifically allowed by the standard). – Xeo Sep 23 '12 at 00:31
  • 2
    @JosephGarvin: On `std::forward` being moot if you don't pass it further: No, it's not. Image any functor (not specifically lambdas), with an `operator()` that is *ref-qualified* (see [here](http://stackoverflow.com/a/8610728/500104)) on both lvalues and rvalues, and the rvalue one does something different. If you don't use `std::forward` when passing in a temporary, you'll ever get the lvalue overload called. Basically, you *do* pass the functor - to its own member functions. ;) – Xeo Sep 23 '12 at 00:36
  • You're saying when I actually call f, I should do std::forward(f)() instead of just f()? – Joseph Garvin Sep 23 '12 at 01:10
  • 2
    So, if I'm understanding correctly, everywhere I have template code that takes template type parameters now I should take by &&, and every time I use the parameter where I don't explicitly want a copy I should be using std::forward? That seems like a metric ton of extra boilerplate... – Joseph Garvin Sep 23 '12 at 01:35
  • 1
    @Joseph: For full-blown genericity, yes, that is the path to code. Unless you explicitly want a read-only reference, then you can just go with `T const&` of course. – Xeo Sep 23 '12 at 01:39
  • 1
    @JosephGarvin It's not wrong to consider that `template void foo(F f) { f(); }` is the 'good enough' version of `template void foo(F&& f) { std::forward(f)(); }`. Except for what I would consider pathological case, you do not lose actual functionality -- you may do some unnecessary work, but you get to the same result either way. – Luc Danton Sep 23 '12 at 01:56
  • 1
    @Xeo: Are you saying all template parameters should be universal references unless you need copies? That if STL was rewritten for C++11, it'd take template parameters with && instead of by value (eg for the compare parameter in sd::sort)? – Jon May 20 '14 at 02:29
5

When you create a lambda function you get a temporary object. You cannot bind a temporary to a non-const l-value references. Actually, you cannot directly create an l-value referencing a lambda function.

When you declare you function template using T&& the argument type for the function will be T const& if you pass a const object to the function, T& if you pass a non-const l-value object to it, and T if you pass it a temporary. That is, when passing a temporary the function declaration will take an r-value reference which can be passed without moving an object. When passing the argument explicitly by value, a temporary object is conceptually copied or moved although this copy or move is typically elided. If you only pass temporary objects to your functions, the first two declarations would do the same thing, although the first declaration could introduce a move or copy.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • @Nicol: The `T` in `T&&` will be a simple `T` (aka `int`), not `T&&` (aka `int&&`). Reference collapsing will only apply for lvalues, which would make the `T` in `T&&` yield `T&`. – Xeo Sep 23 '12 at 00:01
  • Dietmar *does* make it sound like the end product will be `T` though, especially with the last sentence. The first two declarations will not be the same for an rvalue, since one will incur a move that the other does not. – Xeo Sep 23 '12 at 00:03
  • OK,I meant what I wrote but what I wrote seems, indeed, not be accurate although in practice it probably doesn't matter (because the copy or move will normally be elided anyway). I'll update the response correspondingly. – Dietmar Kühl Sep 23 '12 at 00:12
  • 5
    *"I know you think you understand what you thought I said, but I am not sure you realize that what you heard is not what I meant."* -- Alan Greenspan addressing a question from a student during a presentation. – David Rodríguez - dribeas Sep 23 '12 at 01:04
3

This is a good question -- the first part: pass-by-value or use forwarding. I think the second part (having FunctorT& as an argument) has been reasonably answered.

My advice is this: use forwarding only when the function object is known, in advance, to modify values in its closure (or capture list). Best example: std::shuffle. It takes a Uniform Random Number Generator (a function object), and each call to the generator modifies its state. The function object is forwarded into the algorithm.

In every other case, you should prefer to pass by value. This does not prevent you from capturing locals by reference and modifying them within your lambda function. That will work just like you think it should. There should be no overhead for copying, as Dietmar says. Inlining will also apply and references may be optimized out.

Andrew
  • 39
  • 1
  • When you say there should be no overhead for copying, you mean that in practice the copies will be optimized out or that semantically copies aren't made? – Joseph Garvin Sep 23 '12 at 15:41
  • Copies should be elided. Optimized away. – Andrew Sep 24 '12 at 00:01
  • The only by-value copy that can be elided is an rvalue conceptually created at call site, which can be constructed _as_ the argument. Beyond that, copies of by-value args, e.g. cascaded to other function calls, absolutely can't be elided. Each function must get its own copy, which must live separately from the original: it must not track concurrent changes to its source; and for non-`const` by-value args, it must conversely be alterable without affecting the original. So here, copies are _guaranteed_. Use a forwarding reference, always. It's either free or saves pointless copies. Why pay ever? – underscore_d Jul 18 '16 at 22:50