Reputation: 434
I like C++11 and its ability to combine STL algorithms to lambdas; it makes the STL much more approachable and useful to everybody. But one thing that I don't understand is what happens inside an STL algorithm (like std::accumulate
) regarding object copying or referencing inside the lambda (or wherever functor you give to it).
My three questions are:
[](Type &a, Type &b){}
), and will it be more optimal than a regular variant; or is it just syntax sugar, the compiler will optimize it anyway, and I could simply omit the ampersands?As for question #2, a quick experiment in Godbolt's GCC page (using compilation flags -stc=c++11 -Os
) seems to suggest the latter, as the generated assembly from the code below is identical wherever I use [](T i1, T i2)
or [](T &i1, T &i2)
; I don't know if those results could be generalized to more complex types/objects, however.
Example #1:
#include<array>
#include<numeric>
template <typename T>
T vecSum(std::array<T, 4> &a){
return std::accumulate(a.begin(), a.end(), T(0),
[](T i1, T i2) {
return std::abs(i1) + std::abs(i2);
}
);
}
void results() {
std::array<int, 4> a = {1,-2, 3,-4};
std::array<int, 4> b = {1,-2,-3, 4};
volatile int c = vecSum(a) + vecSum(b);
}
Example #2:
#include<string>
#include<array>
#include<numeric>
struct FatObject {
std::array<int, 1024*1024> garbage;
std::string string;
FatObject(const std::string &str) : string(str) {
std::fill(garbage.begin(),garbage.end(),0xCAFEDEAD);
}
std::string operator+(const FatObject &rhs) const {
return string + rhs.string;
}
};
template <typename T>
T vecSum(std::array<T, 4> &a){
return std::accumulate(a.begin(),a.end(),T(0),
[](T i1, T i2) {
return i1 + i2;
}
);
}
void results() {
std::array<FatObject, 4> a = {
FatObject("The "),
FatObject("quick "),
FatObject("brown "),
FatObject("fox")
};
std::array<FatObject, 4> b = {
FatObject("jumps "),
FatObject("over "),
FatObject("the "),
FatObject("dog ")
};
volatile std::string c = vecSum(a) + vecSum(b);
}
Upvotes: 1
Views: 1305
Reputation: 2540
Your question is quite broad, but here is my concise answer.
1) In general, the guidelines for passing by-value vs by-reference in lambdas or functors are the same as they are for any regular function or method (a lambda is a functor created on the fly for you, which is a an object with an operator()(T)
). The choice is mostly specific to your case, for example if the lambda/functor needs read-only access to its arguments you tipycally would pass a const reference.
2) Inside an algorithm that accepts a callable object as an argument (and as a template parameter) the compiler is bound to respect the rules of the language. Therefore parameters will be passed by value or by reference internally as per the signature of the lambda/functor.
Keep in mind that copy elision may enter into play, but that is a separate issue, not directly related to the fact that you are calling a lambda inside an standard library algorithm.
The example with int
is too simple. I suggest you to experiment with actual objects.
3) C++ Standard provides precise definitions for the conditions where copy elision occurs, as well as the requirements on the signature of a lambda/functor parameter for a particular Standard Library algorithm.
However, it will not be easy in general to know if the internal implementation of a particular algorithm is going to call the lambda/functor in a way that meets copy elision conditions.
Note that the requirements on signature have some degree of flexibility, for example in the std::accumulate
documentation we have
The signature of the function should be equivalent to the following: Ret fun(const Type1 &a, const Type2 &b); The signature does not need to have const &.
so you can choose to pass by value or by reference as you see fit.
Upvotes: 2
Reputation: 19
In lambdas/functions are the same rules as in all C++.
You should use non-const reference if the intent of the function is to modify the object for the caller. The function should use const& if it is just using the object without changing it. And it should pass by value if it is going to copy/move the object into its internal storage.
If you pass a small object like int
it makes no difference if you pass by value or by reference.
When you start to pass a big object it makes a big impact on performance.
Upvotes: 0