mirror of
https://github.com/Nekrolm/ubbook.git
synced 2025-12-18 05:14:34 +03:00
tuple creation notes
This commit is contained in:
@@ -44,6 +44,7 @@
|
||||
5. [Cамоинициализация](lifetime/self_init.md)
|
||||
6. [std::vector и инвалидация ссылок](lifetime/vector_invalidation.md)
|
||||
7. [Висячие ссылки в лямбдах](lifetime/lambda_capture.md)
|
||||
8. [Создание кортежей](lifetime/tuple_creation.md)
|
||||
5. Неработающий синтаксис и стандартная библиотека
|
||||
1. [Most Vexing Parse](syntax/most_vexing_parse.md)
|
||||
2. [Const](syntax/const_launder.md)
|
||||
|
||||
117
lifetime/tuple_creation.md
Normal file
117
lifetime/tuple_creation.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# Кортежи, стреляющие по ногам
|
||||
|
||||
С C++11 в стандартной библиотеке есть замечательный шаблон класса `std::tuple`.
|
||||
Кортеж. Гетерогенный список. Отличная и полезная штука. Вот только создать кортеж, ничего не сломав
|
||||
и при этом получив именно то, что вы хотели — задача совершенно не тривиальная.
|
||||
|
||||
Явно указывать типы элементов очень длинного контейнера — занятие не из приятных.
|
||||
|
||||
С++11 дал нам целых три способа сэкономить на указании типов — разные функции создания кортежей:
|
||||
|
||||
- `make_tuple`
|
||||
- `tie`
|
||||
- `forward_as_tuple`
|
||||
|
||||
С++17 дает еще и возможность использовать автовыведение типов и просто писать
|
||||
|
||||
```C++
|
||||
auto t = tuple { 1, "string", 1.f };
|
||||
```
|
||||
|
||||
Все это великолепное разнообразие дает нам возможность тонко настраивать то,
|
||||
какие именно типы мы хотим получить в элементах контейнера — ссылочные или нет.
|
||||
А также возможность ошибиться и получить проблему с lifetime.
|
||||
|
||||
`std::make_tuple` отбрасывает ссылки, приводит ссылки на массивы к указателям, отбрасывает `const`. В общем,
|
||||
применяет `std::decay_t`.
|
||||
|
||||
При этом есть особенный частный случай, сделанный, как обычно, из благих побуждений.
|
||||
|
||||
Если типом аргумента `make_tuple` является `std::reference_wrapper<T>`, то в кортеже он [превращается](https://godbolt.org/z/bv17q5fEM)
|
||||
в `T&`.
|
||||
|
||||
```C++
|
||||
int x = 5;
|
||||
float y = 6;
|
||||
auto t = std::make_tuple(std::ref(x),
|
||||
std::cref(y),
|
||||
"hello");
|
||||
static_assert(std::is_same_v<decltype(t),
|
||||
std::tuple<int&,
|
||||
const float&,
|
||||
const char*>>);
|
||||
```
|
||||
|
||||
Конструктор с автовыводом типов особый случай `std::reference_wrapper` [не рассматривает](https://godbolt.org/z/cEd3e69bj).
|
||||
Но decay происходит.
|
||||
|
||||
```C++
|
||||
int x = 5;
|
||||
float y = 6;
|
||||
auto t = std::tuple(std::ref(x), std::cref(y), "hello");
|
||||
static_assert(std::is_same_v<decltype(t),
|
||||
std::tuple<std::reference_wrapper<int>,
|
||||
std::reference_wrapper<const float>,
|
||||
const char*>>);
|
||||
```
|
||||
|
||||
`std::forward_as_tuple` всегда конструирует кортеж ссылок. И соответственно можно получить [ссылку на мертвый временный объект](https://godbolt.org/z/8c8EjGq7c).
|
||||
|
||||
```C++
|
||||
int x = 5;
|
||||
auto t = std::forward_as_tuple(x, 6.f, std::move("hello"));
|
||||
static_assert(std::is_same_v<decltype(t),
|
||||
std::tuple<int&,
|
||||
float&&,
|
||||
const char (&&) [6]>>); // Да, это rvalue ссылка на массив
|
||||
|
||||
std::get<1>(t); // UB!
|
||||
```
|
||||
|
||||
`std::tie` конструирует кортеж только из `lvalue` ссылок. И подорваться на нем сложнее, но все равно [можно](https://godbolt.org/z/WPP7qca6a), если вы захотите полученный кортеж возвращать из функции. Но эта ситуация совершенно аналогична случаям возврата любых ссылок из функций.
|
||||
|
||||
```C++
|
||||
template <class... T>
|
||||
auto tie_consts(const T&... args) {
|
||||
return std::tie(args...);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
auto t = tie_consts(1, 1.f, "hello");
|
||||
static_assert(std::is_same_v<decltype(t),
|
||||
std::tuple<const int&,
|
||||
const float&,
|
||||
const char (&)[6]>>);
|
||||
std::cout << std::get<1>(t) << "\n"; // UB
|
||||
}
|
||||
```
|
||||
|
||||
Общие рекомендации
|
||||
|
||||
1. Для создания возвращаемых кортежей использовать `make_tuple` с явным указанием cref/ref
|
||||
либо конструктор, если ссылки не нужны.
|
||||
2. `std::tie` использовать только чтобы временно представить набор переменных в виде кортежа:
|
||||
```C++
|
||||
std::tie(it, inserted) = map.insert({x, y}); // распаковка кортежей
|
||||
std::tie(x1, y1, z1) == std::tie(x2, y2, z2); // покомпонентное сравнение
|
||||
```
|
||||
3. `std::forward_as_tuple` использовать только при передаче аргументов. Нигде не сохранять получаемый кортеж.
|
||||
|
||||
|
||||
И в конце бонус.
|
||||
|
||||
Особые любители Python могут захотеть попробовать использовать `std::tie` для выполнения обмена значений переменных.
|
||||
|
||||
```Python
|
||||
x, y = y, x
|
||||
```
|
||||
|
||||
```C++
|
||||
int x = 5;
|
||||
int y = 3;
|
||||
std::tie(x, y) = std::tie(y, x);
|
||||
std::cout << x << " " << y;
|
||||
```
|
||||
|
||||
У нас тут не Python, поэтому поведение этого кода неопределено. Но не печальтесь. Всего лишь `unspecified`.
|
||||
В результате вы получите либо `5 5`, либо `3 3`.
|
||||
Reference in New Issue
Block a user