Daily C++ Dose: Lambdas (Part 1)

Was reading a blog post about lambdas, and found myself asking what a lambda was really. Then saw there was a great "Back to Basics" presentation on lambdas, so decided to just go through that first. There is a lot to learn about lambdas, so going to break it into multiple posts over multiple days.

Source

Back To Basics: Lambda Expressions - Barbara Geller & Ansel Sermersheim - CppCon 2020

Up to 31:50

Notes

Structure for Lambdas: [](){}

[capture](parameters) -> returntype{body}

Can omit (), but would look weird. So best practice to just keep it.

Added in C++11 but constantly being updated with each new standard.

Return type should always be auto because return type is compiler dependent.

Parameters list: Can be empty or omitted, but highly recommended to keep it there even if it is empty.

Return type: Data type of return item in body, if it can't be deduced.

Body: contains the programming statements and can't be empty

Background

  • Lambda expression is an expression that returns a function object.
  • Some background. function pointer is a data type.
  • Set's up to a point to a function, but you don't de-reference it with a '*'.
void (*myProcess)(int);
myProcess = std::exit;
myProcess(42);

// myProcess looks like a function. notice how it is not *myProcess.

Everything to the right of the equal size is the lambda expression: auto myLamb = [](){ return 17; };

Once the lambda is evaluated, it becomes a closure. What is closure in C++? Term for any function object that is returned from a lambda expression.

Operator Overloading examples:

bool operator==(const T &value)
bool operator>(const T &value)
T operator+=(const T &value)
void operator()()
bool operator()(int value)
double operator()(double d1, double d2)

Terms

  • Function Object Data Type

    • Class or structure with a function call operator method.
    • Class which declares the operator()() method.
  • Function object

    • Instance of a function object data type.
    • It is a callable object.
class Pclass{...}

Pclass pc;
pc.operator()("method 1");
pc("method 2");

//notice how the syntax for method 2 is the same format for calling a 
//function, function pointer or a function object.
  • Avoid using the term Functor

    • most programmers use the term Functor when discussing function objects.
    • best case to use standard terms outlined by the standard.
    • Functor has it's own meaning, so only use Functor if you actually mean it.
      • TODO: add Functor exact defintion.
  • std::function

    • Also added in C++11.
    • Wanted a class to store a function object.
    • Allows to pass a lambda function to another function.

Lambda Captures

Capture clause

Which variables are visible in the body.

Required to be there, even if it is empty.

Variables listed here are captured from the outer scope.

  • capture by value

    • Value is copied as a const value.
    • either local scope or this->
    • variable doesn't matter if out of scope when lambdas is called.
    • Any number of variables in capture clause.
    • Modify captured value? mark entire lambda as mutable. will capture by value. e.g. auto myLamb = [x]() mutable { return ++x; };
  • capture by reference

    • An & is added to indicate capture by lvalue reference.
  • Don't need to capture global variables, already accessible.

Important Note: Capture by Reference

If capture variable is defined prior to lambda, variable will be resolved to that value, even if the variable is modified after the lambda.

If you capture by reference (eg. &x instead of x). then the value of x at the time of access will be used. but beware because it is an address, will be undefined behavior if x is out of scope before invoking the lambda.

C++14 - Generalized Capture

  • By Value
    • [varA = 10] or [varB = x], will save a copy value into assignment
  • By Reference
    • [&varC = y], y must be declared in local scope.
  • By Move
    • [varD = std::move(z)], move occurs where the lambda express is defined.

Capture clause

C++11: [this] capture pointer by value

C++14: [self = *this] capture object by value and initialize new variable

C++17": [*this] capture *this object by value.

Beware of the following

[=](){return x;} Capture all variables used in the body of the lambda. x needs to be defined prior to the expression. x does not need to be in scope since it will copy the value.

[&](){return x;} Capture all references used in body of lambda. Careful with the value since x is a reference. Out of scope x by the time the lambda expression is ran would cause undefined behavior.

Extra Notes

Encourage to pick variable names that makes sense for a return type lambda expression.

Lambda expressions gets run in 2 parts.
- Evaluating, when the lambda expression is defined.
- Evaluating does the capture first.
- Invoking, when the lambda expression is when called.

C++14 allows for generic lambdas. Can use parameters with the auto data type.

Return type:

  • C++11 could have return a different type compared to the defined return type as long as it can be deduced.
  • C++14 multiple return statements, but must return same data type.
  • -> return_type Need the arrow to define return type.

You'll only receive email when Wron publishes a new post

More fromĀ Wron