Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

Lambda

Download as pdf or txt
Download as pdf or txt
You are on page 1of 6

Section 73.

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:

// Declare variable 'a'


int a = 0;

// Declare a lambda which captures 'a' by reference


auto set = [&a]() {
a = 1;
};

set();
assert(a == 1);

The keyword mutable is not needed, because a itself is not const.

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.

Section 73.8: Generic lambdas


Version ≥ c++14

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;

GoalKicker.com – C++ Notes for Professionals 390


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.

Section 73.9: Using lambdas for inline parameter pack


unpacking
Version ≥ C++14

Parameter pack unpacking traditionally requires writing a helper function for each time you want to do it.

In this toy example:

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

GoalKicker.com – C++ Notes for Professionals 391


custom helper function to do it.

This can be avoided with lambdas.

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

With fold expressions, index_over() can be simplified to:

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":

template<class Tup, class F>


void for_each_tuple_element(Tup&& tup, F&& f) {
using T = std::remove_reference_t<Tup>;
using std::tuple_size;
auto from_zero_to_N = index_over< tuple_size<T>{} >();

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.

To go back to the toy example at the top, rewrite it as:

GoalKicker.com – C++ Notes for Professionals 392


template<std::size_t I>
void print_indexes_upto() {
index_over(index<I>)([](auto i){
std::cout << i << '\n'; // here i is a compile-time constant
});
}

which is much shorter, and keeps logic in the code that uses it.

Live example to play with.

Section 73.10: Generalized capture


Version ≥ C++14

Lambdas can capture expressions, rather than just variables. This permits lambdas to store move-only types:

auto p = std::make_unique<T>(...);

auto lamb = [p = std::move(p)]() //Overrides capture-by-value of `p`.


{
p->SomeFunc();
};

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:

auto lamb_copy = lamb; //Illegal


auto lamb_move = std::move(lamb); //legal.

Now lamb_move owns the memory.

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:

auto shared_lambda = [](auto&& f){


return [spf = std::make_shared<std::decay_t<decltype(f)>>(decltype(f)(f))]
(auto&&...args)->decltype(auto) {
return (*spf)(decltype(args)(args)...);
};
};
auto lamb_shared = shared_lambda(std::move(lamb_move));

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`
};

GoalKicker.com – C++ Notes for Professionals 393


lamb(20); //`a` becomes 20.

Generalize capture does not need to capture an external variable at all. It can capture an arbitrary expression:

auto lamb = [p = std::make_unique<T>(...)]()


{
p->SomeFunc();
}

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.

Section 73.11: Conversion to function pointer


If a lambda's capture list is empty, then the lambda has an implicit conversion to a function pointer that takes the
same arguments and returns the same return type:

auto sorter = [](int lhs, int rhs) -> bool {return lhs < rhs;};

using func_ptr = bool(*)(int, int);


func_ptr sorter_func = sorter; // implicit conversion

Such a conversion may also be enforced using unary plus operator:

func_ptr sorter_func2 = +sorter; // enforce implicit conversion

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;

Section 73.12: Porting lambda functions to C++03 using


functors
Lambda functions in C++ are syntactic sugar that provide a very concise syntax for writing functors. As such,
equivalent functionality can be obtained in C++03 (albeit much more verbose) by converting the lambda function
into a functor:

// Some dummy types:


struct T1 {int dummy;};
struct T2 {int dummy;};
struct R {int dummy;};

GoalKicker.com – C++ Notes for Professionals 394


// Code using a lambda function (requires C++11)
R use_lambda(T1 val, T2 ref) {
// Use auto because the type of the lambda is unknown.
auto lambda = [val, &ref](int arg1, int arg2) -> R {
/* lambda-body */
return R();
};
return lambda(12, 27);
}

// The functor class (valid C++03)


// Similar to what the compiler generates for the lambda function.
class Functor {
// Capture list.
T1 val;
T2& ref;

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();
}
};

// Equivalent to use_lambda, but uses a functor (valid C++03).


R use_functor(T1 val, T2 ref) {
Functor functor(val, ref);
return functor(12, 27);
}

// Make this a self-contained example.


int main() {
T1 t1;
T2 t2;
use_functor(t1,t2);
use_lambda(t1,t2);
return 0;
}

If the lambda function is mutable then make the functor's call-operator non-const, i.e.:

R operator()(int arg1, int arg2) /*non-const*/ {


/* lambda-body */
return R();
}

GoalKicker.com – C++ Notes for Professionals 395

You might also like