Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should templated functions take lambda arguments by value or by rvalue reference?

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?

like image 740
Joseph Garvin Avatar asked Sep 22 '12 23:09

Joseph Garvin


People also ask

What are rvalue references good for?

Rvalue references is a small technical extension to the C++ language. Rvalue references allow programmers to avoid logically unnecessary copying and to provide perfect forwarding functions. They are primarily meant to aid in the design of higer performance and more robust libraries.

Can lvalue reference bind rvalue?

By default, the compiler cannot bind a non-const or volatile lvalue reference to an rvalue.

Where is rvalue reference used?

Rvalue references are used to indicate whether the recipient will move /pilfer/otherwise render inert (but valid) the referent, not copy them. Copy semantics should be indicated by const & or value.

Is return value a rvalue?

Typically rvalues are temporary objects that exist on the stack as the result of a function call or other operation. Returning a value from a function will turn that value into an rvalue.


3 Answers

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.

like image 50
Xeo Avatar answered Oct 21 '22 11:10

Xeo


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.

like image 44
Dietmar Kühl Avatar answered Oct 21 '22 12:10

Dietmar Kühl


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.

like image 3
Andrew Avatar answered Oct 21 '22 10:10

Andrew