passing lambda as argument - by reference or value?

12,102

Solution 1

As a possible drawback, note that passing by copy could not work if the lambda isn't copyable. If you can get away with it, passing by copy is just fine.
As an example:

#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);
}

In the snippet above, lambda isn't copyable because of foo. Its copy constructor is deleted as a consequence of the fact that the copy constructor of a std::unique_ptr is deleted.
On the other side, F &&f accepts both lvalue and rvalue references being it a forwarding reference, as well as const references.
In other terms, if you want to reuse the same lambda as an argument more than once, you cannot if your functions get your object by copy and you must move it for it's not copyable (well, actually you can, it's a matter of wrapping it in a lambda that captures the outer one by reference).

Solution 2

As lambda expressions can have their own fields (like classes), copying / using reference can cause different results. Here's simple example:

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;
}

Btw if you want to judge it by performance there's actually no difference, lambdas are very small and easy to copy.

Also – if you want to pass lambda as parameter (not a variable that contains it) you should use forwarding reference so it could work.

Share:
12,102
hg_git
Author by

hg_git

Updated on June 09, 2022

Comments

  • hg_git
    hg_git almost 2 years

    I've written a template code that takes a functor as an argument and after some processing, executes it. Although someone else might pass that function a lambda, a function pointer or even an std::function but it is meant primarily for lambda(not that I ban other formats). I want to ask how should I take that lambda - by value? by reference? or something else.

    Example 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);
    }
    

    In above code, I've a choice of making it either of these -

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

    What would be the better way and why? Are there any limitations to any of these?

  • skypjack
    skypjack about 7 years
    @NathanOliver Of course, but you cannot reuse the lambda anymore. If you want to pass it to two functions as in the example, it isn't possible.
  • Abhinav Gauniyal
    Abhinav Gauniyal about 7 years
    @skypjack followup ques - is it possible to fallback to movable functor in a scenario like this other than defining two similar overloads..
  • skypjack
    skypjack about 7 years
    @AbhinavGauniyal By defining two overloads that do exactly the same job, to be able only to not use a forwarding reference? I don't get exactly the benefits. Did I misunderstand something?
  • Abhinav Gauniyal
    Abhinav Gauniyal about 7 years
    @skypjack I meant having two definitions of h() where first one takes by value and 2nd by &&.. So that compile doesn't fails.
  • skypjack
    skypjack about 7 years
    @AbhinavGauniyal And the two functions have exactly the same body or the one that takes the lambda by copy forwards it to the other one. That's a possible approach, of course, if it's worth it for you...
  • KeyC0de
    KeyC0de over 5 years
    @skypjack So a non-copyable lambda is one which captures at least one variable of non-copyable type?
  • skypjack
    skypjack over 5 years
    @Nik-Lz not the best definition ever but close to reality. ;-)
  • AnT stands with Russia
    AnT stands with Russia about 5 years
    "As lambda expressions can have their own fields" and "lambdas are very small and easy to copy" is two contradictory statements. How large a lambda is depends on how large these fields are. One can create an arbitrarily large and very hard to copy lambda.