Какие новые возможности появились в C++17 и как их использовать в реальных проектах?
auto [x, y, z] = getPoint();
Или такое:
if (auto it = map.find(key); it != map.end()) { ... }
Или вот ещё:
std::variant<int, float, std::string> v = 42;
Мой вопрос: какие именно нововведения принёс C++17? Я слышал про structured bindings, if-init, std::variant, std::optional, но как всё это работает на практике? Есть ли что-то ещё, что стоит знать?
Меня особенно интересуют:
1. Какие фичи реально упрощают повседневную разработку?
2. Что изменилось в стандартной библиотеке?
3. Есть ли какие-то подводные камни при переходе с C++14 на C++17?
Буду благодарен за подробный разбор с примерами кода. Хочу понять, какие фичи стоит внедрять в свои проекты, а от каких пока воздержаться.
Спасибо!
<<Основные нововведения C++17 с примерами
C++17 — это эволюционный шаг вперёд, который добавил множество удобных возможностей. Разберём их по категориям.
1. Структурные привязки (Structured Bindings)
Позволяют распаковывать кортежи, пары и структуры в отдельные переменные одной строкой:
// До C++17
std::pair<int, double> p = getValues();
int a = p.first;
double b = p.second;
// C++17
auto [a, b] = getValues(); // a - int, b - double
// Работает и с массивами
int arr[3] = {1, 2, 3};
auto [x, y, z] = arr; // x=1, y=2, z=3
Важно: структурные привязки создают копии элементов. Для ссылок используйте auto&.
2. Инициализация в условии (if/switch with initializer)
Позволяет объявлять переменную прямо в условии, ограничивая её область видимости:
// Вместо:
{
auto it = myMap.find(key);
if (it != myMap.end()) {
process(it->second);
}
}
// Можно так:
if (auto it = myMap.find(key); it != myMap.end()) {
process(it->second);
} // it выходит из области видимости здесь
// Аналогично для switch:
switch (char c = getChar(); c) {
case 'a': handleA(); break;
case 'b': handleB(); break;
}
3. std::variant — типобезопасная "сумма типов"
Замена объединениям и сырым union:
#include <variant>
#include <iostream>
std::variant<int, float, std::string> v;
v = 42; // хранит int
v = 3.14f; // хранит float
v = "hello"s; // хранит string
// Посещение с помощью std::visit
std::visit([](auto&& arg) {
std::cout << arg << '\n';
}, v);
// Проверка типа
if (std::holds_alternative<int>(v)) {
std::cout << "int: " << std::get<int>(v);
}
4. std::optional — значение может отсутствовать
Безопасная замена "магическим" значениям (например, -1 или nullptr):
std::optional<int> findValue(const std::vector<int>& v, int target) {
auto it = std::find(v.begin(), v.end(), target);
if (it != v.end()) return *it;
return std::nullopt;
}
// Использование
auto result = findValue(data, 42);
if (result) {
std::cout << "Found: " << *result;
} else {
std::cout << "Not found";
}
// Или с value_or
int val = result.value_or(-1);
5. std::string_view — лёгкое представление строк
Не владеющий памятью просмотр строки — идеально для параметров функций:
void processName(std::string_view name) {
// name не копирует строку
for (char c : name) { ... }
}
// Можно передавать std::string, const char*, литералы
processName("John"); // без создания std::string
std::string s = "Doe";
processName(s); // тоже без копирования
6. Вывод шаблонных аргументов для классов (CTAD)
Компилятор может вывести типы для шаблонов классов:
// До C++17
std::pair<int, double> p1(1, 2.5);
std::vector<int> v1 = {1, 2, 3};
// C++17
std::pair p2(1, 2.5); // auto
std::vector v2 = {1, 2, 3}; // auto
std::mutex mtx;
std::lock_guard lck(mtx); // auto
7. Новые алгоритмы и параллельные версии
C++17 добавил параллельные версии алгоритмов:
#include <execution>
#include <algorithm>
std::vector<int> data = ...;
// Последовательная версия
std::sort(data.begin(), data.end());
// Параллельная версия (требует поддержки)
std::sort(std::execution::par, data.begin(), data.end());
// Новые алгоритмы: gcd, lcm, clamp
int g = std::gcd(12, 18); // 6
int c = std::clamp(x, 0, 100); // ограничить x в [0,100]
8. Вложенные пространства имён
// Вместо:
namespace A { namespace B { namespace C { ... } } }
// Можно:
namespace A::B::C { ... }
Подводные камни и рекомендации
- CTAD не работает с агрегатной инициализацией:
std::pair p{1, 2.5};— ошибка, нужноstd::pair p(1, 2.5); - std::string_view не гарантирует нуль-терминацию — будьте осторожны при передаче в C-функции.
- std::variant может бросать исключение
std::bad_variant_accessпри неправильном доступе. - Параллельные алгоритмы требуют поддержки от компилятора и библиотеки (TBB, Intel Parallel STL).
В целом, C++17 — очень удачный стандарт. Начните с std::optional, std::string_view и структурных привязок — они сразу упростят код.
Дополнительные фичи и практические советы
Хочу добавить несколько менее известных, но полезных возможностей C++17, которые часто упускают в обзорах.
1. constexpr if (if constexpr)
Позволяет выполнять условную компиляцию внутри шаблонов:
template<typename T>
auto getValue(T t) {
if constexpr (std::is_integral_v<T>) {
return t + 1; // для int
} else {
return t; // для float/string
}
}
Это заменяет SFINAE и специализации во многих случаях.
2. Fold expressions (свёртки) для variadic templates
template<typename... Args>
auto sum(Args... args) {
return (... + args); // бинарная левая свёртка
}
// sum(1, 2, 3) → ((1 + 2) + 3) = 6
3. Упрощённое лямбда-выражение (constexpr лямбды)
Лямбды теперь могут быть constexpr и захватывать *this по значению:
auto lambda = [](auto x) constexpr { return x * 2; };
constexpr int result = lambda(5); // работает в compile-time
4. std::filesystem
Кроссплатформенная работа с файловой системой:
#include <filesystem>
namespace fs = std::filesystem;
fs::path p = "dir/file.txt";
if (fs::exists(p)) {
auto size = fs::file_size(p);
auto time = fs::last_write_time(p);
}
Совет по миграции
Начните с включения флага -std=c++17