Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

static in lambda



  • Yes, I could doubtless look this up, but I always welcome explanations from the C++ experts here instead :)

    Friend @J-Hilk offered the following lambda over at https://forum.qt.io/topic/123558/how-do-i-know-that-the-client-has-connected-to-the-server/18

                QObject::connect(socket, &QTcpSocket::bytesWritten, socket, [socket, bytesToSend = data.size()](qint64 bytes)->void{
                    static qint64 bytesSend{0};
                    bytesSend += bytes;
                    if(static_cast<qint64>(bytesToSend) == bytesSend)
                        socket->close();
                });
    

    How "safe" is that static qint64 bytesSend{0}; in a lambda? I presume this is correctly supported in C++ as his code intends? "Where" is that static variable stored such that it is "local"/applies only to this lambda? If you were to call this connect code more than once, would it create multiple lambdas, would each one have its own static variable or would they share the same one?


  • Lifetime Qt Champion

    Just try it out and print out the address of the static :)
    I would expect the same rules apply for unnamed and named functions here.



  • @Christian-Ehrlicher
    I don't have access to Qt today, and it might only tell me how gcc implements it. You guys being experts you could just tell me! :)

    I would expect the same rules apply for unnamed and named functions here.

    Then the code looks "naughty" to me! If the static were in a named function it would for sure be shared across multiple instances (sockets), and wouldn't do what @J-Hilk intends, unless it's only called once. Right?



  • @JonB said in static in lambda:

    You guys being experts you could just tell me! :)

    I am not an expert, but I've got issues with static in lambda and MS-VS2013.
    I don't remember exactly the problem, but it was not really working.



  • @KroMignon
    That is interesting. And if true might confirm this could differ between compiler implementations, as I suggested? Or perhaps VS 2013 is too old to be trusted, I don't know.


  • Lifetime Qt Champion

    VS2013 is simply not fully c++11 conform so ... :)


  • Moderators

    @JonB For the purpose of this example think of a lambda as a class with operator(). That static is just a normal static variable in member function scope so it behaves like such. It is accessible as long as instance of that lambda exists so as long as the connection does.

    If you wrote a separate connect with exactly the same lambda it would be a separate static, because each lambda (even if identical) is a separate "class" to the compiler. If you intended to reuse the same static you'd need to assign that lambda to na auto variable and reuse that in both connects.

    @KroMignon Lambdas in VS2013 are buggy and all sorts of non-conformant to the standard. Expect it to misbehave and definitely don't assume anything about the standard based on that compiler.


  • Moderators

    @JonB to be more confusing, you do not have to use a static at all let me introduce you to:

        QCoreApplication app(argc, argv);
    
        auto bytesWritten = [bytesToSend = 64 ](int bytes)mutable ->void{
            bytesToSend -= bytes;
            if(bytesToSend <= 0){
                qDebug() <<"finished";
                qApp->exit();
            }
        };
    
        for( int i{0}; i < 8; i++){
            bytesWritten(8);
        }
        return app.exec();
    


  • @Chris-Kawa
    I don't understand enough of what you are saying, but from reading around I'm beginning to think that a static local variable in a lambda is equivalent to taking it outside the lambda, putting it into the calling function, and passing it by reference. So here:

    static qint64 bytesSend{0};
    QObject::connect(..., [&bytesSend]());
    

    In which case, if this connect() is called more than once they will all share the same bytesSend and code would not behave as intended (which is to count bytes sent on one socket).

    ?



  • @J-Hilk said in static in lambda:

    auto bytesWritten = [bytesToSend = 64 ](int bytes)mutable ->void{

    Same question! Is that bytesToSend a separate variable for each call of this line or the same one? If it's separate, is that really the same as using static, as per my previous post just above? :)


  • Moderators

    @JonB No, that's not how it works. It's closer to:

    class SomeDummyClass
    {
       static qint64 bytesSend;
       operator()(int bytes) { ... }
    } dummy_instance;
    
    connect(..., &dummy_instance, SomeDummyClass::operator())
    

    so this will have separate statics:

    connect(..., []{ static int foo; });
    connect(..., []{ static int foo; });
    

    and this will share one:

    auto func = []{ static int foo; };
    connect(..., func);
    connect(..., func);
    

    @J-Hilk Just to note - this requires C++14 but is a great replacement for such statics.

    @JonB said:

    Is that bytesToSend a separate variable for each call of this line or the same one?

    It's equivalent to defining a non-static member in that class above so it's unique to an instance of the lambda.



  • @Chris-Kawa
    Hmm, OK, then from what you are saying @J-Hilk is good, they will be separate statics....

    But some post I was reading over at stackoverflow was saying something like: "Because the lambda definitions are identical in multiple calls, this means the static variable is as though it lives in the calling function and is passed (by reference) to the lambda", as per my code interpretation above. Which you are saying is not correct....

    So I wonder where the separate instances of the static variable are actually stored, as we need a distinct one for each invocation....?


  • Moderators

    @JonB Well lambdas are their own thing so neither your nor mine is 100% correct, but mine is closer to how it works in terms of encapsulation and lifetime.
    What @J-Hilk wrote is good and it doesn't use statics. A variable defined in the capture clause is a non-static member variable in that dummy class, so something along the lines of:

    class SomeDummyClass
    {
       qint64 bytesSend = 64;
       operator()(int bytes) { ... }
    } dummy_instance;
    

    The statics are part of that dummy class, so follow the rules of the usual static class members. A lambda defined inline in the connect is just instantiating that dummy class, so like someFunction(QString()) - creating local instance in place. The difference is that lambdas don't have user visible type, so the DummyClass is "private" and exposed only internally to the compiler, so while this:

    someFunc(QString());
    someFunc(QString());
    

    would share a static between different QString instances does not apply to lambdas, becase this:

    someFunc([]{});
    someFunc([]{});
    

    is to the compiler this:

    someFunc(SomeDummyClass1());
    someFunc(SomeDummyClass2());
    

    so each lambda is its own separate class.



  • @Chris-Kawa said in static in lambda:

    What @J-Hilk wrote is good and it doesn't use statics.

    I was (originally) asking about his original code, which does use the static.

    so each lambda is its own separate class.

    I don't doubt that you are correct, the stackoverflow post was claiming that because the lambdas were identical (being invoked from the same line of code) this made them share the same, one class, like

    someFunc(SomeDummyClass1());
    someFunc(SomeDummyClass1());
    

    Maybe I misunderstood what I read there!


  • Moderators

    @JonB Maybe they meant the case with lambda reuse via variable, like I posted above. This

    someFunc(SomeDummyClass1());
    someFunc(SomeDummyClass1());
    

    is simply not possible because you don't have access to the class, so you can't reuse it. You can only reuse an instance of that class (i.e. the auto variable). Think of lambdas as class that can only be instantiated once.

    It's to the point that this won't work:

    auto foo = []{};
    auto bar = decltype(foo)();
    

    i.e. you can't try to query lambdas type and then create another instance of it.



  • @Chris-Kawa
    Would you care to have a look at https://stackoverflow.com/a/60592393/489865. The interesting bit (excerpt):

    void bar() {
       auto f = []() { static int k = 1; cout << k++ << "\n";}; // define k as static
       f();
       f();
    }
    
    void test() {
       bar();
       bar();  // k is persistent through lambda instantiations
       return 0;
    }
    

    Is this correct? Am I misunderstanding, or does this mean one instance of static int k shared across multiple invocations? Wouldn't that make @J-Hilk's original code (at potentially) wrong, as separate sockets would all shre the same bytes-written count? Or, is this behaviour to do with the fact that in this example the lambda is stored in a variable auto f, and that makes it different from him passing it inline to connect()?

    My brain hurts! This was suppsoed to eb a quick question, I'm spending all morning discussing it :)


  • Moderators

    @JonB Yeah, I guess I went too far in my class analogy :) That's because lambdas are a little different and it's not exactly that there is only one instance of it. It's more that the compiler is the only one allowed to name its type, not user in their code. So you're right - this example will share the static because the type of the lambda is the same in this case. Note however that it's not the same as

    auto f = []() { static int k = 1; cout << k++ << "\n";}; // define k as static
    f();
    auto f2 = []() { static int k = 1; cout << k++ << "\n";}; // define k as static
    f2();
    

    these lambdas, although look the same are of different types internally.



  • @Chris-Kawa said in static in lambda:

    these lambdas, although look the same are of different types internally.

    Yes, that I do realise! Just as in C/C++ struct { int foo; } and another struct { int foo; } are not the same type. (But I think they are in Pascal-like languages....)

    So you're right - this example will share the static because the type of the lambda is the same in this case

    Which I think would make @J-Hilk's original not behave as anticipated if more than connect() is made! :)


Log in to reply