Что принес 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 вы получаете паттерн-матчинг, почти как в функциональных языках.