4

I'm having a trouble finding a definitive answer if the following code is correct in both cases, with non-capturing lambda and with call of a static function. I'm pretty sure the latter is fine, what I'm concerned about is the lifetime of a lambda when "defined" inside another block/function call, etc. The code seems to work fine but I'd still prefer to know why... Does the non-capturing lambda end up defined as some kind of anonymous (hidden) global symbol so it's basically the same as that static function?

#include <cstdio>
class Listener {
public:
    void event() { printf("event() called\n"); }

    static void forwardEvent(void* userArg) { static_cast<Listener*>(userArg)->event(); }
};

class Producer {
    using Listener = void(void* arg);
    Listener* listener_;
    void* userArg_;

public:
    void attachListener(Listener* listener, void* userArg) {
        listener_ = listener;
        userArg_ = userArg;
    }

    void issueEvent() { listener_(userArg_); }
};

int main() {
    Listener listener;
    Producer producer;

    {  // different scope, in case that makes a difference...
        producer.attachListener(Listener::forwardEvent, &listener);
    }
    producer.issueEvent();

    {  // different scope, in case that makes a difference...
        producer.attachListener([](void* userArg) { static_cast<Listener*>(userArg)->event(); }, &listener);
    }
    producer.issueEvent();
}
vaind
  • 1,642
  • 10
  • 19
  • @TonyTannous Not entirely sure that's a dupe. OP is specifically asking about *non-capturing* lambdas. (there's probably a dupe for that anyway). – cigien Oct 01 '20 at 16:27
  • 2
    Does this answer your question? [Lifetime of lambda objects in relation to function pointer conversion](https://stackoverflow.com/questions/8026170/lifetime-of-lambda-objects-in-relation-to-function-pointer-conversion) – Artyer Oct 01 '20 at 17:02

1 Answers1

4

Okay, so your nested Listener definition is this.

using Listener = void(void* arg);
Listener* listener_;

Basically, you deal with good old function pointers. Now when you pass an argument for the function pointer, you do this

producer.attachListener([](void* userArg) { static_cast<Listener*>(userArg)->event(); }, &listener);

The non-capturing lambda is converted to a function pointer via an implicit conversion. This function pointer is

  1. guaranteed to be valid, and points at a function that
  2. behaves exactly the same as if the lambda's operator() was called.

Functions "exist" for the entire duration of the program, and their addresses never become dangling. So your code has well-defined behavior as far as using the pointer obtained from the lambda expression is concerned.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • 1
    As an aside, having two "Listener" types in the code sample is a bit confusing. I would suggest calling the nested type something else. – StoryTeller - Unslander Monica Oct 01 '20 at 16:54
  • > Functions "exist" for the entire duration of the program, and their addresses never become dangling. That's what I wanted to hear, thanks – vaind Oct 02 '20 at 11:13