Вспомогательные типы данных: pair, tuple, struct

Оглавление

Регулярно бывает такое, что вы хотите вернуть из функции несколько переменных, а не одну. Разберем эту проблему на примере функции divmod, которая возвращает результат деления и остаток от деления двух чисел:

1
2
3
<output_type> divmod(int a, int b) {
    return { a / b, a % b };
}

Разберем, каким же может быть <output_type>: мы рассмотрим каждый из трех возможных вариантов, разобрав их преимущества и недостатки каждого из них.

Struct

Первый вариант - создать новую структуру, которая будет содержать все необходимые переменные.

1
2
3
4
5
6
7
8
9
10
11
12
13
struct Divmod {
    int div;
    int mod;
};

Divmod divmod(int a, int b) {
    return {a / b, a % b};
}

int main() {
    Divmod result = divmod(10, 3);
    cout << result.div << " " << result.mod << "\n";
}

Данный подход универсален, однако создавать каждый раз новую структуру может быть неудобно, зато очень понятно с точки зрения использования результатов. В промышленном программировании обычно используют именно этот вариант из-за самой лучшей читабельности.

Pair

Второй вариант - использовать pair.

1
2
3
4
5
6
7
8
pair<int, int> divmod(int a, int b) {
    return {a / b, a % b};
}

int main() {
    pair<int, int> result = divmod(10, 3);
    cout << result.first << " " << result.second << "\n";
}

pair - это структура, которая хранит два элемента разных типов. В данном случае это два целых числа. Важным приемуществом pair над struct является то, что он уже встроен в стандартную библиотеку C++ и у него определен operator< - это позволяет сравнивать любые две пары в лексикографическом порядке, а так же позволяет отсортировать массив из пар без дополнительного кода, в отличии от struct.

Tuple

Что если вам нужно вернуть больше двух переменных из функции? Тогда вы можете либо воспользьзоваться каким-то из уже известными вам вриантов: struct или вложенными pair, например:

1
2
3
4
5
6
7
8
pair<int, pair<int, int>> divmodsum(int a, int b) {
    return {a / b, {a % b, a + b}};
}

int main() {
    pair<int, pair<int, int>> result = divmodsum(10, 3);
    cout << result.first << " " << result.second.first << " " << result.second.second << "\n";
}

Но вопрос удобства использования вложенных пар смешён. Поэтому в С++ есть расширение пары - tuple, в котором вы можете хранить сколько угодно переменных любого типа:

1
2
3
4
5
6
7
8
tuple<int, int, int> divmodsum(int a, int b) {
    return {a / b, a % b, a + b};
}

int main() {
    tuple<int, int, int> result = divmodsum(10, 3);
    cout << get<0>(result) << " " << get<1>(result) << " " << get<2>(result) << "\n";
}

У tuple, так же как и у pair определен operator<, что позволяет сравнивать два tuple в лексикографическом порядке.

Structured bindings

К сожалению, обращение к элементам tuple происходит по индексу через метод get, что не всегда удобно. Однко начиная с C++17 можно использовать structured bindings:

1
2
auto [div, mod, sum] = divmodsum(10, 3);
cout << div << " " << mod << " " << sum << "\n";

Structured bindings будут работать с любым из перечисленных типов данных в данном разделе: pair, tuple, struct. Рекомендую использовать их, так как они делают код более читаемым и понятным.