2.1.7 Lambdas

What was the problem

How would we pass a custom comparator to std::max_element before C++11, say to compare strings by increasing size? Certainly by writing a free or static function taking two strings as arguments and returning the result of the comparison.

// Comparator for strings by increasing size. 
// Declared as a free function. 
static bool string_size_less_equal 
(const std::string& lhs, const std::string& rhs) 
{ 
  return lhs.size() < rhs.size(); 
} 
 
std::size_t 
largest_string_size(const std::vector<std::string>& strings) 
{ 
  // The comparator is outside the scope of this function. 
  return 
    std::max_element 
      (strings.begin(), strings.end(), &string_size_less_equal) 
    ->size(); 
}// Need a function object if the comparator has parameters. 
struct string_size_equals 
{ 
  std::size_t size; 
 
  bool operator()(const std::string& string) const 
  { 
    return string.size() == size; 
  } 
}; 
 
bool has_string_of_size 
(const std::vector<std::string>& strings, std::size_t s) 
{ 
  string_size_equals comparator = {s}; 
 
  return 
    std::find_if(strings.begin(), strings.end(), comparator) 
      != strings.end(); 
}

Now what if we needed a parameterized comparator, for example to call std::find_if to search for a string of a specific size? The free function would not allow to store the size, so we would certainly write a functor object, i.e. a struct storing the size parameter and defining an operator() receiving a string and returning the result of the comparison.

How the Problem is Solved

Having the comparators as independent objects or functions is nice if they are used in many places, but for a single use it is undoubtedly too verbose. And confusing too. Wouldn’t it be clearer if the single-use comparator was declared next to where it is used? Hopefully this is something we can use with lambdas, starting with C++11:

bool has_string_of_size 
(const std::vector<std::string>& strings, std::size_t s) 
{ 
  // Only three lines for the equivalent of the type 
  // declaration, definition and the instantiation. 
  // The third argument is a lambda. 
  return std::find_if 
    (strings.begin(), strings.end(), 
     [=](const std::string& string) -> bool 
     { 
       return string.size() == s; 
     }) 
    != strings.end(); 
}

Guideline

Mind the next reader: keep your lambdas small.

The Internals of Lambdas

A declaration like

[a, b, c]( /* arguments */ ) -> T { /* statements */ }struct something 
{ 
  T operator()( /* arguments */ ) const { /* statements */ } 
 
  /* deduced type */ a; 
  /* deduced type */ b; 
  /* deduced type */ c; 
};

is equivalent to

Actually the compiler will create a unique type like this struct for every lambda we write.

More conceptually, the parts of a lambda are the following:

[capture](arguments) specifier ->return_type { body }

Where: