Lambda functions can take arguments of arbitrary types. This allows a lambda to be more generic:
auto twice = [](auto x){ return x+x; };
int i = twice(2); // i == 4
std::string s = twice("hello"); // s == "hellohello"
This is implemented in C++ by making the closure type's operator()
overload a template function. The following type has equivalent behavior to the above lambda closure:
struct _unique_lambda_type
{
template<typename T>
auto operator() (T x) const {return x + x;}
};
Not all parameters in a generic lambda need be generic:
[](auto x, int y) {return x + y;}
Here, x
is deduced based on the first function argument, while y
will always be int
.
Generic lambdas can take arguments by reference as well, using the usual rules for auto
and &
. If a generic parameter is taken as auto&&
, this is a forwarding reference to the passed in argument and not an rvalue reference:
auto lamb1 = [](int &&x) {return x + 5;};
auto lamb2 = [](auto &&x) {return x + 5;};
int x = 10;
lamb1(x); // Illegal; must use `std::move(x)` for `int&&` parameters.
lamb2(x); // Legal; the type of `x` is deduced as `int&`.
Lambda functions can be variadic and perfectly forward their arguments:
auto lam = [](auto&&... args){return f(std::forward<decltype(args)>(args)...);};
or:
auto lam = [](auto&&... args){return f(decltype(args)(args)...);};
which only works "properly" with variables of type auto&&
.
A strong reason to use generic lambdas is for visiting syntax.
boost::variant<int, double> value;
apply_visitor(value, [&](auto&& e){
std::cout << e;
});
Here we are visiting in a polymorphic manner; but in other contexts, the names of the type we are passing isn't interesting:
mutex_wrapped<std::ostream&> os = std::cout;
os.write([&](auto&& os){
os << "hello world\n";
});
Repeating the type of std::ostream&
is noise here; it would be like having to mention the type of a variable every time you use it. Here we are creating a visitor, but no a polymorphic one; auto
is used for the same reason you might use auto
in a for(:)
loop.