Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Is it possible QMetaObject::invokeMethod with non-copyable argument?
Qt 6.12 LTS is out! See what's new in the release blog

Is it possible QMetaObject::invokeMethod with non-copyable argument?

Scheduled Pinned Locked Moved Solved General and Desktop
5 Posts 3 Posters 993 Views 2 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • T Offline
    T Offline
    Tarae
    wrote on last edited by
    #1

    I know you can't use signals and slots with non-copyable arguments. One signal can be connected to multiple slots, so it must be possible to copy arguments to every slot. But invokeMethod? I can't see any reason for it to not work. Yet I can't figure it out. Example:
    Simple class inheriting QObject :

    class Bar : public QObject
    {
      Q_OBJECT
    public:
      void foo1() { qDebug() << "foo1"; }
      void foo2(int i) { qDebug() << "foo2 i" << i;}
      void foo3(std::unique_ptr<int> i) {qDebug() << "foo3 s.i:" << *(i.get()); }
    };
    

    Let's create an instance of this class and move it to another thread:

    Bar * bar = new Bar();  
    QThread barThread;
    bar->moveToThread(&barThread);
    barThread.start();
    

    Now, I want to call foo1, foo2 and foo3 in this new thread. Using QMetaObject::invokeMethod it's easy to call foo1 and foo2:

    QMetaObject::invokeMethod(bar, &Bar::foo1);
    
    int i2 = 7;
    QMetaObject::invokeMethod(bar, [bar, i2](){bar->foo2(i2);});
    

    But when I try to call foo3, the program doesn't compile:

    auto i3 = std::make_unique<int>(3);
    QMetaObject::invokeMethod(bar, [bar, i3](){bar->foo3(i3);});
    

    It's because unique_ptr is not copyable. So I use std::move:

    QMetaObject::invokeMethod(bar, [c, i = std::move(i3)](){bar->foo3(std::move(i));});
    

    Still doesn't compile. Am I doing something wrong or is it just not possible to use QMetaObject::invokeMethod with non-copyable argument?

    SGaistS Chris KawaC 2 Replies Last reply
    0
    • T Tarae

      I know you can't use signals and slots with non-copyable arguments. One signal can be connected to multiple slots, so it must be possible to copy arguments to every slot. But invokeMethod? I can't see any reason for it to not work. Yet I can't figure it out. Example:
      Simple class inheriting QObject :

      class Bar : public QObject
      {
        Q_OBJECT
      public:
        void foo1() { qDebug() << "foo1"; }
        void foo2(int i) { qDebug() << "foo2 i" << i;}
        void foo3(std::unique_ptr<int> i) {qDebug() << "foo3 s.i:" << *(i.get()); }
      };
      

      Let's create an instance of this class and move it to another thread:

      Bar * bar = new Bar();  
      QThread barThread;
      bar->moveToThread(&barThread);
      barThread.start();
      

      Now, I want to call foo1, foo2 and foo3 in this new thread. Using QMetaObject::invokeMethod it's easy to call foo1 and foo2:

      QMetaObject::invokeMethod(bar, &Bar::foo1);
      
      int i2 = 7;
      QMetaObject::invokeMethod(bar, [bar, i2](){bar->foo2(i2);});
      

      But when I try to call foo3, the program doesn't compile:

      auto i3 = std::make_unique<int>(3);
      QMetaObject::invokeMethod(bar, [bar, i3](){bar->foo3(i3);});
      

      It's because unique_ptr is not copyable. So I use std::move:

      QMetaObject::invokeMethod(bar, [c, i = std::move(i3)](){bar->foo3(std::move(i));});
      

      Still doesn't compile. Am I doing something wrong or is it just not possible to use QMetaObject::invokeMethod with non-copyable argument?

      Chris KawaC Offline
      Chris KawaC Offline
      Chris Kawa
      Lifetime Qt Champion
      wrote on last edited by Chris Kawa
      #3

      @Tarae Your example does not show a problem with invokeMethod but with your usage of lambda. Even without invokeMethod this won't work, because you're trying to capture i3 by copy and unique_ptr is not copyable:

      auto i3 = std::make_unique<int>(3);
      auto lambda  = [bar, i3](){bar->foo3(i3);} // nope, can't do
      

      You need to move the parameter into a capture variable:

      auto lambda  = [bar, i3cp = std::move(i3)](){bar->foo3(i3cp);}
      

      but this won't compile either, because your function takes a parameter by value, so there's another copy there. You have to move your parameter again:

      auto i3 = std::make_unique<int>(3);
      auto lambda  = [bar, i3cp = std::move(i3)](){bar->foo3(std::move(i3cp));}
      

      but then you have another problem, because lambdas are constant by default i.e. its captures are const too and not movable, so you have to make your lambda mutable:

      auto lambda  = [bar, i3cp = std::move(i3)]() mutable { bar->foo3(std::move(i3cp)); }
      

      This will work. Notice, that this lambda is non-copyable, because it captures a unique_ptr, which itself is non-copyable, yet you can still use that lambda in invokeMethod. That's because invokeMethod will move it if it needs to (e.g. when scheduled on another thread's queue).

      But all in all moving unique_ptrs around like this is kinda shady. It's hard to keep track of what's where and who holds it. I gave you a solution, but I would advise against using it.

      T 1 Reply Last reply
      1
      • T Tarae

        I know you can't use signals and slots with non-copyable arguments. One signal can be connected to multiple slots, so it must be possible to copy arguments to every slot. But invokeMethod? I can't see any reason for it to not work. Yet I can't figure it out. Example:
        Simple class inheriting QObject :

        class Bar : public QObject
        {
          Q_OBJECT
        public:
          void foo1() { qDebug() << "foo1"; }
          void foo2(int i) { qDebug() << "foo2 i" << i;}
          void foo3(std::unique_ptr<int> i) {qDebug() << "foo3 s.i:" << *(i.get()); }
        };
        

        Let's create an instance of this class and move it to another thread:

        Bar * bar = new Bar();  
        QThread barThread;
        bar->moveToThread(&barThread);
        barThread.start();
        

        Now, I want to call foo1, foo2 and foo3 in this new thread. Using QMetaObject::invokeMethod it's easy to call foo1 and foo2:

        QMetaObject::invokeMethod(bar, &Bar::foo1);
        
        int i2 = 7;
        QMetaObject::invokeMethod(bar, [bar, i2](){bar->foo2(i2);});
        

        But when I try to call foo3, the program doesn't compile:

        auto i3 = std::make_unique<int>(3);
        QMetaObject::invokeMethod(bar, [bar, i3](){bar->foo3(i3);});
        

        It's because unique_ptr is not copyable. So I use std::move:

        QMetaObject::invokeMethod(bar, [c, i = std::move(i3)](){bar->foo3(std::move(i));});
        

        Still doesn't compile. Am I doing something wrong or is it just not possible to use QMetaObject::invokeMethod with non-copyable argument?

        SGaistS Offline
        SGaistS Offline
        SGaist
        Lifetime Qt Champion
        wrote on last edited by SGaist
        #2

        Hi,

        AFAIK, I don't think you can move around unique_ptr with invokeMethod.
        See @Chris-Kawa's excellent answer.

        That noted, all your examples with unique_ptr looks like you are trying to reproduce with a lambda what invokeMethod already does.

        Interested in AI ? www.idiap.ch
        Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

        1 Reply Last reply
        0
        • T Tarae

          I know you can't use signals and slots with non-copyable arguments. One signal can be connected to multiple slots, so it must be possible to copy arguments to every slot. But invokeMethod? I can't see any reason for it to not work. Yet I can't figure it out. Example:
          Simple class inheriting QObject :

          class Bar : public QObject
          {
            Q_OBJECT
          public:
            void foo1() { qDebug() << "foo1"; }
            void foo2(int i) { qDebug() << "foo2 i" << i;}
            void foo3(std::unique_ptr<int> i) {qDebug() << "foo3 s.i:" << *(i.get()); }
          };
          

          Let's create an instance of this class and move it to another thread:

          Bar * bar = new Bar();  
          QThread barThread;
          bar->moveToThread(&barThread);
          barThread.start();
          

          Now, I want to call foo1, foo2 and foo3 in this new thread. Using QMetaObject::invokeMethod it's easy to call foo1 and foo2:

          QMetaObject::invokeMethod(bar, &Bar::foo1);
          
          int i2 = 7;
          QMetaObject::invokeMethod(bar, [bar, i2](){bar->foo2(i2);});
          

          But when I try to call foo3, the program doesn't compile:

          auto i3 = std::make_unique<int>(3);
          QMetaObject::invokeMethod(bar, [bar, i3](){bar->foo3(i3);});
          

          It's because unique_ptr is not copyable. So I use std::move:

          QMetaObject::invokeMethod(bar, [c, i = std::move(i3)](){bar->foo3(std::move(i));});
          

          Still doesn't compile. Am I doing something wrong or is it just not possible to use QMetaObject::invokeMethod with non-copyable argument?

          Chris KawaC Offline
          Chris KawaC Offline
          Chris Kawa
          Lifetime Qt Champion
          wrote on last edited by Chris Kawa
          #3

          @Tarae Your example does not show a problem with invokeMethod but with your usage of lambda. Even without invokeMethod this won't work, because you're trying to capture i3 by copy and unique_ptr is not copyable:

          auto i3 = std::make_unique<int>(3);
          auto lambda  = [bar, i3](){bar->foo3(i3);} // nope, can't do
          

          You need to move the parameter into a capture variable:

          auto lambda  = [bar, i3cp = std::move(i3)](){bar->foo3(i3cp);}
          

          but this won't compile either, because your function takes a parameter by value, so there's another copy there. You have to move your parameter again:

          auto i3 = std::make_unique<int>(3);
          auto lambda  = [bar, i3cp = std::move(i3)](){bar->foo3(std::move(i3cp));}
          

          but then you have another problem, because lambdas are constant by default i.e. its captures are const too and not movable, so you have to make your lambda mutable:

          auto lambda  = [bar, i3cp = std::move(i3)]() mutable { bar->foo3(std::move(i3cp)); }
          

          This will work. Notice, that this lambda is non-copyable, because it captures a unique_ptr, which itself is non-copyable, yet you can still use that lambda in invokeMethod. That's because invokeMethod will move it if it needs to (e.g. when scheduled on another thread's queue).

          But all in all moving unique_ptrs around like this is kinda shady. It's hard to keep track of what's where and who holds it. I gave you a solution, but I would advise against using it.

          T 1 Reply Last reply
          1
          • T Offline
            T Offline
            Tarae
            wrote on last edited by
            #4
            This post is deleted!
            1 Reply Last reply
            0
            • T Tarae has marked this topic as solved on
            • Chris KawaC Chris Kawa

              @Tarae Your example does not show a problem with invokeMethod but with your usage of lambda. Even without invokeMethod this won't work, because you're trying to capture i3 by copy and unique_ptr is not copyable:

              auto i3 = std::make_unique<int>(3);
              auto lambda  = [bar, i3](){bar->foo3(i3);} // nope, can't do
              

              You need to move the parameter into a capture variable:

              auto lambda  = [bar, i3cp = std::move(i3)](){bar->foo3(i3cp);}
              

              but this won't compile either, because your function takes a parameter by value, so there's another copy there. You have to move your parameter again:

              auto i3 = std::make_unique<int>(3);
              auto lambda  = [bar, i3cp = std::move(i3)](){bar->foo3(std::move(i3cp));}
              

              but then you have another problem, because lambdas are constant by default i.e. its captures are const too and not movable, so you have to make your lambda mutable:

              auto lambda  = [bar, i3cp = std::move(i3)]() mutable { bar->foo3(std::move(i3cp)); }
              

              This will work. Notice, that this lambda is non-copyable, because it captures a unique_ptr, which itself is non-copyable, yet you can still use that lambda in invokeMethod. That's because invokeMethod will move it if it needs to (e.g. when scheduled on another thread's queue).

              But all in all moving unique_ptrs around like this is kinda shady. It's hard to keep track of what's where and who holds it. I gave you a solution, but I would advise against using it.

              T Offline
              T Offline
              Tarae
              wrote on last edited by
              #5

              @Chris-Kawa Thank you for the explanation and the solution.
              But I would argue that using unique_ptr is making it harder to keep track of ownership. The one holding the unique_ptr holds the data, it's simple as that.

              1 Reply Last reply
              0

              • Login

              • Login or register to search.
              • First post
                Last post
              0
              • Categories
              • Recent
              • Tags
              • Popular
              • Users
              • Groups
              • Search
              • Get Qt
              • Unsolved