2015-07-22 1 views
1

je tente de mettre en œuvre std::async à partir de zéro, et se sont heurtées à un accident de parcours avec des arguments de type de mouvement uniquement. L'essentiel est, C++ 14 init-captures nous permettent de capturer des variables simples "par déplacement" ou "par transfert parfait", mais ils ne semblent pas apparaissent pour nous permettre de capturer paquets de paramètres "par déplacement" ni "par transfert parfait", car vous ne pouvez pas capturer un pack de paramètres par init-capture - uniquement par capture nommée.En utilisant std :: bind pour capturer un pack de paramètres « par mouvement »

J'ai trouvé ce qui semble être une solution de contournement, en utilisant std::bind pour capturer le pack de paramètres « par mouvement », puis en utilisant une enveloppe pour déplacer les paramètres de la mémoire de l'objet de liaison dans les fentes des paramètres de la fonction Je veux vraiment appeler. Il semble même élégant, si vous ne pensez pas trop à ce sujet. Mais je ne peux pas m'empêcher de penser qu'il doit y avoir un meilleur moyen - idéalement celui qui ne compte pas sur std::bind du tout.

(Dans le pire des cas, j'aimerais savoir combien de std::bind je devrais réimplémenter par moi-même pour m'en éloigner.) L'un des objectifs de cet exercice est de montrer comment tout est mis en œuvre. le chemin vers le bas, afin d'avoir une dépendance aussi compliquée que std::bind vraiment nul)

Mes questions sont les suivantes:.

  • Comment puis-je faire mon travail de code, sans utiliser std::bind? (Par exemple, en utilisant uniquement des caractéristiques linguistiques essentielles. Lambdas génériques sont un jeu équitable.)

  • est mon pare-balles std::bind solution de contournement? Autrement dit, quelqu'un peut-il montrer un exemple où le std::async du STL fonctionne et mon Async échoue?

  • Pointeurs à la discussion et/ou des propositions pour soutenir la capture paramètre-pack en C++ 1Z sera accepté avec reconnaissance.

Here's my code:

template<typename UniqueFunctionVoidVoid> 
auto FireAndForget(UniqueFunctionVoidVoid&& uf) 
{ 
    std::thread(std::forward<UniqueFunctionVoidVoid>(uf)).detach(); 
} 

template<typename Func, typename... Args> 
auto Async(Func func, Args... args) 
    -> std::future<decltype(func(std::move(args)...))> 
{ 
    using R = decltype(func(std::move(args)...)); 
    std::packaged_task<R(Args...)> task(std::move(func)); 
    std::future<R> result = task.get_future(); 

#ifdef FAIL 
    // sadly this syntax is not supported 
    auto bound = [task = std::move(task), args = std::move(args)...]() { task(std::move(args)...) }; 
#else 
    // this appears to work 
    auto wrapper = [](std::packaged_task<R(Args...)>& task, Args&... args) { task(std::move(args)...); }; 
    auto bound = std::bind(wrapper, std::move(task), std::move(args)...); 
#endif 

    FireAndForget(std::move(bound)); 

    return result; 
} 

int main() 
{ 
    auto f3 = [x = std::unique_ptr<int>{}](std::unique_ptr<int> y) -> bool { sleep(2); return x == y; }; 
    std::future<bool> r3 = Async(std::move(f3), std::unique_ptr<int>{}); 
    std::future<bool> r4 = Async(std::move(f3), std::unique_ptr<int>(new int)); 
    assert(r3.get() == true); 
    assert(r4.get() == false); 
} 
+0

vous devriez préférer rediriger les références avec 'std :: forward' pour prendre les paramètres par valeur et utiliser' std :: move' –

+0

Les tuples peuvent vous donner beaucoup de latitude lorsqu'il s'agit de stocker des paquets de choses (contraste 'std: : tie', 'std :: make_tuple',' std :: forward_as_tuple' ou en utilisant un constructeur directement) et quand il s'agit de 'restaurer' les éléments du pack original. D'un autre côté, cela soulève la question de savoir comment implémenter un tuple assez facilement à des fins didactiques et/ou comment «développer» des éléments de tuple dans un appel. Est-ce que le fait de prendre 'std :: tuple' pour acquis empêcherait une réponse d'être utile? –

+0

@PiotrS Je ne crois pas qu'il soit * possible * d'implémenter 'async' sans capturer les arguments par valeur. Sinon, vous finissez avec des références qui pendent au moment où vous vous déplacez pour exécuter la tâche.(N'hésitez pas à me prouver le contraire en écrivant du code dans une réponse!) @LucDanton bien sûr, je suis heureux d'utiliser 'tuple' dans une solution, mais je ne sais pas comment" développer des éléments de tuple dans un appel "sans beaucoup de lignes de code, même en C++ 14. – Quuxplusone

Répondre

2

Il m'a été suggéré en ligne qu'une autre approche serait de capturer le pack args dans un std::tuple, puis re-développer cette tuple dans la liste des arguments de task en utilisant quelque chose comme (bientôt disponible dans une bibliothèque standard C++ 17 près de chez vous!).

auto bound = [task = std::move(task), args = std::make_tuple(std::move(args)...)]() { 
     std::experimental::apply(task, args); 
    }; 

Ceci est beaucoup plus propre. Nous avons réduit la quantité de code de bibliothèque concernée, en passant de bind à "simplement" tuple. Mais c'est toujours une grosse dépendance dont j'aimerais pouvoir me débarrasser!