web-dev-qa-db-fra.com

passer lambda comme argument - par référence ou valeur?

J'ai écrit un code de modèle qui prend un foncteur comme argument et l'exécute après un certain traitement. Bien que quelqu'un d'autre puisse passer cette fonction un lambda, un pointeur de fonction ou même un std::function mais il est destiné principalement à lambda (pas que j'interdise d'autres formats). Je veux demander comment dois-je prendre ce lambda - en valeur? par référence? ou autre chose.

Exemple de code -

#include <iostream>
#include <functional>
using namespace std;

template<typename Functor>
void f(Functor functor)
{
    functor();
}

void g()
{
    cout << "Calling from Function\n";
}

int main() 
{
    int n = 5;

    f([](){cout << "Calling from Temp Lambda\n";});

    f([&](){cout << "Calling from Capturing Temp Lambda\n"; ++n;});

    auto l = [](){cout << "Calling from stored Lambda\n";};
    f(l);

    std::function<void()> funcSTD = []() { cout << "Calling from std::Function\n"; };
    f(funcSTD);

    f(g);
}

Dans le code ci-dessus, j'ai le choix de le faire soit -

template<typename Functor>
    void f(Functor functor)

template<typename Functor>
    void f(Functor &functor)

template<typename Functor>
    void f(Functor &&functor)

Quelle serait la meilleure façon et pourquoi? Y a-t-il des limitations à l'un de ces éléments?

21
hg_git

Comme inconvénient possible, notez que le passage par copie ne pourrait pas fonctionner si le lambda n'est pas copiable. Si vous pouvez vous en tirer, passer par copie est très bien.
Par exemple:

#include<memory>
#include<utility>

template<typename F>
void g(F &&f) {
    std::forward<F>(f)();
}

template<typename F>
void h(F f) {
    f();
}

int main() {
    auto lambda = [foo=std::make_unique<int>()](){};

    g(lambda);
    //h(lambda);
}

Dans l'extrait ci-dessus, lambda n'est pas copiable à cause de foo. Son constructeur de copie est supprimé en raison du fait que le constructeur de copie d'un std::unique_ptr est supprimé.
D'un autre côté, F &&f accepte les références lvalue et rvalue étant une référence de transfert, ainsi que les références const.
En d'autres termes, si vous voulez réutiliser le même lambda comme argument plus d'une fois, vous ne pouvez pas si vos fonctions obtiennent votre objet par copie et vous devez le déplacer car il n'est pas copiable (eh bien, en fait, vous pouvez, il s'agit de l'envelopper dans un lambda qui capture l'extérieur par référence).

8
skypjack

Comme les expressions lambda peuvent avoir leurs propres champs (comme les classes), la copie/l'utilisation de la référence peut entraîner des résultats différents. Voici un exemple simple:

template<class func_t>
size_t call_copy(func_t func) {
    return func(1);
}

template<class func_t>
size_t call_ref(func_t& func) {
    return func(1);
}

int main() {
    auto lambda = [a = size_t{0u}] (int val) mutable {
        return (a += val);
    };
    lambda(5);

    // call_ref(lambda); – uncomment to change result from 5 to 6
    call_copy(lambda);

    std::cout << lambda(0) << std::endl;

    return 0;
}

En fait, si vous voulez le juger par les performances, il n'y a en fait aucune différence, les lambdas sont très petites et faciles à copier.

Aussi - si vous voulez passer lambda en paramètre (pas une variable qui le contient), vous devez utiliser la référence de transfert pour que cela fonctionne.

4
Kamil Koczurek