std::bind and lambda functions 2

In part 1 we looked at various things we can do with functions in C. Since C++ is mostly a superset of C we can do those things in C++ as well. C++ also gives us some other ways of creating functions though. Let’s dispense with the easy options quickly then get on to the more interesting possibilities.


A function in a namespace

namespace arithmetic
{
    double add( int i, float f )
    {
        return i + f;
    }
}
double (*pFn)( int, float ) = arithmetic::add;

A static function in a class

class TestClass
{
public:
    static double add( int i, float f )
    {
        return i + f;
    }
    
};
double (*pFn)( int, float ) = TestClass::add;

Nothing tricky about putting functions into a namespace, or a class acting as a namespace.


Overloaded functions

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

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

Even overloaded functions work just as we’d expect. Section 13.4 of the C++11 standard states:

The function selected is the one whose type is identical to the function type of the target type required in the context.

Let’s not get too confident though, overloads are going to return to trouble us later.


Pointer to member function

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

Ignore the fact that add makes absolutely no sense as a member function (very few of these examples make any sense outside of a narrow context).

What is the type of a pointer to member function? We already know what the type of add would be if it wasn’t a member function:

double (*)( int, float );

The key thing missing is the name of the class of which the function was a member. The type has to specify the class for type safety – you don’t want anyone trying to call the add function for an object that doesn’t even have an add function (and yes, you can force this with casting, but you can force almost anything with casting). Given that * is in the place where the function name used to go, we put the class name in the obvious (ish) place:

double (TestClass2::*)( int, float );

We can write this code to get the address of the add function:

double (TestClass2::*pFn)( int, float ) = &TestClass2::add;

And we can call it like this:

TestClass2 c;
(c.*pFn)( 10, 20.0f );
TestClass2* p = &c;
(p->*pFn)( 10, 20.0f );

We have two new operators .* and ->*.

Notice that this time we had to use the address-of operator & to get the address of the function and, because the function call operator () is higher precedence than .* and ->*, we need the brackets around c.*pFn and p->*pFn.

(This also works with virtual functions).


There is one more trick we need to know about before we can start applying our knowledge to std::bind and lambda. At the beginning of part 1 I wrote:

The round brackets are known as the function call operator.

At the time, referring to the function call operator might have seemed like overkill. It’s a C function, you just call the thing by passing in the arguments in the brackets. That nomenclature is important though because C++ supplies us with a function call operator that we can use as a member function of a class:

class Adder
{
public:
    double operator()( int i, float f ) const
    {
        return add( i, f );
    }
    
};

(There is a reason why I am calling the add function rather than just doing the addition correctly – that reason will become apparent later).

We call the member function operator() by applying the function call operator to an object of type Adder :

Adder a;
a( 10, 20.0f );

While describing a function in part 1 I said:

we take a thing with a name … and apply an operator – () – to it.

Well, that’s what we have here. We have a thing with a name (a) and we’re applying the function call operator (()) to it. That calls the member function operator().

We have a thing that is acting like a function but is actually an object. It is often known as a function object or functor. At first glance a function object seems like a rather heavyweight way of creating a function, but objects have a really useful property – they can store state.

Let’s look at what we can do with that property of storing state. We don’t have to pass in both arguments to operator(), we can have one of the arguments specified at construction time:

class Adder
{
public:
    Adder( float f )
    : f_( f )
    {}
    
    double operator()( int i ) const
    {
        return add( i, f_ );
    }
    
private:
    float f_;
};
Adder a( 20.0f );
a( 10 );

Again, this just looks like a heavyweight way of creating a function-like thing. It’s more work, it’s not as convenient to call – why should we bother? We bother because we have taken a function that takes two arguments to the function call operator – add – and turned it into a function (object) that takes a single argument to the function call operator. The second argument is supplied in the constructor. This is the C++ version of currying.

The one-argument version is useful because there are algorithms that expect a function that takes a single argument, for example the single source sequence version of std::transform :

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

float f = functionReturningFloat();
Adder a( f );
std::transform( 
    std::begin( v_i ), 
    std::end( v_i ), 
    std::back_inserter( v_d ), 
    a );

We don’t even need to create a named Adder object, we can just use a temporary:

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

The call to std::transform is equivalent to doing this:

for( 
    std::vector< int >::iterator i( std::begin( v_i ) ); 
    i != std::end( v_i ); 
    ++i )
{
    v_d.push_back( add( *i, f ) );
}

And this is why the fact that we took a two argument function and turned it into a single argument function is important. std::transform requires a single argument function. By wrapping add in a function object and specifying the second argument in the constructor we ended up with a single argument function (a unary operation in standardese).

If we take a look at the declaration of std::transform in the standard (again, just the single-source-sequence one), we see this:

template<
    class InputIterator, 
    class OutputIterator,
    class UnaryOperation >
OutputIterator
transform(
    InputIterator first, 
    InputIterator last,
    OutputIterator result, 
    UnaryOperation op);

The final parameter of the function std::transform is a unary operation – it takes one argument. Internally, std::transform is going to use the function call operator on op, i.e. it is going to perform op( elem ) for each element in the input sequence. It is all templated, and op follows the normal rules when passing an object to a templated parameter – whatever functions are called on op must be supported by the type of op type. Since the function that std::transform calls is operator() with a single argument, the type of op must support that. We know of two “things” in C++ world that can support the function call operator – functions and function objects.

What I have been trying to say in multiple different ways is that by wrapping a function (in this case add) in a function object we can turn a function into something requiring fewer arguments at the point where the function call operator is invoked. Of course the extra arguments have to come from somewhere, and in this case they are specified in the constructor of the function object. We can give the standard algorithm exactly what it needs (an operator() with the right number and type of parameters) but supply any necessary extra paraeters via the constructor of the function object.

Here’s another example. Let’s take the file metadata structure I used in a previous post:

struct FileMetaData
{
    std::string fileName_;
    std::string directory_;
    
    std::size_t size_;
    std::time_t lastWriteTime_;
};

Assume we have an unsorted vector of file metadata objects and we want to search for a file with a given file name. std::find_if looks like the obvious choice since it lets us specify our own predicate for the search.

It seems like this function would be useful:

bool compareFileName( 
    FileMetaData const& metaData, 
    std::string const& fileName )
{
    return metaData.fileName_ == fileName;
}

compareFileName will be useful, but it is a two argument function and std::find_if takes a UnaryPredicate. Unary means it expects a single argument, and according to Merriam-Webster a predicate is:

something that is affirmed or denied of the subject in a proposition in logic

For our purposes that comes down to “something returning a bool”. compareFileName is a predicate (it will affirm or deny whether the file names match), but it is not unary.

We’re going to use our “wrap it in a function object” trick to turn compareFileName into a single argument function (object):

class CompareFileName
{
public:
    CompareFileName( std::string const& fileName )
    : fileName_( fileName )
    {
    }
    
    bool operator()( FileMetaData const& metaData ) const
    {
        return compareFileName( metaData, fileName_ );
    }
    
private:
    std::string fileName_;
};

Then we can use CompareFileName like this:

std::vector< FileMetaData > files( getFiles() );
std::string fileNameToFind( getFileName() );

std::vector< FileMetaData >::iterator i(
    std::find_if(
        std::begin( files ),
        std::end( files ),
        CompareFileName( fileNameToFind ) ) );

There is one more thing. When we looked at overloaded functions before we had no problem assigning them to a function pointer. If we try and use overloaded functions in an algorithm we run into trouble.

Here is a pair of overloaded functions:

double negate( int i )
{
    return -i;
}

double negate( float f )
{
    return -f;
}

And here is a way in which we might want to use the integer version:

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

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

Just using the name of the function directly doesn’t work, we get this error message from Visual Studio:

error C2914: 'std::transform' : cannot deduce template argument as function argument is ambiguous

How come it worked before but doesn’t work now? The reason it worked before was because of this clause in the standard:

The function selected is the one whose type is identical to the function type of the target type required in the context.

The important words are target type. When we are supplying an argument to a template function we don’t know what the target type is. We can’t have a target type until we know the function type, but in order to pick the right function (and hence find out its type) we need to know the target type.

There are at least three ways around this. One is to assign the function to a function pointer where we know the target type and therefore won’t get an error:

double (*pNegateFn)( int ) = negate;

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

Secondly, we can use what is possibly the most legitimate cast ever:

std::transform( 
    std::begin( v_i ), 
    std::end( v_i ), 
    std::back_inserter( v_d ), 
    static_cast< double (*)( int ) >( negate ) );

And finally we can specify the template arguments to std::transform explicitly. We don’t normally do this with functions, but in this case it gives us the target type that the compiler is looking for:

std::transform< 
  std::vector< int >::const_iterator, 
  std::back_insert_iterator< std::vector< double > >,
  double (*)( int ) >( 
    std::begin( v_i ), 
    std::end( v_i ), 
    std::back_inserter( v_d ), 
    negate );

Note that I had to specify the type of std::back_inserter for this to compile. When you are specifying template arguments explicitly you need to get them right.

In practice I rarely run into problems with overloaded functions, when I do, I use static_cast to get around them.


We’ve reached the end of part 2 and still haven’t talked about std::bind or lambda. At least we’re looking at the right language now, and we have laid all of the groundwork for the next exciting episode where I promise we will get to std::bind (and maybe lambda).

One thought on “std::bind and lambda functions 2

Leave a Reply

Your email address will not be published.