Skip to content
Educora
Advanced20 min10 / 10

Modern C++: lambdas, structured bindings and constexpr

Write short, reliable code with features from C++11 to C++20: lambda expressions, structured bindings, `const` and `constexpr`.

Check yourself
In this lesson you will learn
  • Describe how the C++ standards evolved and their key additions
  • Explain the difference between const and constexpr
  • Unpack pairs and map entries with structured bindings
  • Write lambda expressions and use them with STL algorithms

C++ has been around for more than 40 years, yet it has not grown old: the language is updated regularly. The difference between C++ code written in 1998 and today's code is like the difference between a push-button phone and a smartphone. In this lesson you will learn the modern C++ features that make code shorter, more readable and safer.

C++ standards

  1. 1998
    C++98 — the first international standard

    The language became an ISO standard, with the STL as part of it.

  2. 2011
    C++11 — “a new language”

    auto, range-based for, lambdas, nullptr, constexpr, std::unique_ptr.

  3. 2014
    C++14 — small improvements

    std::make_unique, lambdas with auto parameters.

  4. 2017
    C++17

    Structured bindings, std::optional, if constexpr.

  5. 2020
    C++20 — a major update

    Concepts, ranges, modules, std::format.

const and constexpr

const says that a variable will not change after its first value, but that value may be computed while the program runs. constexpr is stricter: the value must be known at compile time. When a constexpr function is called with constant arguments, the compiler computes the result itself — no time is spent at run time. static_assert checks a condition at compile time.

C++
#include <iostream>
#include <iterator>

constexpr int cube(int x) {
    return x * x * x;
}

int main() {
    constexpr int volume = cube(3);
    static_assert(volume == 27, "wrong volume");

    int boxes[cube(2)] = {};
    const int players = 4;

    std::cout << "Volume: " << volume << '\n';
    std::cout << "Boxes: " << std::size(boxes) << '\n';
    std::cout << "Players: " << players << '\n';
    return 0;
}
Expected output
Volume: 27
Boxes: 8
Players: 4
cube(2) became 8 at compile time, so it could be used as an array size.

Structured bindings

Structured bindings, which arrived in C++17, unpack a pair, a tuple or a simple struct into separate variables in one line: auto [low, high] = ...;. They are especially handy when looping over a std::map: instead of entry.first and entry.second you write meaningful names.

C++
#include <iostream>
#include <map>
#include <string>
#include <utility>

std::pair<int, int> minMax(int a, int b) {
    if (a < b) {
        return {a, b};
    }
    return {b, a};
}

int main() {
    auto [low, high] = minMax(9, 4);
    std::cout << low << ' ' << high << '\n';

    std::map<std::string, std::string> capitals = {
        {"Japan", "Tokyo"}, {"Azerbaijan", "Baku"}, {"Georgia", "Tbilisi"}};
    for (const auto& [country, capital] : capitals) {
        std::cout << country << " -> " << capital << '\n';
    }
    return 0;
}
Expected output
4 9
Azerbaijan -> Baku
Georgia -> Tbilisi
Japan -> Tokyo

Lambda expressions

A lambda is a nameless function written right where it is needed: [capture](parameters) { body }. The square brackets say which surrounding variables the lambda “captures”: [bonus] takes a copy, while [&total] works through a reference and can change the original.

C++
#include <iostream>

int main() {
    auto square = [](int x) { return x * x; };

    int bonus = 10;
    auto addBonus = [bonus](int score) { return score + bonus; };

    int total = 0;
    auto addToTotal = [&total](int x) { total += x; };

    std::cout << square(6) << '\n';
    std::cout << addBonus(75) << '\n';
    addToTotal(5);
    addToTotal(7);
    std::cout << "Total: " << total << '\n';
    return 0;
}
Expected output
36
85
Total: 12

The real power of lambdas shows up together with STL algorithms. Below we sort the results by score in descending order, and then use std::count_if to count how many students reached the pass mark:

C++
#include <algorithm>
#include <iostream>
#include <string>
#include <utility>
#include <vector>

int main() {
    std::vector<std::pair<std::string, int>> results = {
        {"Aysel", 92}, {"Murad", 85}, {"Leyla", 97}, {"Elvin", 78}};

    std::sort(results.begin(), results.end(),
              [](const auto& a, const auto& b) { return a.second > b.second; });

    for (const auto& [name, score] : results) {
        std::cout << name << ": " << score << '\n';
    }

    int passMark = 80;
    auto passed = std::count_if(results.begin(), results.end(),
                                [passMark](const auto& r) { return r.second >= passMark; });
    std::cout << "Passed: " << passed << '\n';
    return 0;
}
Expected output
Leyla: 97
Aysel: 92
Murad: 85
Elvin: 78
Passed: 3
CaptureMeaning
[]captures nothing
[x]a copy of x
[&x]x by reference
[=]copies of all variables it uses
[&]all variables it uses, by reference

Key points

  • C++ is updated regularly: C++11, C++14, C++17 and C++20 made the language shorter and safer.
  • const means the value will not change; constexpr requires it to be computed at compile time.
  • auto [a, b] = ...; unpacks a pair or a map entry into separate variables.
  • A lambda is [capture](parameters) { body }; [x] takes a copy and [&x] a reference.
  • Lambdas are the shortest way to give your own rule to algorithms such as std::sort and std::count_if.

Check yourself

10 questions. Every correct answer earns XP.

1 / 10
After auto [a, b] = std::pair<int, int>{3, 8};, what is b?