官术网_书友最值得收藏!

Using lambdas with standard algorithms

One of the most important modern features of C++ is lambda expressions, also referred to as lambda functions or simply lambdas. Lambda expressions enable us to define anonymous function objects that can capture variables in the scope and be invoked or passed as arguments to functions. Lambdas are useful for many purposes, and in this recipe, we will learn how to use them with standard algorithms.

Getting ready

In this recipe, we'll discuss standard algorithms that take an argument that's a function or predicate that's applied to the elements it iterates through. You need to know what unary and binary functions are and what predicates and comparison functions are. You also need to be familiar with function objects because lambda expressions are syntactic sugar for function objects.

How to do it...

You should prefer to use lambda expressions to pass callbacks to standard algorithms instead of functions or function objects:

  • Define anonymous lambda expressions in the place of the call if you only need to use the lambda in a single place:
    auto numbers =
      std::vector<int>{ 0, 2, -3, 5, -1, 6, 8, -4, 9 };
    auto positives = std::count_if(
      std::begin(numbers), std::end(numbers),
      [](int const n) {return n > 0; });
    
  • Define a named lambda, that is, one assigned to a variable (usually with the auto specifier for the type), if you need to call the lambda in multiple places:
    auto ispositive = [](int const n) {return n > 0; };
    auto positives = std::count_if(
      std::begin(numbers), std::end(numbers), ispositive);
    
  • Use generic lambda expressions if you need lambdas that only differ in terms of their argument types (available since C++14):
    auto positives = std::count_if(
      std::begin(numbers), std::end(numbers),
      [](auto const n) {return n > 0; });
    

How it works...

The non-generic lambda expression shown in the second bullet takes a constant integer and returns true if it is greater than 0, or false otherwise. The compiler defines an unnamed function object with the call operator, which has the signature of the lambda expression:

struct __lambda_name__
{
  bool operator()(int const n) const { return n > 0; }
};

The way the unnamed function object is defined by the compiler depends on the way we define the lambda expression that can capture variables, use the mutable specifier or exception specifications, or have a trailing return type. The __lambda_name__ function object shown earlier is actually a simplification of what the compiler generates because it also defines a default copy and move constructor, a default destructor, and a deleted assignment operator.

It must be well understood that the lambda expression is actually a class. In order to call it, the compiler needs to instantiate an object of the class. The object instantiated from a lambda expression is called a lambda closure.

In the following example, we want to count the number of elements in a range that are greater than or equal to 5 and less than or equal to 10. The lambda expression, in this case, will look like this:

auto numbers = std::vector<int>{ 0, 2, -3, 5, -1, 6, 8, -4, 9 };
auto minimum { 5 };
auto maximum { 10 };
auto inrange = std::count_if(
    std::begin(numbers), std::end(numbers),
    [minimum, maximum](int const n) {
      return minimum <= n && n <= maximum;});

This lambda captures two variables, minimum and maximum, by copy (that is, value). The resulting unnamed function object created by the compiler looks very much like the one we defined earlier. With the default and deleted special members mentioned earlier, the class looks like this:

class __lambda_name_2__
{
  int minimum_;
  int maximum_;
public:
  explicit __lambda_name_2__(int const minimum, int const maximum) :
  minimum_( minimum), maximum_( maximum)
  {}
  __lambda_name_2__(const __lambda_name_2__&) = default;
  __lambda_name_2__(__lambda_name_2__&&) = default;
  __lambda_name_2__& operator=(const __lambda_name_2__&)
    = delete;
  ~__lambda_name_2__() = default;
  bool operator() (int const n) const
 {
    return minimum_ <= n && n <= maximum_;
  }
};

The lambda expression can capture variables by copy (or value) or by reference, and different combinations of the two are possible. However, it is not possible to capture a variable multiple times and it is only possible to have & or = at the beginning of the capture list.

A lambda can only capture variables from an enclosing function scope. It cannot capture variables with static storage duration (that is, variables declared in a namespace scope or with the static or external specifier).

The following table shows various combinations for lambda captures semantics:

The general form of a lambda expression, as of C++17, looks like this:

[capture-list](params) mutable constexpr exception attr -> ret
{ body }

All parts shown in this syntax are actually optional except for the capture list, which can, however, be empty, and the body, which can also be empty. The parameter list can actually be omitted if no parameters are needed. The return type does not need to be specified as the compiler can infer it from the type of the returned expression. The mutable specifier (which tells the compiler the lambda can actually modify variables captured by copy), the constexpr specifier (which tells the compiler to generate a constexpr call operator), and the exception specifiers and attributes are all optional.

The simplest possible lambda expression is []{}, though it is often written as [](){}.

The latter two examples in the preceding table are forms of generalized lambda captures. These were introduced in C++14 to allow us to capture variables with move-only semantics, but they can also be used to define new arbitrary objects in the lambda. The following example shows how variables can be captured by move with generalized lambda captures:

auto ptr = std::make_unique<int>(42);
auto l = [lptr = std::move(ptr)](){return ++*lptr;};

Lambdas that are written in class methods and need to capture class data members can do so in several ways:

  • Capturing individual data members with the form [x=expr]:
    struct foo
    {
      int         id;
      std::string name;
      auto run()
     {
        return [i=id, n=name] { std::cout << i << ' ' << n << '\n'; };
      }
    };
    
  • Capturing the entire object with the form [=] (please notice that the implicit capture of pointer this via [=] is deprecated in C++20):
    struct foo
    {
      int         id;
      std::string name;
      auto run()
     {
        return [=] { std::cout << id << ' ' << name << '\n'; };
      }
    };
    
  • Capturing the entire object by capturing the this pointer. This is necessary if you need to invoke other methods of the class. This can be captured either as [this] when the pointer is captured by value, or [*this] when the object itself is captured by value. This can make a big difference if the object may go out of scope after the capture occurs but before the lambda is invoked:
    struct foo
    {
      int         id;
      std::string name;
      auto run()
     {
        return[this]{ std::cout << id << ' ' << name << '\n'; };
      }
    };
    auto l = foo{ 42, "john" }.run();
    l(); // does not print 42 john
    

In this latter case seen here, the correct capture should be [*this] so that object is copied by value. In this case, invoking the lambda will print 42 john, even though the temporary has gone out of scope.

The C++20 standard introduces several changes to capturing the pointer this:

  • It deprecates the implicit capturing of this when you use [=]. This will produce a deprecation warning to be issued by the compiler.
  • It introduces explicit capturing of the this pointer by value when you want to capture everything with [=, this]. You can still only capture the pointer this with a [this] capture.

There are cases where lambda expressions only differ in terms of their arguments. In this case, the lambdas can be written in a generic way, just like templates, but using the auto specifier for the type parameters (no template syntax is involved). This is addressed in the next recipe, as noted in the upcoming See also section.

See also

  • Using generic and template lambdas to learn how to use auto for lambda parameters and how to define template lambdas in C++20
  • Writing a recursive lambda to understand the technique we can use to make a lambda call itself recursively
主站蜘蛛池模板: 青河县| 扎兰屯市| 嘉黎县| 漳平市| 鸡东县| 浮梁县| 噶尔县| 黔江区| 鸡东县| 莒南县| 惠水县| 嘉义市| 普格县| 泸西县| 邵武市| 苍山县| 宝山区| 淅川县| 大新县| 平顺县| 亚东县| 宝兴县| 体育| 盈江县| 陆良县| 兴化市| 广西| 白朗县| 长泰县| 无极县| 福贡县| 无为县| 英吉沙县| 石屏县| 巴林左旗| 江口县| 明水县| 区。| 慈溪市| 山东省| 蒙山县|