std::bind and lambda functions 3

In part 2 we saw that by wrapping a function inside a function object we could take a function that requires two arguments and turn it into a function object that requires one argument. The code we ended up with looks like this:

class Adder
{
public:
    Adder( float f )
    : f_( f )
    {}
    
    double operator()( int i ) const
    {
        return add( i, f_ );
    }
    
private:
    float f_;
};
std::vector< int > v_i( functionReturningVector() );
std::vector< double > v_d;

float f = functionReturningFloat();

std::transform( 
    std::begin( v_i ), 
    std::end( v_i ), 
    std::back_inserter( v_d ), 
    Adder( f ) );

There is a problem here – we had to write a lot of boilerplate in order to wrap one function – Adder is 15 lines long, the call to add is a single line. Fortunately, C++11 gives us a couple of ways to get the same effect but with much less boilerplate. We can use std::bind to adapt add as follows:

std::vector< int > v_i( functionReturningVector() );
std::vector< double > v_d;

float f = functionReturningFloat();

std::transform( 
    std::begin( v_i ), 
    std::end( v_i ), 
    std::back_inserter( v_d ), 
    std::bind( add, std::placeholders::_1, f ) );

[ Aside:

I have put in the fully scoped name of the placeholder _1 just to show where it comes from. Even though I am normally a fan of using the fully qualified name this one is just so ugly I will assume that:

using namespace std::placeholders;

is in use for the rest of my examples. This simplifies our loop to:

std::transform( 
    std::begin( v_i ), 
    std::end( v_i ), 
    std::back_inserter( v_d ), 
    std::bind( add, _1, f ) );

End aside ]


We have two std::transform loops, one of which uses Adder( f ), the other of which uses std::bind( add, _1, f ).

We know what the code Adder( f ) does. It creates a function object of type Adder which implements operator () to call add with one argument that comes from the function call operator, and another argument that was supplied in the constructor. We need the code std::bind( add, _1, f ) to do something very similar.

Let’s remove std::bind from the context of std::transform so we can focus on one thing at a time. Here’s some code we can use to look at std::bind in isolation:

float f = functionReturningFloat();
auto fn = std::bind( add, _1, f );
std::cout << fn( 7 ) << "\n";

The call to std::bind returns a function object that we assign to the variable fn. I am using auto because I don't want to get into the question of what the type of the return value of std::bind is yet (we'll look at it later). fn is a function object that takes a single argument to its function call operator. We can therefore invoke the function call operator with a single argument - fn( 7 ) and write out the result, which will be a double because the return value of add is a double.

There are a lot of moving parts here. I am going to walk through them.

We have three functions in play:

  • add The function we ultimately want to call. The function we are going to wrap.
  • fn The function (object) that wraps add. This is the thing that converts a two argument function into a single argument function.
  • std::bind The function that creates fn from add.

We call std::bind to create fn. Later we call fn which in turn will call add.

The function we want to wrap - add - is a two argument function. Anything that calls add must call it with two arguments. Therefore, fn must call add with two arguments. Since std::bind is creating fn, std::bind must know what the two arguments are. When we call std::bind we not only supply it with the function to be wrapped - add - we also supply it with the two arguments that must be passed to add. These two arguments are _1 and f.

The second argument is easy. f is a variable of type float and we already know that the second argument to add must be of type float. It all matches just as it should.

The first argument is more interesting - _1. Arguments of the form _n are called placeholders, that's why they live in the std::placeholders namespace. The placeholder argument links the argument we pass when we call fn to the first argument that gets passed to add. Let's look at the code again:

auto fn = std::bind( add, _1, f );
std::cout << fn( 7 ) << "\n";

_1 says "take the first argument that is passed to fn and pass it to add as the first argument". In this example, when we call fn( 7 ), that ultimately results in a call to add( 7, f )

Any argument that is passed to fn can be passed on to add in any position. The placeholder we use (_1, _2 etc.) tells us which argument to use from the argument list passed to fn::operator (). The position of the placeholder in the call to std::bind tells us which position that argument will end up in when we call add.

Let me lay out the code slightly differently:

float f = 10.0f;
auto fn = std::bind( 
    add,        // add is the function we are wrapping
    _1, f       // add has two arguments therefore 
                // we supply two arguments here.
    );

// At this point, std::bind has been called but 
// add and fn have not been called

std::cout << fn( 7 ) << "\n";   // Call fn, which in 
                                // turn calls add.

Here's an example with a different placeholder:

float f = 10.0f;
auto fn = std::bind( add, _2, f );
std::cout << "result = " << fn( 7, 12 ) << "\n";

We are now using placeholder _2. This means that we have to supply the call to fn with two arguments (and if we don't we get an unfriendly error message). The output of this piece of code is:

result = 22

showing that the second argument from the call fn is the one that is used. Of course this is a nonsensical example because there is no point in passing two arguments to fn in the first place - the first argument is ignored so there was no point in supplying it.

There are plenty of other cute tricks we can play. Since an int is convertible to a float we can do this:

auto fn = std::bind( add, _1, _2 );
std::cout << "result = " << fn( 7, 12 ) << "\n";
result = 19

or this:

auto fn = std::bind( add, _2, _1 );
std::cout << "result = " << fn( 7, 12 ) << "\n";
result = 19

Since add is commutative the result is the same - I am just showing that we can change the order in which the arguments are supplied to std::bind (and therefore the order of the arguments supplied to add).

We can use a placeholder more than once:

auto fn = std::bind( add, _1, _1 );
std::cout << "result = " << fn( 7 ) << "\n";
result = 14

Or not use a placeholder at all (yes, doing this in real life is a waste of time, I just want to show all the possibilities):

auto fn = std::bind( add, 6, 5 );
std::cout << "result = " << fn() << "\n";
result = 11

I can call the object returned from std::bind directly:

float f = 10.0f;
double d = std::bind( add, _1, f )( 10 );
std::cout << "result = " << d << "\n";
result = 20

For a not-pointless example, back in this post I used placeholders to swap the order of arguments to a comparator in order to reverse the sorting order:

std::partial_sort( 
    ReverseIter( pivotElementIterator ), 
    ReverseIter( displayBegin ), 
    ReverseIter( std::begin( files ) ), 
    std::bind( comparator, _2, _1 ) );

Errors

There are a number of things we can do wrong.

We can specify too many arguments in the std::bind call. add takes two arguments, let's try giving it three:

double d = std::bind( add, _1, f0, f1 )( 10 );

Visual Studio gives the surprisingly helpful error message:

// error C2197: 'double (__cdecl *)(int,float)' : too many arguments for call

The message from GCC is longer and more confusing, but does include the words "too many arguments to function".


We can specify too few arguments in the std::bind call:

double d = std::bind( add, _1 )( 10 );

I get the following (also very helpful) error message from Visual Studio:

error C2198: 'double (__cdecl *)(int,float)' : too few arguments for call

As before, the GCC message is more verbose, but does include the words "too few arguments to function".


We can specify too few arguments in the call to fn:

auto fn = std::bind( add, _1, f );
std::cout << "result = " << fn() << "\n";

Sadly the resulting error message is almost incomprehensible.


Specifying too many arguments in our call to fn does not result in an error:

auto fn = std::bind( add, _1, f );
std::cout << "result = " << fn( 
    7, 15.6, 
    std::complex< double >( 1.0, 2.0 ) ) << "\n";

As always, just because you can do something doesn't mean that you should do it.


We can give an argument of the wrong type to std::bind:

std::complex< double > c( 1.0, 2.0 );
auto fn = std::bind( add, _1, c );
fn( 7.0 );   // Error reported here
error C2664: 'double (int,float)' : cannot convert argument 2 from 'std::complex' to 'float'

Interestingly, the error is not reported until we try and call fn.


We can give an argument of the wrong type when we call fn:

std::complex< double > c( 1.0, 2.0 );
auto fn = std::bind( add, _1, f );
fn( c );
error C2664: 'double (int,float)' : cannot convert argument 1 from 'std::complex' to 'int'

Finally, let's get back to our std::transform call:

std::transform( 
    std::begin( v_i ), 
    std::end( v_i ), 
    std::back_inserter( v_d ), 
    std::bind( add, _1, f ) );

std::transform is a function. When we call a function, we evaluate all of the arguments to that function. One of those arguments is the result of a call to std::bind. As we have seen, the result of calling std::bind is a function object that wraps add and turns it into a single argument function. Look at the definition of std::transform (this is from the GCC implementation. I have removed some error checking and done some reformatting):

template<
    typename _InputIterator, 
    typename _OutputIterator,
    typename _UnaryOperation >
_OutputIterator
transform(
    _InputIterator __first, 
    _InputIterator __last,
    _OutputIterator __result, 
    _UnaryOperation __unary_op)
{
    for (; __first != __last; ++__first, ++__result)
        *__result = __unary_op(*__first);
    
    return __result;
}

std::transform loops from __first to __last, and each time around the loop invokes the function call operator on __unary_op with a single argument - __unary_op(*__first). We must supply std::transform with a thing-that-supports-the-function-call-operator-taking-a-single-argument. That is exactly what we have done using std::bind.

More to come in part 4, including the mysterious type of the return value of std::bind.

3 thoughts on “std::bind and lambda functions 3

  1. I find it very interesting that if you bind an argument of the wrong type you don’t get an error until the function is called. This was quite a surprise. I guess the result function type must have templated overloads for invoking the wrapped function, which are only instantiated at the call site. It was also a surprise that you can pass extra arguments to the function returned from bind. I look forward to hearing more about what type is returned from bind.

    • Bob says:

      I haven’t dug into the implementation of std::bind, but I think you’re right. std::bind itself has to cope with any number of arguments of any type and it isn’t until we try and apply operator() that the types of those arguments need to match up with the types required.

Leave a Reply

Your email address will not be published.