Как работает std :: tie?

120

Я использовал, std::tieне особо задумываясь об этом. Это работает, поэтому я только что принял это:

auto test()
{
   int a, b;
   std::tie(a, b) = std::make_tuple(2, 3);
   // a is now 2, b is now 3
   return a + b; // 5
}

Но как работает эта черная магия ? Как временное, созданное std::tieизменением aи b? Мне это кажется более интересным, так как это функция библиотеки, а не языка, так что, конечно же, мы можем реализовать ее и понять.

Болов
источник

Ответы:

152

Чтобы прояснить основную концепцию, давайте сведем ее к более простому примеру. Хотя std::tieэто полезно для функций, возвращающих (кортеж из) большего количества значений, мы можем легко понять это с помощью всего одного значения:

int a;
std::tie(a) = std::make_tuple(24);
return a; // 24

Что нам нужно знать, чтобы двигаться вперед:

  • std::tie создает и возвращает кортеж ссылок.
  • std::tuple<int>и std::tuple<int&>представляют собой 2 совершенно разных класса, между которыми нет связи, кроме того, что они были созданы из одного и того же шаблона std::tuple.
  • У кортежа есть operator=принимающий кортеж разных типов (но с одинаковым числом), где каждый член назначается индивидуально - из cppreference :

    template< class... UTypes >
    tuple& operator=( const tuple<UTypes...>& other );

    (3) Для всех я, правопреемников std::get<i>(other)к std::get<i>(*this).

Следующий шаг - избавиться от тех функций, которые только мешают вам, поэтому мы можем преобразовать наш код в следующий:

int a;
std::tuple<int&>{a} = std::tuple<int>{24};
return a; // 24

Следующий шаг - посмотреть, что именно происходит внутри этих структур. Для этого я создаю два типа Tзаместителя std::tuple<int>и Trзаместителя std::tuple<int&>, урезанные до минимума для наших операций:

struct T { // substituent for std::tuple<int>
    int x;
};

struct Tr { // substituent for std::tuple<int&>
    int& xr;

    auto operator=(const T& other)
    {
       // std::get<I>(*this) = std::get<I>(other);
       xr = other.x;
    }
};

auto foo()
{
    int a;
    Tr{a} = T{24};

    return a; // 24
}

И, наконец, мне нравится избавляться от структур целиком (ну, это не эквивалент на 100%, но он достаточно близок для нас и достаточно ясен, чтобы позволить это):

auto foo()
{
    int a;

    { // block substituent for temporary variables

    // Tr{a}
    int& tr_xr = a;

    // T{24}
    int t_x = 24;

    // = (asignement)
    tr_xr = t_x;
    }

    return a; // 24
}

По сути, std::tie(a)инициализирует ссылку на член данных a. std::tuple<int>(24)создает член данных со значением 24, и присваивание присваивает 24 ссылке элемента данных в первой структуре. Но поскольку этот элемент данных является ссылка обязана a, что в основном правопреемники 24к a.

Болов
источник
1
Что меня беспокоит, так это то, что мы вызываем оператор присваивания для rvalue.
Адам Захран
В этом ответе говорится, что контейнер не может содержать ссылку. Зачем tupleдержать ссылку?
nn0p
6
@ nn0p std::tupleне является контейнером, по крайней мере, в терминологии C ++, не то же самое, что std::vectorи подобные. Например, вы не можете перебирать кортеж обычными способами, потому что он содержит разные типы объектов.
bolov
@Adam tie (x, y) = make_pair (1,2); фактически становится std :: tie (x, y) .operator = (std :: make_pair (1, 2)), поэтому «присвоение rvalue» работает XD
Ju Piece
30

Это никоим образом не отвечает на ваш вопрос, но позвольте мне опубликовать его в любом случае, потому что C ++ 17 в основном готов (с поддержкой компилятора), поэтому, задаваясь вопросом, как работает устаревший материал, вероятно, стоит посмотреть, как текущий и в будущем версия C ++ тоже работает.

В C ++ 17 можно в значительной степени отказаться std::tieот так называемых структурированных привязок . Они делают то же самое (ну, не то же самое , но имеют тот же общий эффект), хотя вам нужно ввести меньше символов, для этого не нужна поддержка библиотеки, и у вас также есть возможность принимать ссылки, если это произойдет то, что ты хочешь.

(Обратите внимание, что в C ++ 17 конструкторы выполняют вывод аргументов, поэтому они make_tupleтоже стали в некоторой степени излишними.)

int a, b;
std::tie(a, b) = std::make_tuple(2, 3);

// C++17
auto  [c, d] = std::make_tuple(4, 5);
auto  [e, f] = std::tuple(6, 7);
std::tuple t(8,9); auto& [g, h] = t; // not possible with std::tie
Damon
источник
2
Если эта последняя строка компилируется, я немного обеспокоен. Похоже, привязка ссылки к временному объекту является незаконной.
Нир Фридман
3
@Neil Это должна быть ссылка на rvalue или ссылка на const lvalue. Вы не можете привязать ссылку lvalue к prvalue (временно). Хотя это было "расширением" в MSVC на протяжении веков.
Нир Фридман
1
Вероятно, также стоит упомянуть, что в отличие от tieструктурированных привязок можно использовать этот способ для типов, которые не могут быть сконструированы по умолчанию.
Дэн
5
Да, std::tie()он намного менее полезен по сравнению с C ++ 17, где структурированные привязки обычно лучше, но он все еще имеет применение, включая присвоение существующим (не одновременно вновь объявленным) переменным и краткое выполнение других вещей, таких как замена нескольких переменных или другие вещи, которые необходимо присвоить ссылки.
underscore_d