Перейти к содержанию
Educora
Продвинутый20 мин10 / 10

Современный C++: лямбды, структурные привязки и constexpr

Пиши короткий и надёжный код с возможностями стандартов C++11–C++20: лямбда-выражения, структурные привязки, `const` и `constexpr`.

Проверь себя
В этом уроке ты узнаешь
  • Описывать развитие стандартов C++ и их главные нововведения
  • Объяснять разницу между const и constexpr
  • Распаковывать пары и элементы словаря с помощью структурных привязок
  • Писать лямбда-выражения и использовать их с алгоритмами STL

C++ существует уже больше 40 лет, но не устарел: язык регулярно обновляется. Разница между кодом на C++ 1998 года и сегодняшним — как между кнопочным телефоном и смартфоном. В этом уроке ты изучишь возможности современного C++, которые делают код короче, понятнее и безопаснее.

Стандарты C++

  1. 1998
    C++98 — первый международный стандарт

    Язык утверждён как стандарт ISO, STL вошла в его состав.

  2. 2011
    C++11 — «новый язык»

    auto, for по диапазону, лямбды, nullptr, constexpr, std::unique_ptr.

  3. 2014
    C++14 — небольшие улучшения

    std::make_unique, лямбды с параметрами auto.

  4. 2017
    C++17

    Структурные привязки, std::optional, if constexpr.

  5. 2020
    C++20 — крупное обновление

    Концепты, диапазоны (ranges), модули, std::format.

const и constexpr

const означает, что переменная не изменится после первого значения, но само это значение может вычисляться и во время работы программы. constexpr строже: значение должно быть известно при компиляции. Если constexpr-функцию вызвать с константными аргументами, результат вычислит сам компилятор — во время работы программы время на это не тратится. А static_assert проверяет условие при компиляции.

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;
}
Ожидаемый результат
Volume: 27
Boxes: 8
Players: 4
cube(2) превратилось в 8 ещё при компиляции, поэтому его можно было использовать как размер массива.

Структурные привязки

Структурные привязки, появившиеся в C++17, в одну строку раскладывают пару, кортеж или простую структуру на отдельные переменные: auto [low, high] = ...;. Особенно удобно при переборе std::map: вместо entry.first и entry.second пишешь осмысленные имена.

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;
}
Ожидаемый результат
4 9
Azerbaijan -> Baku
Georgia -> Tbilisi
Japan -> Tokyo

Лямбда-выражения

Лямбда — безымянная функция, которую пишут прямо на месте: [захват](параметры) { тело }. В квадратных скобках указывают, какие внешние переменные лямбда «захватывает»: [bonus] берёт копию, а [&total] работает через ссылку и может менять оригинал.

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;
}
Ожидаемый результат
36
85
Total: 12

Настоящая сила лямбд раскрывается вместе с алгоритмами STL. Ниже мы сортируем результаты по убыванию баллов, а затем с помощью std::count_if считаем, сколько учеников набрали проходной балл:

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;
}
Ожидаемый результат
Leyla: 97
Aysel: 92
Murad: 85
Elvin: 78
Passed: 3
ЗахватЗначение
[]ничего не захватывает
[x]копию x
[&x]x по ссылке
[=]копии всех используемых переменных
[&]все используемые переменные по ссылке

Главное

  • C++ регулярно обновляется: C++11, C++14, C++17 и C++20 сделали язык короче и безопаснее.
  • const означает, что значение не изменится, а constexpr требует вычислить его при компиляции.
  • auto [a, b] = ...; раскладывает пару или элемент словаря на отдельные переменные.
  • Лямбда: [захват](параметры) { тело }; [x] берёт копию, [&x] — ссылку.
  • Лямбды — самый короткий способ передать своё правило алгоритмам вроде std::sort и std::count_if.

Проверь себя

Вопросов: 10. Каждый правильный ответ приносит XP.

1 / 10
Чему равно b после auto [a, b] = std::pair<int, int>{3, 8};?