C++17: что нового? Полный гайд с примерами для профи

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Что принес C++17? Разбор главных новинок стандарта

Если вы пишете на C++ и хотите оставаться в мейнстриме, то C++17 — это та версия, которую стоит освоить прямо сейчас. Вышел он еще в 2017 году, но до сих пор многие проекты «застревают» на C++14 или даже C++11, боясь переходить на новый стандарт. И зря.

В этой статье мы разберем новые возможности C++17 на реальных примерах. Вы узнаете, как упростить код, избавиться от лишних проверок и сделать программы быстрее. Мы не будем перечислять все 105 изменений — только то, что реально пригодится в работе.

1. Структурные привязки (Structured Bindings) — декомпозиция за секунду

Одна из самых удобных фич C++17 — это структурные привязки. Раньше, чтобы разобрать кортеж или пару, приходилось писать std::tie или обращаться по индексам. Теперь всё гораздо элегантнее.

Пример: возврат нескольких значений из функции

#include <iostream>
#include <tuple>
#include <string>

std::tuple<int, std::string, double> getData() {
    return {42, "Ответ", 3.14};
}

int main() {
    // C++17: одна строка вместо std::tie
    auto [id, name, value] = getData();
    std::cout << name << ": " << id << " (" << value << ")\n";
    return 0;
}

Что изменилось? Раньше нужно было объявлять переменные отдельно, а потом вызывать std::tie. Теперь auto [a, b, c] делает всю работу за вас.

Структурные привязки работают не только с кортежами, но и с массивами, и с любыми структурами, у которых есть публичные поля. Это одно из нововведений C++17, которое делает код чище и читаемее.

2. std::optional — избавляемся от «магических» значений

Сколько раз вы видели код, где функция возвращает -1 или nullptr для обозначения ошибки? Или хуже — bool-флаг и результат по ссылке? std::optional<T> решает эту проблему раз и навсегда.

Пример: безопасный поиск в контейнере

#include <iostream>
#include <optional>
#include <map>
#include <string>

std::optional<int> findValue(const std::map<std::string, int>& m, const std::string& key) {
    auto it = m.find(key);
    if (it != m.end()) {
        return it->second;  // есть значение
    }
    return std::nullopt;    // нет значения
}

int main() {
    std::map<std::string, int> ages = {{"Alice", 30}, {"Bob", 25}};
    
    auto result = findValue(ages, "Charlie");
    if (result) {
        std::cout << "Найдено: " << *result << "\n";
    } else {
        std::cout << "Ключ не найден\n";
    }
    
    // Или используем value_or для значения по умолчанию
    int age = result.value_or(0);
    return 0;
}

std::optional — это контейнер, который может содержать значение или быть пустым. Вы проверяете его через if (opt), а получаете значение через *opt или opt.value(). Это намного безопаснее, чем «договорные» коды ошибок.

3. if constexpr — шаблоны без головной боли

C++17 новые возможности для метапрограммирования — это просто подарок. if constexpr позволяет выполнять условную компиляцию внутри шаблонов без SFINAE-акробатики.

Пример: универсальная функция для разных типов

#include <iostream>
#include <type_traits>
#include <string>

template <typename T>
void printInfo(const T& value) {
    if constexpr (std::is_integral_v<T>) {
        std::cout << "Целое число: " << value << "\n";
    } else if constexpr (std::is_floating_point_v<T>) {
        std::cout << "Число с плавающей точкой: " << value << "\n";
    } else if constexpr (std::is_same_v<T, std::string>) {
        std::cout << "Строка: \"" << value << "\"\n";
    } else {
        std::cout << "Неизвестный тип\n";
    }
}

int main() {
    printInfo(42);          // Целое число
    printInfo(3.14);        // Число с плавающей точкой
    printInfo(std::string("Hello")); // Строка
    return 0;
}

До C++17 пришлось бы писать специализации шаблона или использовать std::enable_if. Теперь компилятор сам «выбрасывает» неподходящие ветки на этапе компиляции. Это одна из самых мощных новых возможностей C++17 для обобщённого программирования.

4. std::variant — безопасная замена union

Классические union в C++ — это бомба замедленного действия. Вы никогда не знаете, какое поле активно. std::variant (известный также как «типобезопасное объединение») полностью решает эту проблему.

Пример: хранение разных типов в одной переменной

#include <iostream>
#include <variant>
#include <string>

int main() {
    std::variant<int, double, std::string> data;
    
    data = 42;
    std::cout << "int: " << std::get<int>(data) << "\n";
    
    data = 3.14;
    std::cout << "double: " << std::get<double>(data) << "\n";
    
    data = std::string("Hello");
    std::cout << "string: " << std::get<std::string>(data) << "\n";
    
    // Безопасное посещение с помощью std::visit
    std::visit([](auto&& arg) {
        std::cout << "Посетили: " << arg << "\n";
    }, data);
    
    return 0;
}

std::variant — это ключевое нововведение C++17 для тех, кто пишет парсеры, конечные автоматы или просто хочет избежать неопределённого поведения. В сочетании с std::visit вы получаете паттерн-матчинг, почти как в функциональных языках.

5. Файловая система (std::files

Блоги

Рекомендаций по книгам