Сучасний C++ (17/20/23): auto, structured bindings, optional, format
C++ суттєво змінився між 2011 і 2023. Сучасний C++20/23 — це майже інша мова порівняно з C++03. Цей курс — про те, як C++ пишуть зараз, у production-проєктах 2026.
Що приніс сучасний C++
| Стандарт | Рік | Ключові фічі |
|---|---|---|
| C++11 | 2011 | auto, lambdas, range-for, smart pointers, move-семантика, nullptr, constexpr |
| C++14 | 2014 | generic lambdas, auto return, make_unique, binary literals |
| C++17 | 2017 | structured bindings, std::optional, std::variant, std::filesystem, if constexpr |
| C++20 | 2020 | concepts, ranges, coroutines, modules, std::format, std::span |
| C++23 | 2023 | std::print, std::expected, std::mdspan, deducing this |
У 2026 актуальний компілятор підтримує C++23. Пишемо C++20/23, не C++03/11.
auto — type deduction
auto i = 42; // int
auto d = 3.14; // double
auto s = "hello"; // const char*
auto v = std::vector<int>{1,2,3}; // std::vector<int>
auto x = std::string("text"); // std::string
const auto& ref = some_vector; // const reference — без копії
Використовуй auto усюди, де тип очевидний з ініціалізатора або занадто довгий (std::map<std::string, std::vector<std::string>>::iterator → auto).
Structured bindings (C++17)
Розпакувати tuple/pair/struct:
#include <map>
std::map<std::string, int> ages = {{"Olena", 25}, {"Ivan", 30}};
for (const auto& [name, age] : ages) {
std::cout << name << ": " << age << "\n";
}
// Своя структура
struct Point { int x, y; };
Point p{3, 4};
auto [x, y] = p;
Аналог Python for name, age in items.items().
range-based for + init-statement (C++20)
// Простий
for (auto x : container) { ... }
// З init-statement (C++20):
for (auto v = compute(); auto& x : v) {
// ...
}
Можна оголосити змінну в самому циклі — менший scope.
if/switch з init-statement (C++17)
if (auto it = map.find(key); it != map.end()) {
// використовуємо it лише тут
use(it->second);
}
// it тут уже нема
switch (auto err = compute(); err.code) {
case 0: ...
case 1: ...
}
Lambda — modern
auto square = [](int n) { return n * n; };
// З захопленням
int multiplier = 5;
auto multiply = [multiplier](int n) { return n * multiplier; };
// За посиланням
auto counter = [&multiplier]() { ++multiplier; };
// Generic lambda (C++14+)
auto add = [](auto a, auto b) { return a + b; };
add(2, 3); // int
add(2.5, 3.5); // double
// Template lambda (C++20)
auto print_all = []<typename T>(const std::vector<T>& v) {
for (const auto& x : v) std::cout << x << " ";
};
Initializer lists
std::vector<int> v = {1, 2, 3, 4};
std::map<std::string, int> m = {{"a", 1}, {"b", 2}};
auto pair = std::pair{1, "text"}; // CTAD (C++17 deduction)
std::optional (C++17)
Замість T* для «може бути значення»:
#include <optional>
std::optional<User> findUser(int id) {
if (id < 0) return std::nullopt;
return User{id, "Olena"};
}
auto user = findUser(42);
if (user) {
std::cout << user->name;
}
// Або через value_or:
std::string name = user.has_value() ? user->name : "guest";
Безпечніше за nullptr/-1 для індикації «нема значення».
std::variant — type-safe union
#include <variant>
std::variant<int, std::string, double> v;
v = 42;
v = "text";
v = 3.14;
// Перевірити тип
if (std::holds_alternative<int>(v)) {
int n = std::get<int>(v);
}
// std::visit — pattern matching
std::visit([](const auto& x) {
std::cout << x << "\n";
}, v);
Замінює union-и з C, але типобезпечно.
std::format / std::print (C++20/23)
Сучасне форматування рядків. Замість printf чи cout <<:
#include <format>
#include <print> // C++23
std::string s = std::format("Name: {}, Age: {}", name, age);
std::print("Sum: {}, Product: {}\n", a + b, a * b);
Python-like синтаксис, типобезпечно (на відміну від printf).
⚠ std::print потребує GCC 14+/Clang 18+. Якщо нема — використовуй fmt::print з бібліотеки {fmt} (саме її скопіювали в стандарт).
std::span (C++20) — view на масив
#include <span>
void print(std::span<int> data) {
for (int x : data) std::cout << x << " ";
}
std::vector<int> v = {1, 2, 3, 4};
int arr[] = {5, 6, 7};
print(v); // ✓
print(arr); // ✓
print({v.data(), 2}); // ✓ перші 2 елементи
span — не власник даних, просто «вид». Швидкий, без копіювання, на будь-яку колекцію.
constexpr — обчислення в compile-time
constexpr int factorial(int n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
constexpr int x = factorial(10); // 3628800, обчислено компілятором
constexpr if (C++17):
template<typename T>
auto get_value(T t) {
if constexpr (std::is_pointer_v<T>) {
return *t;
} else {
return t;
}
}
if constexpr вибирає гілку в compile-time — не-обрана навіть не компілюється.
Інструменти
- CMake — стандарт збірки (не
g++ file.cpp -o outруками) - Compiler Explorer (godbolt.org) — як твій код компілюється
- clang-format — автоформатування
- clang-tidy — лінтер
- ASAN/UBSAN/TSAN — sanitizers (детальніше в m11)
- vcpkg / Conan — менеджери пакетів
Тест: перевір себе
- У чому суть
if constexpr? - Що дає
structured bindings? - Як використати
std::optionalдля функції, яка «може повернути значення»? - Що таке
std::spanі чим відрізняється відstd::vector? - Чому
std::formatкраще заprintf?
Відповіді
- Вибір гілки коду в compile-time. Не-обрана гілка навіть не компілюється — це дозволяє використовувати фічі, що не існують для деяких типів. Замінює SFINAE-трюки.
- Розпакування tuple/pair/struct у окремі змінні. Замість
auto pair = func(); auto first = pair.first; auto second = pair.second;—auto [first, second] = func();. Особливо зручно вforпоmap. std::optional<User> find(int id) { if (notFound) return std::nullopt; return User{...}; } if (auto u = find(1)) { use(u->name); }span— лише вид (view) на існуючу пам'ять, не власник. Дешевий (без копіювання), не керує lifecycle.vector— власник, керує пам'яттю, можнаpush_back.- Типобезпечно (на відміну від
printf, де%dдля float = UB). Простий синтаксис{}як Python f-strings. Розширюваний — можна додавати власні форматтери для своїх типів.