Pointer to function, function types, lambdas and std::function
C++ offer great flexibility regarding how to declare and use functions.
It inherits all that plain C can do and offers nice, extra features in the language and in the std lib.
Pointers
pointers can store addresses, and functions has addresses as well. Therefore, this is valid C++ code:
#ifndef POINTER_TO_FUNCTION_CC
#define POINTER_TO_FUNCTION_CC
#include <iostream>
namespace ptf
{
void duplicator(int &n)
{
n *= 2;
}
void incrementor(int &n)
{
n += 1;
}
void applier(int *v, int vSize, void (*f)(int &))
{
for (int i = 0; i < vSize; i++)
{
f(v[i]);
}
}
void printer(int *v, int vSize)
{
for (int i = 0; i < vSize; i++)
{
std::cout << v[i] << " ";
}
std::cout << std::endl;
}
};
#endif // POINTER_TO_FUNCTION_CCThe void (*f)(int &) might look like a bit clumsy, but the parenthesis around *f make sure that you ton't mistype the return type (for example int* instead of int).
Function typedefs
Optionally, you can write a typedef to make it easier to the eye:
#ifndef FUNCTION_TYPEDEF_CC
#define FUNCTION_TYPEDEF_CC
namespace ptf
{
typedef void (*func_t)(int &);
};
#endif // FUNCTION_TYPEDEF_CCIt's possible now to write something like ptf::func_t f = ptf::duplicator.
Lambdas
Pass functions around for later use deciding on the context is nice, however the context associated with it is limited.
For instance, you are constrained by the function contract (how many parameters, their types and return type) and this is all you get available when the time comes.
This is also why the opaque pointer parameter is ofter present in libraries consuming pointer to functions.
On the other hand, lambdas can carry references and values from the context they came from:
void main()
{
std::cout << "lambda" << std::endl;
int x = 0;
auto sample = [&x](int &n)
{
n -= 1;
x += n;
};
ptf::applier2(v, 5, sample);
}Lambda has these key parts:
- type: the variable type for this lambda.
autois used in this specific example, but there are other possibilities. - name: variable name, how you will refer to this lambda. it is
samplein this example. - context capture: this is where lambda shines. set the shared context between the current function and the lambda being declared. it is the
[&x]. You can define the capture in many ways, by value, by reference, pass nothing at all, you call it. - parameter list: like a regular function, the parameter list the lambda will receive when it gets called. it' the
(int &n)part. - lambda's body: function body to be executed.
It might look ugly or too similar to a regular function, but in fact it has more similarities with classes than with functions.
Thanks to the capture group, lambdas can carry state.
std::function
A side effect of this is that lambda signatures, when have a non-empty capture group, doesn't fit well in regular function pointers, leading to unexpected behavior and compile errors.
To solve that, functions consuming lambdas must adapt:
#include <iostream>
#include <functional>
namespace ptf
{
void applier2(int *v, int vSize, std::function<void(int &)> f)
{
for (int i = 0; i < vSize; i++)
{
f(v[i]);
}
}
};You can check the usage of each approach in the main.cc:
#include <iostream>
#include "pointer-to-function.cc"
#include "function-typedef.cc"
#include "std-function-printer.cc"
int main(int argc, char *argv[])
{
// our sample data
int v[] = {1, 2, 3, 4, 5};
// pointer to function
std::cout << "pointer to function:" << std::endl;
ptf::applier(v, 5, ptf::duplicator);
ptf::applier(v, 5, ptf::incrementor);
ptf::printer(v, 5);
std::cout << std::endl;
// function typedefs
std::cout << "function typedef:" << std::endl;
ptf::func_t f[] = {ptf::duplicator, ptf::incrementor};
for (int i = 0; i < 2; i++)
{
ptf::applier(v, 5, f[i]);
}
ptf::printer(v, 5);
std::cout << std::endl;
// lambdas
std::cout << "lambda" << std::endl;
int x = 0;
auto sample = [&x](int &n)
{
n -= 1;
x += n;
};
ptf::applier2(v, 5, sample);
ptf::printer(v, 5);
std::cout << "x = " << x << std::endl;
return 0;
}How to run
Simply compile and run the entrypoint:
g++ -Wall main.cc -o main
./mainNoteworthy
- Technically speaking, class methods and lambdas are equivalent, except that lambdas are a lightweight syntax to get the same result: custom context for a function.
- The example above includes source files instead of headers and also compiles one single entrypoint. It's called jubmo build.