Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

SL & STL C++ Discussion :

Utiliser std::forward deux fois ?


Sujet :

SL & STL C++

  1. #1
    Membre éclairé
    Utiliser std::forward deux fois ?
    Bonjour,

    Je suis dans le cas (simplifié) suivant :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    template<typename F1, typename F2, typename ... Args>
    std::pair<std::function<void()>, std::function<void()>>
    do_bind(F1&& f1, F2&& f2, Args&& ... args) {
        return make_pair(
            std::bind(std::forward<F1>(f1), std::forward<Args>(args)...),
            std::bind(std::forward<F2>(f2), std::forward<Args>(args)...)
        );
    }


    Je ne maîtrise pas encore totalement la sémantique de mouvement, mais j'ai peur que ce code ne fonctionne pas comme je le voudrais : d'après moi, les arguments sont "déplacés" pour le premier bind, il ne sont alors plus disponibles pour le second.

    On peut le tester avec ce code :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    struct A {
        int i = 0;
        A() = default;
        A(int i) : i(i) {}
        A(const A& a) : i(a.i) {}
        A(A&& a) : i(a.i) { a.i = 0; }
    };
     
    void some_func(const A& a) { std::cout << a.i << std::endl; }
    void some_other_func(const A& a) { std::cout << a.i << std::endl; }
     
    int main() {
        auto ret = do_bind(&some_func, &some_other_func, A(5));
        ret.first();
        ret.second();
    }

    ... qui affiche 0 et 5, suggérant que le second membre de la paire a été initialisé en premier.

    Ma question est donc :
    • que faut-il faire pour que les deux fonctions ainsi créées reçoivent toutes deux des arguments aussi bien "forwardés" que possible?
    • quels sont les inconvénients de cette approche :
      Code :Sélectionner tout -Visualiser dans une fenêtre à part
      1
      2
      3
      4
      5
      6
      7
      template<typename F1, typename F2, typename ... Args>
      std::pair<std::function<void()>, std::function<void()>>
      do_bind(F1&& f1, F2&& f2, Args&& ... args) {
          auto fb1 = std::bind(std::forward<F1>(f1), args...);
          auto fb2 = std::bind(std::forward<F2>(f2), std::forward<Args>(args)...);
          return make_pair(std::move(fb1), std::move(fb2));
      }


    Je précise qu'un des buts de cette fonction est d'éviter à son utilisateur d'avoir à fournir deux fois une même série d'arguments.

  2. #2
    Membre émérite
    Bonjour ,

    Le problème dans ce cas c'est que bind stocke en interne les arguments args... pour les réutiliser plus tard lors de l'appel, et qu'il les stocke soit en les copiant, soit en les déplaçant selon qu'on lui passe une lvale ou une rvalue.

    Hors le but de std::forward c'est de prendre ll'expression qu'elle reçoit en paramètre et la retransformer correctement en lvalue ou rvalue, selon la manière dont on a appelé la fonction.

    Par exemple à ce stade do_bind(&some_func, &some_other_func, A(5)); l'expression A(5) est une rvalue et donc lorsqu'on fait ensuite std::bind(std::forward<F1>(f1), std::forward<Args>(args)...) std::forward va transormer l'arg en rvalue, et donc permettre std::bind de le stocker en le déplaçant

    On aurait le même problème dans ce cas par exemple :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    void foo(A&& a)
    {
       A a1(std::forward<A>(a));
       A a2(std::forward<A>(a));
    }
     
    foo(A(5));


    Il me semble que le plus simple reste de ne pas forwarder du tout et tout passer par const ref. Après tout il faut bien que les deux bind copient les arguments pour pouvoir les réutilser plus tard donc on va perde l'info de lvaluness/rvalueness quoi qu'il arrive:

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    template<typename F1, typename F2, typename ... Args>
    std::pair<std::function<void()>, std::function<void()>>
    do_bind(F1&& f1, F2&& f2, const Args& ... args) {
        return make_pair(
            std::bind(std::forward<F1>(f1), args...),
            std::bind(std::forward<F2>(f2), args...)
        );
    }

  3. #3
    Membre éclairé
    Merci pour ta réponse. Ca confirme donc ce que je pensais.

    Citation Envoyé par Arzar Voir le message
    Il me semble que le plus simple reste de ne pas forwarder du tout et tout passer par const ref. Après tout il faut bien que les deux bind copient les arguments pour pouvoir les réutilser plus tard donc on va perde l'info de lvaluness/rvalueness quoi qu'il arrive:
    Oui, si on fait comme ça et qu'on utilise un temporaire, il sera copié pour chaque fonction. Mais avec le dernier code que je propose, on économise une copie, le dernier bind pouvant profiter de la sémantique de mouvement. Dans de nombreux cas, c'est une micro-optimisation, mais ça peut s'avérer intéressant (pourvu que ça ne créé pas d'autres problèmes, mais je n'en vois pas pour le moment).