Skip to content

Latest commit

 

History

History
117 lines (90 loc) · 5.84 KB

tuple_creation.md

File metadata and controls

117 lines (90 loc) · 5.84 KB

Кортежи, стреляющие по ногам

С C++11 в стандартной библиотеке есть замечательный шаблон класса std::tuple. Кортеж. Гетерогенный список. Отличная и полезная штука. Вот только создать кортеж, ничего не сломав и при этом получив именно то, что вы хотели — задача совершенно не тривиальная.

Явно указывать типы элементов очень длинного контейнера — занятие не из приятных.

С++11 дал нам целых три способа сэкономить на указании типов — разные функции создания кортежей:

  • make_tuple
  • tie
  • forward_as_tuple

С++17 дает еще и возможность использовать автовыведение типов и просто писать

   auto t = tuple { 1, "string", 1.f };

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

std::make_tuple отбрасывает ссылки, приводит ссылки на массивы к указателям, отбрасывает const. В общем, применяет std::decay_t.

При этом есть особенный частный случай, сделанный, как обычно, из благих побуждений.

Если типом аргумента make_tuple является std::reference_wrapper<T>, то в кортеже он превращается в T&.

    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 не рассматривает. Но decay происходит.

    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 всегда конструирует кортеж ссылок. И соответственно можно получить ссылку на мертвый временный объект.

     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 ссылок. И подорваться на нем сложнее, но все равно можно, если вы захотите полученный кортеж возвращать из функции. Но эта ситуация совершенно аналогична случаям возврата любых ссылок из функций.

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 использовать только чтобы временно представить набор переменных в виде кортежа:
        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 для выполнения обмена значений переменных.

    x, y = y, x
   int x = 5;
   int y = 3;
   std::tie(x, y) = std::tie(y, x);
   std::cout << x <<  " " << y; 

У нас тут не Python, поэтому поведение этого кода неопределено. Но не печальтесь. Всего лишь unspecified. В результате вы получите либо 5 5, либо 3 3.