How to use lambda expression to sort a vector of custom objects (C++11)

In the last article on sorting a vector of custom objects we achieved our goal with the help of operator overloading or with a function object (for sorting according to a specific attribute). In this article, we are going to do the same with the help of a lambda expression introduced by the C++11. As an example, I am going to show how to sort a vector of Skyscraper objects. In the first variant of the problem, we are going to sort the objects according to their height. In the second variant, we are going to sort the Skyscraper objects according to two different attributes, namely, the height and the number of floors.

Lambda expressions

The sorting algorithm needs a rule according to which to sort the custom objects. Our implementation will use lambda expressions for this purpose. A lambda expression is actually a syntactic shortcut for a function object, i.e. an object that can be called as if it was a function (to that end, it defines the operator() ). A lambda expression enables to bypass the creation of a function object and that is the reason why it is often used to encapsulate short codes passed to algorithms. The basic syntax of a lambda expression goes as follows:

[captures] (parameter list) -> return-type 
{ 
       lambda body; 
}

The parameter list resembles a parameter list of an ordinary function and it is optional. In our case, the parameter list consists of two references to Skyscraper objects to compare, i.e. (const Skyscraper& s1, const Skyscraper& s2). These will be used by our sorting algorithm to decide ordering of objects.

[] (const Skyscraper& s1, const Skyscraper& s2) -> bool
{
       return s1.getHeight() < s2.getHeight();
};

The return type is optional and resembles a return type of an ordinary function. It can be omitted if the lambda body contains just one return statement or the expression does not return a value. In our case, the return type is bool. I chose to include it in my code, but the code would work just fine without it.

[] (const Skyscraper& s1, const Skyscraper& s2) -> bool 
{
       return s1.getHeight() < s2.getHeight();
};

The lambda body contains just what an ordinary function would contain. The lambda body can contain parameters, locally declared variables, global variables, class data members if declared within a class and captured variables (more on that soon). In our case, the lambda body contains the details about how the comparison is performed and the return statement.

[] (const Skyscraper& s1, const Skyscraper& s2) -> bool
{
       return s1.getHeight() < s2.getHeight();
};

The captures are the variables that a lambda expression can obtain (capture) from the surrounding scope. Our capture list will remain empty for the Variant 1 of the problem, however, for the Variant 2 of the problem the capture list will be used to store the information about the attribute according to which we should perform our sorting.

[] (const Skyscraper& s1, const Skyscraper& s2) -> bool
{
       return s1.getHeight() < s2.getHeight();
};
[property](const Skyscraper& s1, const Skyscraper& s2) -> bool 
{
        if(property == FLOORS)
            return s1.getNrFloors() < s2.getNrFloors();
        else
            return s1.getHeight() < s2.getHeight();
};

Sorting Skyscrapers - Variant 1

Below, you can see the class Skyscraper with its attributes name and height. We are going to use this class to demonstrate sorting according to a single parameter, namely height.

class Skyscraper
{
public:
    Skyscraper(const std::string& name, double h);
    std::string getName() const {return m_name;}
    double getHeight() const {return m_height;}
    void print() const;

private:
    std::string m_name;
    double m_height;
};

Let's look at the main.cpp below. We create four Skyscraper objects and put them into a vector. After that, we define a lambda expression called sortRuleLambda with references to two Skyscraper objects as parameters, bool as a return type and no captures. The body of the lambda expression performs the comparison and returns the result. At the end, we pass the sortRuleLamda as the third parameter of the sort algorithm and print out the result.

int main()
{
    std::vector<Skyscraper> skyscrapers {
        Skyscraper ("Empire State", 381),
        Skyscraper ("Petronas", 452),
        Skyscraper ("Burj Khalifa", 828),
        Skyscraper ("Taipei", 509)
    };

    auto sortRuleLambda = [] (const Skyscraper& s1, const Skyscraper& s2) -> bool
    {
       return s1.getHeight() < s2.getHeight();
    };
    std::sort(skyscrapers.begin(), skyscrapers.end(), sortRuleLambda);

    for (const auto& s : skyscrapers)
        s.print();

    return 0;
}

The program outputs the following result:

Empire State 381
Petronas 452
Taipei 509
Burj Khalifa 828

How to sort vector of objects according to a specific attribute - use of a capture

To show you how to sort objects according to a specific attribute, I introduced an extra attribute into the Skyscraper class - namely number of floors. Now we have two different attributes according to which we can sort and we somehow need to inform the sorting function about this attribute.

class Skyscraper
{
public:
    Skyscraper(const std::string& name, double h, int nrFloors);
    std::string getName() const {return m_name;}
    double getHeight() const {return m_height;}
    int getNrFloors() const {return m_nrFloors;}
    void print() const;

private:
    std::string m_name;
    double m_height;
    int m_nrFloors;
};

In order to inform the sorting function about the attribute according to which it should sort our Skyscraper objects, we need to introduce an extra variable named 'property'. The variable 'property' can assume two values, namely FLOORS or HEIGHT defined in itemnames.h. How do we pass this information to our lambda expression ? We pass this information in the [captures] of the lambda expression. In this way, we inform the lambda expression about the attribute according to which it should perform the sorting.

int main()
{
    std::vector<Skyscraper> skyscrapers {
        Skyscraper ("Empire State", 381, 102),
        Skyscraper ("Petronas", 452, 88),
        Skyscraper ("Burj Khalifa", 828, 163),
        Skyscraper ("Taipei", 509, 101)
    };

    int property = FLOORS;
    auto sortRuleLambda = [property] (const Skyscraper& s1, const Skyscraper& s2) -> bool
    {
        if(property == FLOORS)
            return s1.getNrFloors() < s2.getNrFloors();
        else
            return s1.getHeight() < s2.getHeight();
    };

    std::sort(skyscrapers.begin(), skyscrapers.end(), sortRuleLambda);

    for (const auto& s : skyscrapers)
        s.print();

    return 0;
}
If you run the code, you will get the following output (the second number represents the number of floors).

Petronas 452 88
Taipei 509 101
Empire State 381 102
Burj Khalifa 828 163

As an exercise, try changing the variable to HEIGHT.

Note: At the beginning I mentioned that a lambda expression is a syntactic shortcut for a function object which overloads the operator(). Below you can see, how an equivalent function object would look like. The body of the lambda expression would be equivalent to the body of the overloaded operator(). The parameters of the lambda expression (const Skyscraper& s1, const Skyscraper& s2) would be equivalent to the parameters of the overloaded operator(). Finally, the variable 'property' which was a capture of our lambda expression would become an attribute of the function object. An instance of such a function object would be passed as a third parameter to the sort algorithm.

struct EntityComp {
  int property;
  EntityComp(int property) {this->property = property;}
  bool operator()(const Skyscraper& s1, const Skyscraper& s2) const {
      if(property == FLOORS)
          return s1.getNrFloors() < s2.getNrFloors();
      else
          return s1.getHeight() < s2.getHeight();
  }
};
std::sort ( skyscrapers.begin(), skyscrapers.end(), EntityComp(FLOORS) );

Latest update: 20.04.2016
Created: 2015
© Walletfox.com, 2017