Lambda
Lambda
Lambda
7: Capture by reference
If you precede a local variable's name with an &, then the variable will be captured by reference. Conceptually, this
means that the lambda's closure type will have a reference variable, initialized as a reference to the corresponding
variable from outside of the lambda's scope. Any use of the variable in the lambda body will refer to the original
variable:
set();
assert(a == 1);
Of course, capturing by reference means that the lambda must not escape the scope of the variables it captures.
So you could call functions that take a function, but you must not call a function that will store the lambda beyond
the scope of your references. And you must not return the lambda.
Lambda functions can take arguments of arbitrary types. This allows a lambda to be more generic:
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;}
};
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:
or:
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.
Parameter pack unpacking traditionally requires writing a helper function for each time you want to do it.
template<std::size_t...Is>
void print_indexes( std::index_sequence<Is...> ) {
using discard=int[];
(void)discard{0,((void)(
std::cout << Is << '\n' // here Is is a compile-time constant.
),0)...};
}
template<std::size_t I>
void print_indexes_upto() {
return print_indexes( std::make_index_sequence<I>{} );
}
The print_indexes_upto wants to create and unpack a parameter pack of indexes. In order to do so, it must call a
helper function. Every time you want to unpack a parameter pack you created, you end up having to create a
You can unpack parameter packs into a set of invocations of a lambda, like this:
template<std::size_t I>
using index_t = std::integral_constant<std::size_t, I>;
template<std::size_t I>
constexpr index_t<I> index{};
template<class=void, std::size_t...Is>
auto index_over( std::index_sequence<Is...> ) {
return [](auto&& f){
using discard=int[];
(void)discard{0,(void(
f( index<Is> )
),0)...};
};
}
template<std::size_t N>
auto index_over(index_t<N> = {}) {
return index_over( std::make_index_sequence<N>{} );
}
Version ≥ C++17
template<class=void, std::size_t...Is>
auto index_over( std::index_sequence<Is...> ) {
return [](auto&& f){
((void)(f(index<Is>)), ...);
};
}
Once you have done that, you can use this to replace having to manually unpack parameter packs with a second
overload in other code, letting you unpack parameter packs "inline":
from_zero_to_N(
[&](auto i){
using std::get;
f( get<i>( std::forward<Tup>(tup) ) );
}
);
}
The auto i passed to the lambda by the index_over is a std::integral_constant<std::size_t, ???>. This has a
constexpr conversion to std::size_t that does not depend on the state of this, so we can use it as a compile-time
constant, such as when we pass it to std::get<i> above.
which is much shorter, and keeps logic in the code that uses it.
Lambdas can capture expressions, rather than just variables. This permits lambdas to store move-only types:
auto p = std::make_unique<T>(...);
This moves the outer p variable into the lambda capture variable, also called p. lamb now owns the memory
allocated by make_unique. Because the closure contains a type that is non-copyable, this means that lamb is itself
non-copyable. But it can be moved:
Note that std::function<> requires that the values stored be copyable. You can write your own move-only-
requiring std::function, or you could just stuff the lambda into a shared_ptr wrapper:
takes our move-only lambda and stuffs its state into a shared pointer then returns a lambda that can be copied,
and then stored in a std::function or similar.
Generalized capture uses auto type deduction for the variable's type. It will declare these captures as values by
default, but they can be references as well:
int a = 0;
auto lamb = [&v = a](int add) //Note that `a` and `v` have different names
{
v += add; //Modifies `a`
};
Generalize capture does not need to capture an external variable at all. It can capture an arbitrary expression:
This is useful for giving lambdas arbitrary values that they can hold and potentially modify, without having to
declare them externally to the lambda. Of course, that is only useful if you do not intend to access those variables
after the lambda has completed its work.
auto sorter = [](int lhs, int rhs) -> bool {return lhs < rhs;};
Calling this function pointer behaves exactly like invoking operator() on the lambda. This function pointer is in no
way reliant on the source lambda closure's existence. It therefore may outlive the lambda closure.
This feature is mainly useful for using lambdas with APIs that deal in function pointers, rather than C++ function
objects.
Version ≥ C++14
Conversion to a function pointer is also possible for generic lambdas with an empty capture list. If necessary,
template argument deduction will be used to select the correct specialization.
auto sorter = [](auto lhs, auto rhs) { return lhs < rhs; };
using func_ptr = bool(*)(int, int);
func_ptr sorter_func = sorter; // deduces int, int
// note however that the following is ambiguous
// func_ptr sorter_func2 = +sorter;
public:
// Constructor
inline Functor(T1 val, T2& ref) : val(val), ref(ref) {}
// Functor body
R operator()(int arg1, int arg2) const {
/* lambda-body */
return R();
}
};
If the lambda function is mutable then make the functor's call-operator non-const, i.e.: