Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CPlusPlusNotesForProfessionals.pdf
Скачиваний:
47
Добавлен:
20.05.2023
Размер:
5.11 Mб
Скачать

Chapter 73: Lambdas

Parameter

default-capture

capture-list

Details

Specifies how all non-listed variables are captured. Can be = (capture by value) or & (capture by reference). If omitted, non-listed variables are inaccessible within the lambda-body. The defaultcapture must precede the capture-list.

Specifies how local variables are made accessible within the lambda-body. Variables without prefix are captured by value. Variables prefixed with & are captured by reference. Within a class method, this can be used to make all its members accessible by reference. Non-listed variables are inaccessible, unless the list is preceded by a default-capture.

argument-list mutable

throw-specification

attributes

-> return-type lambda-body

Specifies the arguments of the lambda function.

(optional) Normally variables captured by value are const. Specifying mutable makes them nonconst. Changes to those variables are retained between calls.

(optional) Specifies the exception throwing behavior of the lambda function. For example: noexcept or throw(std::exception).

(optional) Any attributes for the lambda function. For example, if the lambda-body always throws an exception then [[noreturn]] can be used.

(optional) Specifies the return type of the lambda function. Required when the return type cannot be determined by the compiler.

A code block containing the implementation of the lambda function.

Section 73.1: What is a lambda expression?

A lambda expression provides a concise way to create simple function objects. A lambda expression is a prvalue whose result object is called closure object, which behaves like a function object.

The name 'lambda expression' originates from lambda calculus, which is a mathematical formalism invented in the 1930s by Alonzo Church to investigate questions about logic and computability. Lambda calculus formed the basis of LISP, a functional programming language. Compared to lambda calculus and LISP, C++ lambda expressions share the properties of being unnamed, and to capture variables from the surrounding context, but they lack the ability to operate on and return functions.

A lambda expression is often used as an argument to functions that take a callable object. That can be simpler than creating a named function, which would be only used when passed as the argument. In such cases, lambda expressions are generally preferred because they allow defining the function objects inline.

A lambda consists typically of three parts: a capture list [], an optional parameter list () and a body {}, all of which can be empty:

[](){}

// An empty lambda, which does and returns nothing

 

 

Capture list

[] is the capture list. By default, variables of the enclosing scope cannot be accessed by a lambda. Capturing a

variable makes it accessible inside the lambda, either as a copy or as a reference. Captured variables become a part of the lambda; in contrast to function arguments, they do not have to be passed when calling the lambda.

int a = 0;

//

Define an integer variable

auto f = []()

{ return a*9; }; //

Error: 'a' cannot be accessed

auto f = [a]()

{ return a*9; }; //

OK, 'a' is "captured" by value

auto f = [&a]() { return a++; }; //

OK, 'a' is "captured" by reference

 

//

Note: It is the responsibility of the programmer

 

//

to ensure that a is not destroyed before the

 

 

 

 

 

 

GoalKicker.com – C++ Notes for Professionals

382

 

//

lambda is called.

auto b = f();

// Call the lambda function. a is taken from the capture list and

not passed here.

 

 

 

 

 

Parameter list

() is the parameter list, which is almost the same as in regular functions. If the lambda takes no arguments, these parentheses can be omitted (except if you need to declare the lambda mutable). These two lambdas are equivalent:

auto call_foo = [x](){ x.foo(); }; auto call_foo2 = [x]{ x.foo(); };

Version ≥ C++14

The parameter list can use the placeholder type auto instead of actual types. By doing so, this argument behaves like a template parameter of a function template. Following lambdas are equivalent when you want to sort a vector in generic code:

auto sort_cpp11 = [](std::vector<T>::const_reference lhs, std::vector<T>::const_reference rhs) { return lhs < rhs; };

auto sort_cpp14 = [](const auto &lhs, const auto &rhs) { return lhs < rhs; };

Function body

{} is the body, which is the same as in regular functions.

Calling a lambda

A lambda expression's result object is a closure, which can be called using the operator() (as with other function objects):

int multiplier = 5;

auto timesFive = [multiplier](int a) { return a * multiplier; }; std::out << timesFive(2); // Prints 10

multiplier = 15;

std::out << timesFive(2); // Still prints 2*5 == 10

Return Type

By default, the return type of a lambda expression is deduced.

[](){ return true; };

In this case the return type is bool.

You can also manually specify the return type using the following syntax:

[]() -> bool { return true; };

Mutable Lambda

Objects captured by value in the lambda are by default immutable. This is because the operator() of the generated closure object is const by default.

auto func = [c = 0](){++c; std::cout << c;}; // fails to compile because ++c // tries to mutate the state of

GoalKicker.com – C++ Notes for Professionals

383

// the lambda.

Modifying can be allowed by using the keyword mutable, which make the closer object's operator() non-const:

auto func = [c = 0]() mutable {++c; std::cout << c;};

If used together with the return type, mutable comes before it.

auto func = [c = 0]() mutable -> int {++c; std::cout << c; return c;};

An example to illustrate the usefulness of lambdas

Before C++11:

Version < C++11

// Generic functor used for comparison struct islessthan

{

islessthan(int threshold) : _threshold(threshold) {}

bool operator()(int value) const

{

return value < _threshold;

}

private:

int _threshold;

};

// Declare a vector

const int arr[] = { 1, 2, 3, 4, 5 }; std::vector<int> vec(arr, arr+5);

// Find a number that's less than a given input (assume this would have been function input) int threshold = 10;

std::vector<int>::iterator it = std::find_if(vec.begin(), vec.end(), islessthan(threshold));

Since C++11:

Version ≥ C++11

// Declare a vector

std::vector<int> vec{ 1, 2, 3, 4, 5 };

// Find a number that's less than a given input (assume this would have been function input) int threshold = 10;

auto it = std::find_if(vec.begin(), vec.end(), [threshold](int value) { return value < threshold; });

Section 73.2: Specifying the return type

For lambdas with a single return statement, or multiple return statements whose expressions are of the same type, the compiler can deduce the return type:

// Returns bool, because "value > 10" is a comparison which yields a Boolean result auto l = [](int value) {

return value > 10;

}

For lambdas with multiple return statements of di erent types, the compiler can't deduce the return type:

GoalKicker.com – C++ Notes for Professionals

384

// error: return types must match if lambda has unspecified return type auto l = [](int value) {

if (value < 10) { return 1;

} else { return 1.5;

}

};

In this case you have to specify the return type explicitly:

// The return type is specified explicitly as 'double' auto l = [](int value) -> double {

if (value < 10) { return 1;

} else { return 1.5;

}

};

The rules for this match the rules for auto type deduction. Lambdas without explicitly specified return types never return references, so if a reference type is desired it must be explicitly specified as well:

auto copy = [](X& x) { return x; }; // 'copy' returns an X, so copies its input auto ref = [](X& x) -> X& { return x; }; // 'ref' returns an X&, no copy

Section 73.3: Capture by value

If you specify the variable's name in the capture list, the lambda will capture it by value. This means that the generated closure type for the lambda stores a copy of the variable. This also requires that the variable's type be copy-constructible:

int a = 0;

[a]() {

return a; // Ok, 'a' is captured by value

};

Version < C++14

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

[p]() { // Compile error; `unique_ptr` is not copy-constructible return p->createWidget();

};

From C++14 on, it is possible to initialize variables on the spot. This allows move only types to be captured in the lambda.

Version ≥ C++14

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

[p = std::move(p)]() {

return p->createWidget();

};

Even though a lambda captures variables by value when they are given by their name, such variables cannot be modified within the lambda body by default. This is because the closure type puts the lambda body in a declaration of operator() const.

GoalKicker.com – C++ Notes for Professionals

385