In the “no raw loops” series I have been glossing over the details of std::bind and lambda. I want to dive a little more deeply into some of those details.
I am not going to explore all of the details, as with everything I post I am going to focus on the parts that are interesting and useful to me. If you are looking for definitive descriptions of what is going on, I highly recommend these three references:
- The C++ Programming Language, 4th Edition by Bjarne Stroustrup
- The C++ Standard Library: A Tutorial and Reference (2nd Edition) by Nicolai M. Josuttis
- The C++ Standard
The standard looks intimidating, it is written in a very precise subset of English, but with a little practice at reading “standardese” it is very approachable, well written and useful. It is worth taking the time to get used to it.
Let’s go back to basics. If you understand C function pointers skip this installment, the C++ stuff won’t arrive until later.
Let’s go back to C and a simple function in C:
double add( int i, float f ) { return i + f; }
What can we do with this function? The first obvious thing we can do is to call it:
int i = 7; float f = 10.5f; double d = add( i, f ); printf( "%f\n", d );
How do we call it? We follow the name of the function with round brackets ()
and put the list of arguments to the function inside the round brackets. The result of the expression add( i, f )
is the return value and type of the function.
The round brackets are known as the function call operator. Notice that word operator, it’s a word that comes up a lot in C and C++. In general you have something being operated upon (maybe several somethings) and an operator that defines what is happening. For example:
int i = 7; ++i;
We take a thing with a name – i
– of type int
and apply an operator – ++
– to it.
In the case of our function we take a thing with a name – add
– of type unknown (at least at the moment it’s unknown, we’ll get to it soon) and apply an operator – ()
– to it.
There is definitely some common ground between an int and our function. Is there more common ground? C provides us with something else we can do with functions. We can create a function pointer and point it at a function. The syntax is wacky (but if we had a problem with wacky syntax we shouldn’t be programming in C or C++):
double (*pFn)( int, float ) = &add;
To obtain the type of the function pointer we take the function prototype and remove the function name double ( int, float )
, then we add (*)
where the function name used to be to indicate that this is a pointer to a function double (*)( int, float )
. That gives us the type of the function. In order to get a variable of that type we put in the name of the variable next to the (*)
– double (*pFn)( int, float )
.
So we have pFn
– a pointer to a function. We can dereference the pointer and apply the function call operator to it:
int i = 7; float f = 10.5f; double d = (*pFn)( i, f ); printf( "%f\n", d );
Given that it’s a pointer we should be able to change the function that it is pointing at. Here’s an unrealistic but illustrative example:
double mul( int i, float f ) { return i * f; } enum Operator { OPERATOR_ADD, OPERATOR_MUL }; double callOperator( enum Operator op, int i, float f ) { double (*pFn)( int, float ); if( op == OPERATOR_ADD ) { pFn = &add; } else { pFn = &mul; } return (*pFn)( i, f ); }
Since pFn
is a pointer, we might wonder what happens if we set it to NULL, then try to call it:
pFn = NULL; d = (*pFn)( i, f );
As you might expect, calling a NULL function pointer falls squarely in the “don’t do that” category. In fact, it’s like attempting to dereference any other NULL pointer. On Visual Studio we get this:
Unhandled exception at 0x0000000000000000 in Blog.exe: 0xC0000005: Access violation executing location 0x0000000000000000.
On cygwin we get this:
Segmentation fault (core dumped)
We can make life simpler for ourselves in a couple of ways. The C standard allows us to omit the &
(address of) operator when we assign a function to a pointer so instead of:
pFn = &add;
We can write:
pFn = add;
In a similar vein, we can omit the *
(indirection) operator. Instead of:
d = (*pFn)( i, f );
We can write:
d = pFn( i, f );
Finally, we can use typedef
to keep the wacky function pointer type syntax in one place:
typedef double (*FunctionPtr)( int, float ); FunctionPtr pFn = add;
We can now store a pointer to a function in a variable. That means we can save that function and use it later. We can pass the pointer to a function into another function, and return it from a function. Orginally the only thing we could do with our function was to call it (apply the function call operator to it), now we can pass it around in the same way as we pass ints around. This gets us into the world of “first class citizens” or “first class functions”.
There doesn’t appear to be a precise definition for a first class function, but it is safe to assume that if a function can only be called it is not a first class function, the function must be able to be stored and passed around to have any hope of being a first class function. There are a couple of Wikipedia articles with useful information:
That’s it for part #1. We haven’t mentioned std::bind and lambdas yet, in fact we haven’t even made it into the right language. Stay tuned for part #2 where we move on to C++.
One thought on “std::bind and lambda functions 1”