Signature deduction for lambdas and automatic conversion to std::function<>

Lambdas are not function objects

Lamb­das (or lamb­da expres­sions) in C++11 are a bless­ing for C++ pro­gram­mers that have been exposed to func­tion­al pro­gram­ming. And, in con­junc­tion with the extend­ed capa­bil­i­ties of tem­plates, they can bring the abstrac­tion lev­el of soft­ware to a whole new lev­el. Yet, as often in C++, lan­guage fea­tures aren’t dri­ven only by expres­sive­ness and gen­er­al­i­sa­tion, and we’re left with some minus­cule stones in our shoes that end up ruin­ing the whole walk.

My intol­er­a­ble aggra­va­tion here is that lamb­das are not func­tion objects, i.e. std::function<R (Args...)> as defined by stan­dard library head­er functional. And this is a prob­lem because the way func­tion objects are tem­plat­ed makes it very easy to meta-pro­gram with them. Where­as lamb­das are almost opaque. It would be per­fect­ly OK for me to auto­mat­i­cal­ly con­vert lamb­das to func­tion objects all the time, since when i’m cod­ing at this lev­el of abstract­ness, effi­cien­cy is very far from being an impor­tant con­sid­er­a­tion. But, alas, i know of no such fea­ture in the lan­guage or stan­dard library.

So what?

So if only i had some­thing like

template <typename LambdaT>
function<typename LambdaT::signature>
to_function(LambdaT lambda);

then, every time i have a func­tion that has an STL function argu­ment, like for instance

// vectorize function object
template <typename R, typename... A>
function<vector<R> (vector<A>...)>
vectorize(function<R (A...)> f);

i would non­cha­lant­ly tack on

// vectorize a lambda
template <typename LambdaT>
auto vectorize(LambdaT f) -> decltype(vectorize(to_function(f)))
  { return vectorize(to_function(f)); }

and relax. The first ver­sion of my func­tion would do all the type deduc­ing; the sec­ond would deal with the very com­mon case of using a lamb­da expres­sion instead of a func­tion object.

Why would you want to do that anyway?

Because func­tion­al pro­gram­ming is pow­er­ful. Using func­tion­al pro­gram­ming, some prob­lems can be struc­tured into sim­ple func­tions and func­tion manip­u­la­tions (func­tion func­tions, oper­a­tions with func­tions, func­tion trans­for­ma­tions…), which real­ly, real­ly, reduces the amount of code, and allows for a deep­er under­stand­ing and struc­tur­ing of the prob­lem, one that will have more of the good fea­tures we want our code to have (clear­ness, cor­rect­ness, testa­bil­i­ty, main­tain­abil­i­ty, reusabil­i­ty).

Allrighty, but what would you do with that?

The exam­ple above solves a basic func­tion­al prob­lem: giv­en a func­tion object f that has some argu­ments and returns a result, how can i get the equiv­a­lent func­tion that does the same with match­ing ele­ments of std::vector argu­ments and returns a std::vector of results? Specif­i­cal­ly, i would like the expres­sion vectorize(f) to return a func­tion, let’s call it vf, such that the pseu­do-expres­sion

vf(vector{a1, a2, ...}, vector{b1, b2, ...}, ...)

returns the same val­ue as

vector{f(a1, b1, ...), f(a2, b2, ...), ...}

This is how it’s done:

template <typename R, typename... A>
function<vector<R> (vector<A>...)>
vectorize(function<R (A...)> f) {
  return [f](vector<A> &&...la) {
    vector<R> result;
    for (size_t i=0; i<common_size(la...); ++i)
      result.push_back(f(la[i]...));
    return result;
  };
}

Using the explic­it tem­plati­sa­tion of the function<R (A...)> argu­ment, we deduce its return and argu­ment types, of which there can be any num­ber thanks to the vari­adic tem­plate, and then, judi­cious­ly sprin­kling our code with trios of peri­ods, we can turn our vari­adic deduc­tions into gen­er­al recipes to achieve our goal.

The only thing that remains to be done in the code above is to define the func­tion common_size(), which returns the com­mon size of its vec­tor argu­ments or, if they are not all the same size, just throws an excep­tion (a plain string here, to keep it sim­ple):

// handle single argument
template <typename Arg>
size_t common_size(Arg &&a) { return a.size(); }

// recursively handle more than one argument
template <typename Arg, typename... Rest>
size_t common_size(Arg &&a, Rest &&... rest) {
  if (a.size()==common_size(rest...))
    return a.size();
  else
    throw "unequal sizes";
}

Here’s a sim­ple exam­ple of usage for vectorize():

auto div=[](int x, float y) { return x/y; };
vector<int> a{1, 2, 3};
vector<float> b{2., 4., 5.};
auto r=vectorize(div)(a, b);

which should give r the val­ue

vector<float>{0.5 0.5 0.6}

… except it doesn’t cus it doesn’t even com­pile. Because “Lamb­das are not func­tion objects”. Instead, we have to use this unwon­der­ful, over-explic­it syn­tax, where we tell the com­pil­er things he already knows, but some­how can’t put to use by him­self (i resort to anthro­po­mor­phi­sa­tion to relieve my frus­tra­tion):

auto r=vectorize(function<float (int, float)>(div))(a, b);

There’s a way

Yes, there’s a way to keep the first won­der­ful, terse syn­tax, if we code the sec­ond ver­sion of vectorize(), the one for lamb­das, that con­verts to function and del­e­gates to the first ver­sion. We just need to write to_function().

And there’s a way to write to_function(), because lamb­das are not so opaque after all.

The type of a lamb­da is com­pil­er-depen­dent, but it must com­ply with this require­ment: it must have an operator()() (a func­tion call oper­a­tor if you’re read­ing aloud), match­ing its sig­na­ture, of course. And although you can’t direct­ly access its sig­na­ture, you can get its type (as a method point­er) using the decltype spec­i­fi­er. If you have a lamb­da with type LambdaT, the type of its operator()() is giv­en by

decltype(&LambdaT::operator())

and it’s some­thing you can match in a tem­plate with the gener­ic type

Ret (Obj::*)(Args...)

where Ret, Obj, and Args... are tem­plate argu­ments, used to deduce the sig­na­ture with a lev­el of indi­rec­tion.

So, if you have the type of a lamb­da (or, for that mat­ter, any oth­er object that sup­ports operator()(), as should be clear by now), you can deduce its sig­na­ture and pro­duce the type of the equiv­a­lent function object like this (the def­i­n­i­tions are out of order to aid the expo­si­tion):

template <typename LambdaT>
struct as_function
  : public method_as_function<decltype(&LambdaT::operator())> { };

template <typename T>
struct method_as_function;
template <typename Obj, typename Ret, typename... Args>
struct method_as_function<Ret (Obj::*)(Args...) const>
  { type=std::function<Ret (Args...)>; };

And using these def­i­n­i­tions, to_function() is sim­ple and beau­ti­ful:

template <typename F>
typename as_function<F>::type to_function(F &&f)
  { return f; }

So that’s the heart of the tech­nique i want­ed to demon­strate. Put the def­i­n­i­tions in the right order, add some bells and whis­tles, and you have a small library to con­vert lamb­das and oth­er func­tion-like objects into function objects, for easy func­tion­al meta-pro­gram­ming.

The code and the test

So here’s a small com­plete head­er that imple­ments the ideas explained above, with some extra fea­tures like han­dling raw func­tion point­ers, ref­er­ences to lamb­da objects, and a _t<> com­pan­ion to as_function<>, à la C++14 STL.

And here’s a test:

The test should print the fol­low­ing: