Comment fonctionne std::tie?
J'ai utilisé std::tie
sans y réfléchir beaucoup. 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
modifier a
et b
? Je trouve cela plus intéressant car il s'agit d'une fonctionnalité de Bibliothèque, pas d'une fonctionnalité de langue, donc c'est sûrement quelque chose que nous pouvons mettre en œuvre et comprendre.
2 réponses
Afin de clarifier le concept de base, réduisons-le à un exemple plus basique. Bien que std::tie
soit utile pour les fonctions renvoyant (un tuple de) plus de valeurs, nous pouvons le comprendre très bien avec une seule valeur:
int a;
std::tie(a) = std::make_tuple(24);
return a; // 24
Choses que nous devons savoir pour aller de l'avant:
-
std::tie
construit et renvoie un tuple de références. -
std::tuple<int>
etstd::tuple<int&>
sont 2 classes complètement différentes, sans connexion entre elles, autres qu'elles ont été générées à partir de la même modèle,std::tuple
. -
Tuple a un
operator=
acceptant un tuple de types différents (mais le même nombre), où chaque membre est assigné individuellement-from cppreference :template< class... UTypes > tuple& operator=( const tuple<UTypes...>& other );
(3) Pour tout i, attribue
std::get<i>(other)
àstd::get<i>(*this)
.
L'étape suivante consiste à se débarrasser de ces fonctions qui ne font que vous gêner, afin 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 ceci, je crée 2 types T
substituant pour std::tuple<int>
et Tr
substituant std::tuple<int&>
, dépouillé 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 me débarrasser des structures toutes ensemble (enfin, ce n'est pas 100% équivalent, mais c'est assez proche pour nous, et assez 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 pour les données référence membre dans la première structure. Mais puisque ce membre de données est une référence liée à a
, cela affecte fondamentalement 24
à a
.
Cela ne répond en aucune façon à votre question, mais permettez-moi de la poster de toute façon parce que C++17 est fondamentalement prêt (avec le support du compilateur), donc tout en me demandant comment les choses obsolètes fonctionnent, il vaut probablement la peine de regarder comment la version actuelle et future de C++ fonctionne aussi.
Avec C++17, Vous pouvez à peu près gratter std::tie
en faveur de ce qu'on appelle liaisons structurées. Ils font la même chose (eh bien, pas le même, mais ils ont le même effet net), bien que vous ayez besoin de tapez moins de caractères, il n'a pas besoin de support de bibliothèque, et vous aussi avez la possibilité de prendre des références, si cela se trouve être ce que vous voulez.
(notez qu'en C++17 constructeurs font la déduction d'arguments, donc make_tuple
est devenu un 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