В кортежи на C++ объединяются различные типы данных, при этом формального класса или структуры не создается. Это как быстрые контейнеры, в которые упаковываются связанные значения. Изучим их эффективное использование в коде.
Создание и использование базовых кортежей
Проще всего кортеж создается из std::make_tuple:
auto person = std::make_tuple("John", 30, 1.75);
// Типы: string, int, double
Эти типы также объявляются явно:
std::tuple<std::string, int, double> person{"John", 30, 1.75};
Доступ к элементам кортежа
Значения кортежа получаются несколькими способами:
auto person = std::make_tuple("John", 30, 1.75);
// Используя «std::get» с индексом
std::string name = std::get<0>(person);
int age = std::get<1>(person);
// Используя «std::get» с типом, который не повторяется
std::string name2 = std::get<std::string>(person);
double height = std::get<double>(person);
// Используя структурированную привязку версии C++17
auto [name3, age2, height2] = person;
Работа с кортежами на практике
Возвращение многочисленных значений из функций
Кортежем заменяются выходные параметры или создаваемая специальная структура:
std::tuple<bool, std::string, int> parseUserData(const std::string& input) {
if (input.empty()) {
return {false, "Empty input", 0};
}
// Данные обрабатываются...
return {true, "John", 30};
}
void processUser() {
auto [success, name, age] = parseUserData("some input");
if (!success) {
// Обрабатывается ошибка
return;
}
// Используются «name» и «age»
}
Поэлементное построение кортежей
std::tuple<std::string, int, double> buildProfile() {
std::tuple<std::string, int, double> profile;
// Элементы задаются отдельно
std::get<0>(profile) = "Jane";
std::get<1>(profile) = 25;
std::get<2>(profile) = 1.68;
return profile;
}
Реальные применения
1. Обработка записей базы данных
class Database {
public:
using Record = std::tuple<int, std::string, std::string, std::chrono::system_clock::time_point>;
Record getUser(int id) {
// Смоделированный запрос к базе данных
return std::make_tuple(
id,
"john_doe",
"john@example.com",
std::chrono::system_clock::now()
);
}
void processUser(int id) {
auto [userId, username, email, lastLogin] = getUser(id);
// Форматируется время последней регистрации
auto time = std::chrono::system_clock::to_time_t(lastLogin);
std::cout << "User " << username << " (ID: " << userId << ")\n"
<< "Email: " << email << "\n"
<< "Last login: " << std::ctime(&time);
}
};
2. Операции с графами
class Graph {
public:
// Возвращается {distance, path}
std::tuple<double, std::vector<int>> shortestPath(int start, int end) {
std::vector<int> path = {start, /* ... */, end};
double distance = 42.0;
return {distance, path};
}
void navigate(int start, int end) {
auto [distance, path] = shortestPath(start, end);
std::cout << "Distance: " << distance << "\n";
std::cout << "Path: ";
for (int node : path) {
std::cout << node << " ";
}
std::cout << "\n";
}
};
3. Кэш с истечением срока
template<typename T>
class Cache {
private:
using CacheEntry = std::tuple<T, std::chrono::system_clock::time_point, int>;
std::unordered_map<std::string, CacheEntry> data;
public:
void insert(const std::string& key, const T& value, int ttlSeconds) {
auto expiry = std::chrono::system_clock::now() +
std::chrono::seconds(ttlSeconds);
data[key] = std::make_tuple(value, expiry, ttlSeconds);
}
bool get(const std::string& key, T& value) {
auto it = data.find(key);
if (it == data.end()) {
return false;
}
auto& [storedValue, expiry, ttl] = it->second;
if (std::chrono::system_clock::now() > expiry) {
data.erase(it);
return false;
}
value = storedValue;
return true;
}
};
4. Система событий
class EventSystem {
public:
using EventData = std::tuple<std::string, // Тип события
std::any, // Данные о событии
std::chrono::system_clock::time_point>; // Временнáя метка
void dispatch(const std::string& type, const std::any& data) {
auto event = std::make_tuple(
type,
data,
std::chrono::system_clock::now()
);
processEvent(event);
}
private:
void processEvent(const EventData& event) {
auto [type, data, timestamp] = event;
if (type == "UserLogin") {
auto username = std::any_cast<std::string>(data);
// Регистрация обрабатывается
}
}
};
Расширенные операции над кортежами
Конкатенация кортежей
auto personalInfo = std::make_tuple("John", 30);
auto contactInfo = std::make_tuple("john@example.com", "123-456-7890");
// Кортежи объединяются при помощи «std::tuple_cat»
auto fullProfile = std::tuple_cat(personalInfo, contactInfo);
// Результат: tuple<string, int, string, string>
Работа с размером кортежа
auto profile = std::make_tuple("John", 30, 1.75);
// Получается количество элементов
constexpr size_t size = std::tuple_size<decltype(profile)>::value;
static_assert(size == 3);
// Получается тип элемента
using FirstType = std::tuple_element<0, decltype(profile)>::type;
static_assert(std::is_same_v<FirstType, std::string>);
Пользовательские типы в кортежах
class User {
std::string name;
public:
User(std::string n) : name(std::move(n)) {}
const std::string& getName() const { return name; }
};
class Role {
int level;
public:
Role(int l) : level(l) {}
int getLevel() const { return level; }
};
// Применение пользовательских типов в кортеже
auto userInfo = std::make_tuple(
User("John"),
Role(5),
std::vector<std::string>{"admin", "user"}
);
auto [user, role, permissions] = userInfo;
std::cout << user.getName() << " has role level " << role.getLevel() << "\n";
Типичные проблемы и их решения
1. Типобезопасность с get<>
auto data = std::make_tuple(1, "hello");
// Ошибка компиляции: неверный тип
// Значение целочисленного типа = std::get<1>(data); // Ошибка: индекс «1» — строка
// Корректное применение
int value = std::get<0>(data);
std::string text = std::get<1>(data);
2. Ссылочные члены
std::string name = "John";
auto tuple1 = std::make_tuple(name); // Делается копия
auto tuple2 = std::make_tuple(std::ref(name)); // Сохраняется ссылка
name = "Jane";
// В «tuple1» по-прежнему содержится «"John"»
// «tuple2» теперь ссылается на «"Jane"»
3. Сравнение кортежей
auto profile1 = std::make_tuple("John", 30);
auto profile2 = std::make_tuple("Jane", 25);
// Кортежи сравниваются лексикографически
bool isLess = profile1 < profile2; // Сначала сравнивается строка
Кортежи — это гибкие инструменты для написания более чистого и сопровождаемого кода. Особенно кстати они приходятся при временном объединении данных или возвращении многочисленных значений из функций. С кортежами код выразительнее, в нем меньше временных структур.
Читайте также:
- C++: полное руководство по бинарной сортировке
- C++: полное руководство по unique_ptr
- Эволюция кортежей в C#
Читайте нас в Telegram, VK и Дзен
Перевод статьи ryan: Tuples in C++: Complete Guide




