std::bind and lambda functions 6

This is the last post in this series, and I am going to use it to wrap up a few things that didn’t quite fit in anywhere else.

As I said at the start, this series is not the ultimate guide to std::bind and lambda, it has focused on a few things that I find interesting. As always, if you want definitive information look at these three references:

As you’d expect, Herb Sutter has written and spoken about lambdas many times.


Passing values by reference

All of my examples have assumed that bound or captured arguments are captured by value. Take a look at this code:

double add( int i, float f )
{
    return i + f;
}

typedef std::function< double( int ) > SingleArgumentAdd;
float f( 10.0f );

SingleArgumentAdd fn0( std::bind( add, _1, f ) );
SingleArgumentAdd fn1( [ f ]( int i )
{ 
    return add( i, f );
} );

f = 20.0;

std::cout << fn0( 5 ) << ", ";
std::cout << fn1( 5 ) << "\n";

It writes out 15, 15 showing that the value of f that is used is the value when the function object is created.

We could pass in a pointer to f rather than f itself:

double add1( int i, float* pF )
{
    return i + *pF;
}
float f( 10.0f );
float* pF( &f );

SingleArgumentAdd fn0( std::bind( add1, _1, pF ) );
SingleArgumentAdd fn1( [ pF ]( int i )
{ 
    return add1( i, pF );
} );

f = 20.0;

std::cout << fn0( 5 ) << ", ";
std::cout << fn1( 5 ) << "\n";

This time we get 25, 25 - the pointer is not dereferenced until fn0 and fn1 are called.

What about passing f by reference? That should give us the same result as passing by pointer - the value of f is the value when the function is called. Here's the code:

float f( 10.0f );

SingleArgumentAdd fn0( std::bind( add, _1, std::cref( f ) ) );
SingleArgumentAdd fn1( [ &f ]( int i )
{ 
    return add( i, f );
} );

f = 20.0;

std::cout << fn0( 5 ) << ", ";
std::cout << fn1( 5 ) << "\n";

The code prints out 25, 25, just as we'd expect. We had to do two things differently though:

  1. We have to explicitly tell std::bind that f must be passed by reference (a const reference in this case). If we don't do this, std::bind will dereference f and use whatever value is in f when std::bind is called. std::cref is a function that returns an object that wraps the reference and makes sure that the reference is not dereferenced until fn0 and fn1 are called. See The C++ Programming Language, 4th Edition, section 33.5.1 for more information.
  2. Lambdas are much simpler. We write &f in the capture block to indicate that f should be captured by reference.

Whether we are using pointers, references, lambdas or std::bind there is one thing we need to keep in mind - when we dereference the reference, the object it refers to must still exist.


Nesting and chaining functions

When we have an object of type std::function it is a first class object in C++. We can copy it, pass it to functions, return it from functions, store it as a member variable and treat it like any other object.

This means that we can set up nested functions, we can chain functions together and we can use std::function objects for callbacks. As always, just because you can doesn't mean that you should. I am experimenting with chains of functions for my current project and, while I can make it work, it isn't pretty and it isn't clear what is going on.


operator () should be const for predicates

I am not going to go into this in detail because others have done a better job than I have. In general, when you write your own predicate function object, operator () should be const - it cannot update any stored state in the function object. There are two reasons for this:

  1. The standard does not place any restrictions on the number of times a predicate is copied (I think that std::for_each is the one exception). As Josuttis points out in The C++ Standard Library: A Tutorial and Reference (2nd Edition) section 10.1.4, a typical implementation of std::remove_if does copy the predicate and will give the wrong result if the predicate is storing state.
  2. By default, there is no guarantee that the algorithm will traverse the list from the beginning to the end. As far as I can tell, these are the only algorithms which are guaranteed to traverse from beginning to end: std::for_each, std::copy, std::move, std::accumulate, std::inner_product, std::partial_sum and std::adjacent_difference. I found these by searching the standard for the phrases starting from first and proceeding to last, and in order.

By default, the function object constructed by a lambda expression has a const operator (). If you really want it to be non-const you can declare the lambda mutable.


Why do we need std::find_if and std::count_if?

There are algorithms which have an overloaded predicate and non-predicate version, for example, std::is_sorted :

bool
is_sorted( ForwardIterator beg, ForwardIterator end );

bool
is_sorted( ForwardIterator beg, ForwardIterator end, BinaryPredicate op );

If we look at std::find we see why we can't overload it with a predicate and non-predicate version:

InputIterator
find( InputIterator beg, InputIterator end, const T& value );

Despite the fact that we can supply template specializations that will match a function, we can't know whether we are supplying a function because we are searching for a function in a container of functions or whether we are supplying a function to act as a predicate. That means we have to have a separate std::find_if (and std::count_if) function.


std::bind and member functions

In part 2 we looked at pointers to member functions, but I never demonstrated how these work with std::bind.

class MemberFunctionDemo
{
public:
    double add( int i, float f )
    {
        return i + f;
    }

    MemberFunctionDemo()
    {
        std::cout << std::bind( 
            &MemberFunctionDemo::add, 
            this, 
            _1, 
            20.0f )( 10 );
    }
};

The first argument to std::bind is always the function that will be called. For member functions we have to use & to get a pointer to the function, and the function name must be fully qualified. The second argument is the object to call the function on - it can be a pointer or a reference to the object, in my example, I am using this.


std::bind as a way of limiting scope

A colleague at Adobe pointed out that we can use a lambda to create a block of code that does not have access to all variables in the surrounding scope:

int i, j, k;

// Some code here...

[ i, j ](){
    // Only have access to i & j here.
    // No access to k
}();

I am not sure whether this is a Good Thing, a Bad Thing or just a Thing.

Leave a Reply

Your email address will not be published.