If lambdas don't have a specified type, how does std::function accept a lambda?

Viewed 435

Can anyone explain how lambda functions are represented in std::function? Is there an implicit conversion by the compiler and std::function used as a container?

I recently asked a slightly different question, which was marked as a duplicate of this question. The answer is the type of a lambda function is not defined and unspecified. I have found some code that appears to provide a container for the lambda function as follows below. I have also included a Stroustrup quote, which seems to contradict that lambda functions do not have a type defined, saying however it is a function closure type. This is only confusing the matter further.

Update: Partial answer regarding implementation of function<> is here.

Appreciate your guidance.

#include <iostream>
#include <vector>
#include <functional>
using namespace std;

static vector<function<void(int)>> cbl;
static vector<function<int(int)>> cblr;

class GT {
public:
    int operator()(int x) {return x;}
};

void f()
{
    auto op = [](int x) {cout << x << endl;}; 
    cbl.push_back(op);

    for (auto& p : cbl)
        p(1);

    auto op2 = [](int x) {return x;};
    cblr.push_back(op2);
    cblr.push_back(GT());
    for (auto& p : cblr)
        cout << p(99) << endl;
}

int main(int argc, char *argv[])
{
    f();
    return 0;
}

Compilation and result:

g++ -pedantic -Wall test130.cc && ./a.out
1
99
99

Stroustrup talks about this in C++ 4th Ed Page 297:

To allow for optimized versions of lambda expressions, the type of a lambda expression is not defined. However, it is defined to be the type of a function object in the style presented in §11.4.1. This type, called the closure type, is unique to the lambda, so no two lambdas have the same type. Had two lambdas had the same type, the template instantiation mechanism might have gotten con- fused. A lambda is of a local class type with a constructor and a const member function operator()().

4 Answers

The type is there. It’s just that you don’t know in advance what it is. Lambdas have type - just the standard says nothing about what that type is; it only gives the contracts that type has to fulfill. It’s up to the compiler implementers to decide what that type really is. And they don’t have to tell you. It’s not useful to know.

So you can deal with it just like you would deal with storage of any “generic” type. Namely: provide suitably aligned storage, then use placement new to copy-construct or move-construct the object in that storage. None of it is specific to std::function. If your job was to write a class that can store an arbitrary type, you’d do just that. And it’s what std::function has to do as well.

std::function implementers usually employ the small-object optimization. The class leaves some unused room in its body. If the object to be stored is of an alignment for which the empty room is suitable, and if it will fit in that unused room, then the storage will come from within the std::function object itself. Otherwise, it’ll have to dynamically allocate the memory for it. That means that e.g. capture of intrinsic vector types (AVX, Neon, etc) - if such is possible - will usually make a lambda unfit for small object optimization storage within std::function.

I'm making no claims as to whether or if the capture of intrinsic vector types is allowed, fruitful, sensible, or even possible. It's sometimes a minefield of corner cases and subtle platform bugs. I don't suggest anyone go and do it without full understanding of what's going on, and the ability to audit the resulting assembly as/when needed (implication: under pressure, typically at 4AM on the demo day, with the suits about to wake up soon - and already irked that they have to interrupt their golf play so early in the day just to watch the presenter sweat).

There is a difference between "does not have a type" and "type is unspecified". Unspecified means that it exists, but you don't get to know what it is. That is, it doesn't have a type name you can key in, but it does have a type.

op in your example is a variable with a type. You don't know what valid combination of letters would identify that type's name (and indeed, no valid combination of letters will identify that type's name). But the type can be computed via decltype(op).

A lambda has a type. You can write a function template that takes a lambda and calls it:

template<typename T>
class F{
    T t;
public:
    F(const T &lambda):t(lambda){}
    void call() {t();}
};

That's a very basic approach for std::function

"Is there an implicit conversion by the compiler and std::function used as a container?" No, you don't need to convert them.

You keep repeatedly claiming that lambdas "don't have a type", but that's not what "unspecified" means, nor what anybody said it means on the last three questions.

Lambdas have a type, you just don't know the types' names.

The standard doesn't specify the name of the type of any lambda. If such a type even has a name, the internal workings of the compiler know it, typeid().name() may reveal it, and error messages often reveal it.

But you don't, and cannot, know in advance what the name of the type of any given lambda is going to be. That is by design. So you cannot write that type out in your source code. That's fine; why would you want to?

Templates and auto don't need the name, because that's how templates work. The inner workings of std::function don't need to know the name of any lambda type; they just need to know whether the expression thing(args) is valid.

Related