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(); 
}

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.

// 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(); 
}
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? Thankfully this is something we can do 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 */ }

is equivalent to

struct something 
{ 
  T operator()( /* arguments */ ) const { /* statements */ } 
 
  /* deduced type */ a; 
  /* deduced type */ b; 
  /* deduced type */ c; 
};

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: