Lei Mao bio photo

Lei Mao

Machine Learning, Artificial Intelligence, Computer Science.

Twitter Facebook LinkedIn GitHub   G. Scholar E-Mail RSS

Introduction

C++ lambda expression defines an anonymous function object (a closure) that could be passed as an argument to a function. Recently I just realized that my knowledge about the usages of lambda expression had been quite limited.


In this blog post, I would like to document the usages of lambda expression which I knew and did not know.

Examples

/* 
 * lambda_expression.cpp
 */
#include <iostream>

int main()
{
    double rate = 0.01;
    
    // Capture by reference
    auto f0 = [&rate](){rate *= 2;};
    // Capture variables in the function body by reference by default
    auto f1 = [&](){rate *= 2;};
    // Capture by const value
    auto f2 = [rate](double x){return (1 + rate) * x;};
    // Specify return type
    auto f3 = [rate](double x)->double{return (1 + rate) * x;};
    auto f4 = [rate](double x)->int{return (1 + rate) * x;};
    // Compile time polymorphism
    // C++14 features
    auto f5 = [](auto x){return 2 * x;};
    auto f6 = []<typename T>(T x){return 2 * x;};
    auto f7 = []<typename T>(T x)->T{return 2 * x;};
    // Capture by mutable value
    // This causes error because rate is immutable
    // auto f8 = [rate](double x){rate += 1.0;};
    // Rate is now mutable
    // But the mutation would not change the value outside the lambda expression scope
    auto f8 = [rate]() mutable {rate += 1.0; return rate;};

    f0();
    std::cout << rate << std::endl;
    f1();
    std::cout << rate << std::endl;
    std::cout << f2(10) << std::endl;
    std::cout << f3(10) << std::endl;
    std::cout << f4(10) << std::endl;
    std::cout << f8() << std::endl;
    std::cout << rate << std::endl;
}

To compile the program, please run the following command in the terminal.

$ g++ lambda_expression.cpp -o lambda_expression --std=c++14

Usages

Typical Usages

Typically, a lambda expression consists of three parts: a capture list [], an optional parameter list () and a body {}, all of which can be empty. One of the simplest lambda expressions is

[](){}

Since the parameter list () is optional, actually the simplest lambda expression is

[]{}

Lambda expression could be passed to or assigned to function pointers and std::function, or passed to function template.

Capture List

Capture list is used to make the variables outside the lambda expression accessible inside the lambda expression, via copy or reference. This is somewhat similar to C++ functor which could also change the inner state of the function object.

// Capture by reference
auto f0 = [&rate](){rate *= 2;};
// Capture variables in the function body by reference by default
auto f1 = [&](){rate *= 2;};
// Capture by const value
auto f2 = [rate](double x){return (1 + rate) * x;};
// Capture by mutable value
// This causes error because rate is immutable
// auto f8 = [rate](double x){rate += 1.0;};
// Rate is now mutable
// But the mutation would not change the value outside the lambda expression scope
auto f8 = [rate]() mutable {rate += 1.0; return rate;};

Parameter List

Parameter list is just like the parameter list for ordinary functions. To allow compile-time polymorphism, we could use template function or auto as the type for arguments and let the compiler to deduct during compile time.

auto f5 = [](auto x){return 2 * x;};
auto f6 = []<typename T>(T x){return 2 * x;};

Function Body

Function body has nothing special.

Return Type

By default, the return type of the lambda expression could be deduced by the compiler at compile time. We could also optionally include the return type in the lambda expression to ask the compiler to double check it for us.

auto f3 = [rate](double x)->double{return (1 + rate) * x;};
auto f4 = [rate](double x)->int{return (1 + rate) * x;};
auto f7 = []<typename T>(T x)->T{return 2 * x;};

Conclusions

I actually did not know how to use the capture list previously. Making a good use of it would make life a lot easier in some scenarios.