Подтвердить что ты не робот

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

Я использовал 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? Я нахожу это более интересным, поскольку это функция библиотеки, а не функция языка, поэтому, безусловно, это то, что мы можем реализовать и понять.

4b9b3361

Ответ 1

Чтобы прояснить основную концепцию, позвольте ее свести к более базовому примеру. Хотя 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.
  • 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

Следующий шаг - посмотреть, что именно происходит внутри этих структур. Для этого я создаю 2 типа 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.

Ответ 2

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

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

(Обратите внимание, что в конструкторах С++ 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