5 votes

Comment std::forward fonctionne-t-il dans le contexte d'une expression de repli?

Dans la STL de MSVC, l'implémentation de std::apply est la suivante :

template 
constexpr decltype(auto) _Apply_impl(_Callable&& _Obj, _Tuple&& _Tpl, index_sequence<_Indices...>) noexcept(/* [...] */) {
    return _STD invoke(_STD forward<_Callable>(_Obj), _STD get<_Indices>(_STD forward<_Tuple>(_Tpl))...);
}

Dans l'expression _STD get<_Indices>(_STD forward<_Tuple>(_Tpl))..., std::get est appelé le même nombre de fois que la taille du tuple. Cependant, à chaque invocation de std::get, il y a une invocation correspondante de std::forward. Lors de la transmission d'une rvalue, std::forward est équivalent à std::move, ce qui implique plusieurs appels à std::move pour l'objet _Tpl. Cela semble potentiellement invalide. Quelqu'un pourrait-il m'aider à expliquer ma préoccupation ?

J'ai essayé de rechercher à travers diverses ressources, mais je n'ai pas trouvé la réponse que je cherchais. Même à partir de la définition, je n'ai pas été convaincu.

6voto

user17732522 Points 943

impliquant de multiples appels à std::move pour l'objet _Tpl. Cela semble potentiellement invalide.

Oui, il y a de multiples appels à std::move sur le tuple. Mais en soi, cela n'est pas invalide.

Généralement, passer un objet avec std::move à une fonction est une promesse à cette fonction que l'appelant ne se basera pas sur l'état de l'objet après l'appel. Par conséquent, un second std::move vers une autre fonction aurait généralement une sémantique incorrecte, car il passerait un état non spécifié à la deuxième fonction.

Mais il existe plusieurs cas où une fonction fait des promesses plus fortes à l'appelant. Par exemple, std::unique_ptr promet qu'après l'appel de son constructeur de déplacement ou de son assignation de déplacement, l'objet transmis sera dans un état vide. Il peut donc toujours être utilisé comme pointeur nul après le std::move.

Pour std::get, on sait encore plus précisément ce qu'il fera : il ne fera rien d'autre que de renvoyer une référence à l'élément demandé du tuple. Et en fonction de savoir si le tuple a été passé en tant que rvalue ou lvalue, il renverra également des références lvalue ou rvalue comme approprié lorsqu'il est combiné avec le type de l'élément.

Ainsi, std::get ne modifiera effectivement pas l'état du tuple et l'expansion du pack (qui n'est pas une expression de repli d'ailleurs.) résultera en une liste d'une référence à chaque élément du tuple, potentiellement en tant que lvalue ou rvalue, mais jamais deux rvalues vers le même objet. Ainsi, en passant les références à la fonction appelée, il n'y a pas de problème équivalent à des double-std::move génériques.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X