5

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()().

12
  • 2
    related, maybe dupe: stackoverflow.com/questions/18453145/… Commented Jun 11, 2020 at 19:39
  • 2
    Compilers know the type of a lambda. Commented Jun 11, 2020 at 19:39
  • 5
    "which seems to contradict that lambda functions don't have a type" - You still have not let go of the misconception. Lambda's have a type. Every object has a type. Commented Jun 11, 2020 at 19:40
  • 5
    Following up StoryTeller's comment, a lambda has an unspecified class type. That doesn't mean it doesn't have a type, it's just that we don't know the name of it. We do know it is a class, so it a functor Commented Jun 11, 2020 at 19:42
  • 2
    Correct. Just like 5 has type int, a lambda expression has its own type. But unlike 5, the C++ standard doesn't tell you what the type of the lambda should be called or the exact way compilers should implement it. That's what is unspecified. Commented Jun 11, 2020 at 19:43

4 Answers 4

9

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).

Sign up to request clarification or add additional context in comments.

2 Comments

How do you invoke operator() on that void*? Wrap the object in an internal polymorphic type which has a virtual operator()? Genuinely asking. I always wondered how std::function deals with type-erasue.
It deals with it by not doing it. I mean: seriously. There are no calls via void*. The type of the lambda is available. You can generate whatever helper code you need to actually do the call: the code that does a reinterpret_cast from void* to the type of pointer-to-lambda, then dereferences that pointer, and calls the reference thus obtained. That code can be a stateless function. So just save a pointer to that. Easy. The type is never lost at all. There is code that knows the type and does the call on a typed object. Just as with new, the storage can be untyped: no erasure!
9

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).

3 Comments

FWIW, the standard doesn't say that there must be no name for the unspecified type. And, indeed, in practice, they do tend to get names (which are observable through error messages, naturally beginning with nice underscores to keep them in their reservations - granted I never tested whether these can be used in code if you guess them right). But indeed it cannot be guaranteed that they will have a name.
@AsteroidsWithWings: "I never tested whether these can be used in code if you guess them right" You can't use them. That's the whole point of the double-underscore rule: they are reserved for the implementation, and any use of such an identifier by your program represents UB.
Sorry, to be clearer, I didn't mean "legally" in the sense of reserved names, but "without breaking the build". That is, are the names exposed in such a way that they can be spelt in the code, or are their presence in error messages just an anomalous manifestation (or even a deliberate exception)? If the latter, one could then still argue that indeed the types don't have names in any meaningful sense, despite the error messages appearing to prove otherwise.
8

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.

3 Comments

Going through answer one by one. I tried using this as follows const auto op3 = []() {cout << "A" << endl;}; F f{op3};. I get missing template arguments before ‘f’ F f{op3};. So perhaps the typename isn't being deduced automatically?
@notaorb It works for me wandbox.org/permlink/GVJpVdXo8qTblcNE
Thanks. g++ required -std=c++17
1

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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.