web-dev-qa-db-fra.com

Comment fonctionne std :: tie?

J'ai utilisé std::tie sans trop y penser. Cela fonctionne donc je viens d'accepter que:

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
}

Mais comment fonctionne cette magie noire ? Comment un temporaire créé par std::tie change-t-il a et b? Je trouve cela plus intéressant car c'est une fonctionnalité de bibliothèque, pas une fonctionnalité de langage, alors c'est sûrement quelque chose que nous pouvons implémenter nous-mêmes et comprendre.

100
bolov

Afin de clarifier le concept de base, réduisons-le à un exemple plus simple. Bien que std::tie soit utile pour les fonctions renvoyant (un tuple de) plus de valeurs, nous pouvons le comprendre parfaitement avec une seule valeur:

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

Ce que nous devons savoir pour avancer:

  • std::tie construit et retourne un tuple de références.
  • std::Tuple<int> et std::Tuple<int&> sont deux classes complètement différentes, sans connexion entre elles, autrement qu'elles aient été générées à partir du même modèle, std::Tuple.
  • Tuple a un operator= acceptant un tuple de types différents (mais le même nombre), chaque membre étant affecté individuellement - de cppreference :

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

    (3) Pour tout i, attribue std::get<i>(other) à std::get<i>(*this).

La prochaine étape consiste à supprimer les fonctions qui ne vous gênent pas pour que nous puissions transformer notre code en ceci:

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

L'étape suivante consiste à voir exactement ce qui se passe à l'intérieur de ces structures. Pour cela, je crée 2 types _ substituant T pour std::Tuple<int> et Tr substituant std::Tuple<int&>, réduit au strict minimum pour nos opérations:

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
}

Et enfin, j'aime bien me débarrasser de toutes les structures (enfin, ce n'est pas équivalent à 100%, mais c'est assez proche pour nous et suffisamment explicite pour le permettre):

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
}

Donc, fondamentalement, std::tie(a) initialise une référence de membre de données à a. std::Tuple<int>(24) crée un membre de données avec la valeur 24, et l'affectation affecte 24 à la référence du membre de données dans la première structure. Mais comme ce membre de données est une référence liée à a, cela attribue fondamentalement 24 à a.

130
bolov

Cela ne répond en aucune façon à votre question, mais laissez-moi le poster quand même, car C++ 17 est fondamentalement prêt (avec le support du compilateur). Ainsi, tout en vous demandant comment fonctionne le matériel obsolète, il est probablement intéressant de regarder comment le future, la version de C++ fonctionne aussi.

Avec C++ 17, vous pouvez très bien utiliser std::tie en faveur de ce que l'on appelle liaisons structurées. Ils font la même chose (bien, pas la même chose, mais ils ont le même effet net), bien que vous ayez besoin de taper moins de caractères, il n’a pas besoin de support de bibliothèque, et vous aussi avoir la capacité de prendre des références, si cela se trouve être ce que vous voulez.

(Notez que dans C++ 17, les constructeurs font la déduction d'arguments, donc make_Tuple est devenu quelque peu superflu aussi.)

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
26
Damon