Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Advantages of forwarding references as functor arguments

Tags:

c++

c++11

There are so many talk about the new C++ forwarding references. However sometimes in a particular situation it's still not clear to me whether they provide any advantage or not.

It's clear that passing heavy state functors (just like random number generators) by value is not a good idea. So let's use references. Okay, but...

...is there any advantage of using forwarding references like

template <class T, class Functor>
T func(T x, Functor &&f)
{
    T y;
    // do some computations involving f(x) and store it in y
    return y;
}

instead of const references

template <class T, class Functor>
T func(T x, const Functor &f)
{
    T y;
    // do some computations involving f(x) and store it in y
    return y;
}

in functions that take functor objects without forwarding them?

The main aspect of the question is the performance consideration.

like image 605
plasmacel Avatar asked Sep 15 '15 22:09

plasmacel


3 Answers

The choice depends on what f does, whether it makes sense to accept an rvalue, whether the function object is expected to be small, and whether reference semantics makes sense. Most standard library algorithms take function objects by value, because they are expected to have little to no state, and taking by value can be more efficient.

A good example for taking forwarding references is std::shuffle:

template< class RandomIt, class URNG >
void shuffle( RandomIt first, RandomIt last, URNG&& g );

The URNG has to be taken by reference, as 1) it's not required to be copyable and 2) you really don't want to copy it (both because RNGs can be pretty expensive to copy and because users usually want shuffle to change the RNG's state; they would be pretty surprised if two calls to shuffle with the same RNG object resulted in the same "random" order). You also can't take it by const reference, because then you can't call operator() on it - generating a random number changes the RNG's state.

The choice is then between a non-const lvalue reference, URNG& and a forwarding reference, URNG&&. Since sometimes you do want to accept an rvalue URNG - for instance when you want to shuffle to the same reproducible order, so you create a generator with a fixed seed at the call point - the latter was chosen.

like image 143
T.C. Avatar answered Sep 20 '22 21:09

T.C.


The forwarding reference version is able to bind either to Functors with operator() declared const or non const.

like image 32
Paolo M Avatar answered Sep 20 '22 21:09

Paolo M


Here is an example that compiles on forward reference but not const reference:

template <class T, class Functor>
T func(T x, Functor &&f)
{
    T y;
    y = f(x);
    return y;
}

template <class T, class Functor>
T func2(T x, const Functor &f)
{
    T y;
    y = f(x);
    return y;
}

int main()
{
    int a = 1;
    auto add_a = [=](int x) mutable { return ++a; };
    func(0, add_a); // compile
    func2(0, add_a); // does not compile
}

http://coliru.stacked-crooked.com/a/3eeb21a57d66452b

Because you mutable lambda does not have operator const (int)

In other words, invoke a mutable lambda will mutate it, however you cannot mutate const reference. So you need to use forward reference.

like image 43
Bryan Chen Avatar answered Sep 18 '22 21:09

Bryan Chen