diff --git a/README.md b/README.md index 23febe4..9373b0b 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/lifetime/tuple_creation.md b/lifetime/tuple_creation.md new file mode 100644 index 0000000..9b70d42 --- /dev/null +++ b/lifetime/tuple_creation.md @@ -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`, то в кортеже он [превращается](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>); +``` + +Конструктор с автовыводом типов особый случай `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, + std::reference_wrapper, + 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>); // Да, это rvalue ссылка на массив + + std::get<1>(t); // UB! +``` + +`std::tie` конструирует кортеж только из `lvalue` ссылок. И подорваться на нем сложнее, но все равно [можно](https://godbolt.org/z/WPP7qca6a), если вы захотите полученный кортеж возвращать из функции. Но эта ситуация совершенно аналогична случаям возврата любых ссылок из функций. + +```C++ +template +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>); + 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`. \ No newline at end of file