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

notaorb
  • 1,944
  • 1
  • 8
  • 18
  • 2
    related, maybe dupe: https://stackoverflow.com/questions/18453145/how-is-stdfunction-implemented – NathanOliver Jun 11 '20 at 19:39
  • 2
    Compilers know the type of a lambda. – Taekahn Jun 11 '20 at 19:39
  • Here is another: https://stackoverflow.com/questions/14936539/how-stdfunction-works – NathanOliver Jun 11 '20 at 19:40
  • 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. – StoryTeller - Unslander Monica Jun 11 '20 at 19:40
  • @StoryTeller-UnslanderMonica so lambda's have a type, but it is not defined or specified? – notaorb Jun 11 '20 at 19:41
  • 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 – NathanOliver Jun 11 '20 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. – StoryTeller - Unslander Monica Jun 11 '20 at 19:43
  • 1
    How many times, on how many questions, are we going to have to say the same thing to you? – Asteroids With Wings Jun 11 '20 at 19:51
  • @AsteroidsWithWings have you answered the question? "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?" – notaorb Jun 11 '20 at 19:53
  • 1
    There are 4 correct answers to the question. Read them and accept one of them. It's possible that you don't like the answers because you claim that lambdas have no type and all answers say that lambdas have a type. – Thomas Sablik Jun 11 '20 at 20:02
  • 1
    @notaorb Yes, several of us spent some time on your previous, now-deleted question, explaining it to you. Your responses to those teachings (including where me and at least one other person directly answered the question you just quoted) seemed to indicate that you understood them. – Asteroids With Wings Jun 11 '20 at 20:09
  • @ThomasSablik I'm clear that lambdas have a type and that it is not defined. I have not questioned that. I was just unclear on Stroustrup's quote where he claims that then says "it is defined to be the type of a function object". Thanks again – notaorb Jun 11 '20 at 20:32

4 Answers4

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

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • 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. – Sebastian Hoffmann Jun 11 '20 at 20:12
  • 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! – Kuba hasn't forgotten Monica Jun 11 '20 at 20:17
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).

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • 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. – Asteroids With Wings Jun 11 '20 at 19:50
  • @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. – Nicol Bolas Jun 11 '20 at 20:30
  • 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. – Asteroids With Wings Jun 11 '20 at 20:32
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.

Thomas Sablik
  • 16,127
  • 7
  • 34
  • 62
  • 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 Jun 11 '20 at 20:23
  • @notaorb It works for me https://wandbox.org/permlink/GVJpVdXo8qTblcNE – Thomas Sablik Jun 11 '20 at 22:16
  • Thanks. g++ required -std=c++17 – notaorb Jun 11 '20 at 22:27
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.

Asteroids With Wings
  • 17,071
  • 2
  • 21
  • 35