Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Unexpected QList<QVariant> Initialiser Behaviour
Qt 6.11 is out! See what's new in the release blog

Unexpected QList<QVariant> Initialiser Behaviour

Scheduled Pinned Locked Moved Solved General and Desktop
10 Posts 6 Posters 1.7k 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.
  • KenAppleby 0K Offline
    KenAppleby 0K Offline
    KenAppleby 0
    wrote on last edited by
    #1

    This isn't so much a problem report, because a solution is easy to implement, but this behaviour is very unexpected and has caused me some wasted time. I post it in case it might help others to avoid it.

    Initialising a member variable of type

    QList<QVariant> results;
    

    using "{}" braces produces the unexpected (to me) result of creating a value of type QList<QList<QVariant>>.
    Whereas initialising the member variable using "()" round brackets produces a value of the expected (to me) type QList<QVariant>.
    Example code that reproduces this:

    class A
    {
    public:
      A(QList<QVariant> resultsList)
        : results{ resultsList } // <<<<<<
      {
         qDebug() << "A results are" << results.size() << "long";
      }
      QList<QVariant> results;
    };
    
    class B
    {
    public:
      B(QList<QVariant> resultsList)
        : results(resultsList) // <<<<<<
      {
         qDebug() << "B results are" << results.size() << "long";
      }
      QList<QVariant> results;
    };
    
    class C
    {
    public:
      C(QList<int> resultsList)
        : results{ resultsList } // <<<<<<
      {
         qDebug() << "C QList<int> results are" << results.size() << "long";
      }
      QList<int> results;
    };
    
    
    int main(int , char *[])
    {
        QList<QVariant> x;
        x << 1 << 2 << 3;
    
        A b1{ x };
        A b2(x);
    
        B c1{ x };
        B c2{ x };
    
        QList<int> i;
        i << 1 << 2 << 3;
        C a1{ i };
        C a2(i);
    }
    

    with the output
    A results are 1 long
    A results are 1 long
    B results are 3 long
    B results are 3 long
    C QList<int> results are 3 long
    C QList<int> results are 3 long

    The behaviour is tied to QVariant, not QList<> as the "C" class results which use a QList<int> show.

    1 Reply Last reply
    0
    • Axel SpoerlA Offline
      Axel SpoerlA Offline
      Axel Spoerl
      Moderators
      wrote on last edited by
      #2

      Can you debug the list object itself, not just the size?
      In the case of A it is a nested variant list - one element which is a variant list itself.

      Software Engineer
      The Qt Company, Oslo

      1 Reply Last reply
      0
      • KenAppleby 0K Offline
        KenAppleby 0K Offline
        KenAppleby 0
        wrote on last edited by
        #3

        Sorry, I described this incorrectly.

        As you say @Axel-Spoerl, case A produces a QList<QVariant>, as it should do, of course, with one element which is a QVariant<QList<QVariant>>.

        This is fair enough, but tricky, I claim.

        Constructing a QList<QVariant> on the stack using the same {} method produces a different result:

        class A
        {
        public:
            A(QList<QVariant> resultsList)
              : results{ resultsList } // <<<<<<
            {
                qDebug() << "A results are length" << results.size();
                for (auto& r : results) qDebug() << "\\ - " << r;
            }
            QList<QVariant> results;
        };
        
        class B
        {
        public:
            B(QList<QVariant> resultsList)
              : results(resultsList) // <<<<<<
            {
                qDebug() << "B results are length" << results.size();
                for (auto& r : results) qDebug() << "\\ - " << r;
            }
            QList<QVariant> results;
        };
        
        int main(int , char *[])
        {
            const QList<QVariant> x{ 1, 2, "3" };
        
            A a1{ x };
            B b1{ x };
        
            QList<QVariant> C{ x };
        
            qDebug() << "Stack C results are length" << C.size();
            for (auto& r : C) qDebug() << "\\ - " << r;
        }
        

        A results are length 1
        - QVariant(QVariantList, QList(QVariant(int, 1), QVariant(int, 2), QVariant(QString, "3")))
        B results are length 3
        - QVariant(int, 1)
        - QVariant(int, 2)
        - QVariant(QString, "3")
        Stack C results are length 3
        - QVariant(int, 1)
        - QVariant(int, 2)
        - QVariant(QString, "3")

        Pl45m4P 1 Reply Last reply
        0
        • KenAppleby 0K KenAppleby 0

          Sorry, I described this incorrectly.

          As you say @Axel-Spoerl, case A produces a QList<QVariant>, as it should do, of course, with one element which is a QVariant<QList<QVariant>>.

          This is fair enough, but tricky, I claim.

          Constructing a QList<QVariant> on the stack using the same {} method produces a different result:

          class A
          {
          public:
              A(QList<QVariant> resultsList)
                : results{ resultsList } // <<<<<<
              {
                  qDebug() << "A results are length" << results.size();
                  for (auto& r : results) qDebug() << "\\ - " << r;
              }
              QList<QVariant> results;
          };
          
          class B
          {
          public:
              B(QList<QVariant> resultsList)
                : results(resultsList) // <<<<<<
              {
                  qDebug() << "B results are length" << results.size();
                  for (auto& r : results) qDebug() << "\\ - " << r;
              }
              QList<QVariant> results;
          };
          
          int main(int , char *[])
          {
              const QList<QVariant> x{ 1, 2, "3" };
          
              A a1{ x };
              B b1{ x };
          
              QList<QVariant> C{ x };
          
              qDebug() << "Stack C results are length" << C.size();
              for (auto& r : C) qDebug() << "\\ - " << r;
          }
          

          A results are length 1
          - QVariant(QVariantList, QList(QVariant(int, 1), QVariant(int, 2), QVariant(QString, "3")))
          B results are length 3
          - QVariant(int, 1)
          - QVariant(int, 2)
          - QVariant(QString, "3")
          Stack C results are length 3
          - QVariant(int, 1)
          - QVariant(int, 2)
          - QVariant(QString, "3")

          Pl45m4P Offline
          Pl45m4P Offline
          Pl45m4
          wrote on last edited by
          #4

          @KenAppleby-0 said in Unexpected QList<QVariant> Initialiser Behaviour:

          A results are length 1

          • QVariant(QVariantList, QList(QVariant(int, 1), QVariant(int, 2), QVariant(QString, "3")))

          Simple:
          You use X to initialize A, which uses the whole list to initalize the results member which becomes a single element list of QList<QVariant> == QVariantList.

          That's how I would explain this behavior.

          @kshegunov made a good explanation here


          If debugging is the process of removing software bugs, then programming must be the process of putting them in.

          ~E. W. Dijkstra

          KenAppleby 0K 1 Reply Last reply
          0
          • Pl45m4P Pl45m4

            @KenAppleby-0 said in Unexpected QList<QVariant> Initialiser Behaviour:

            A results are length 1

            • QVariant(QVariantList, QList(QVariant(int, 1), QVariant(int, 2), QVariant(QString, "3")))

            Simple:
            You use X to initialize A, which uses the whole list to initalize the results member which becomes a single element list of QList<QVariant> == QVariantList.

            That's how I would explain this behavior.

            @kshegunov made a good explanation here

            KenAppleby 0K Offline
            KenAppleby 0K Offline
            KenAppleby 0
            wrote on last edited by
            #5

            @Pl45m4 Why doesn't the same happen with the stack variable?

            QList<QVariant> C{ x };
            
            C 1 Reply Last reply
            0
            • KenAppleby 0K KenAppleby 0

              @Pl45m4 Why doesn't the same happen with the stack variable?

              QList<QVariant> C{ x };
              
              C Offline
              C Offline
              ChrisW67
              wrote on last edited by
              #6

              Why doesn't the same happen with the stack variable?

              It does here (Linux, GCC 13, Qt 5.15.3 or 6.7.2):

              chrisw@newton:/tmp/tt$ ./tt 
              A results are length 1
              \ -  QVariant(QVariantList, (QVariant(int, 1), QVariant(int, 2), QVariant(QString, "3")))
              B results are length 3
              \ -  QVariant(int, 1)
              \ -  QVariant(int, 2)
              \ -  QVariant(QString, "3")
              Stack C results are length 1
              \ -  QVariant(QVariantList, (QVariant(int, 1), QVariant(int, 2), QVariant(QString, "3")))
              
              Pl45m4P 1 Reply Last reply
              2
              • C ChrisW67

                Why doesn't the same happen with the stack variable?

                It does here (Linux, GCC 13, Qt 5.15.3 or 6.7.2):

                chrisw@newton:/tmp/tt$ ./tt 
                A results are length 1
                \ -  QVariant(QVariantList, (QVariant(int, 1), QVariant(int, 2), QVariant(QString, "3")))
                B results are length 3
                \ -  QVariant(int, 1)
                \ -  QVariant(int, 2)
                \ -  QVariant(QString, "3")
                Stack C results are length 1
                \ -  QVariant(QVariantList, (QVariant(int, 1), QVariant(int, 2), QVariant(QString, "3")))
                
                Pl45m4P Offline
                Pl45m4P Offline
                Pl45m4
                wrote on last edited by Pl45m4
                #7

                @ChrisW67

                I got intrigued and made some tests.

                This is my output on Windows where I print the function calls from qlist.h.
                (only have an older Qt / system at hand right now)

                Windows 10, 5.15.2, MSVC2019

                QList<class QVariant>::QList called
                QList<class QVariant>::QList(InputIterator) called
                QList<class QVariant>::QList(initializer_list) called
                QList<class QVariant>::QList called
                QList<class QVariant>::QList(InputIterator) called
                QList<class QVariant>::QList(initializer_list) called
                A results are length 1
                \ -  QVariant(QVariantList, (QVariant(int, 1), QVariant(int, 2), QVariant(QString, "3")))
                \ -  QVariant(int, 1)
                \ -  QVariant(int, 2)
                \ -  QVariant(QString, "3")
                (QVariant(int, 1), QVariant(int, 2), QVariant(QString, "3"))
                B results are length 3
                \ -  QVariant(int, 1)
                \ -  QVariant(int, 2)
                \ -  QVariant(QString, "3")
                Stack C results are length 3
                \ -  QVariant(int, 1)
                \ -  QVariant(int, 2)
                \ -  QVariant(QString, "3")
                

                I assume the problem, if any, has to do with either the QDebug output (don't trust how it interprets data) or some QVariant voodoo, as it does not happen for STL containers.

                Made an online test. No issues

                Edit:

                Maybe this sequence of output

                QList<class QVariant>::QList called
                QList<class QVariant>::QList(InputIterator) called
                QList<class QVariant>::QList(initializer_list) called
                

                indicates that it encapsulates the QVariantList somehow and creates an "inner" list inside another QVariantList using the initializer_list c'tor...

                @KenAppleby-0 I think this is where you are not sure whether this is the expected behavior, right?


                If debugging is the process of removing software bugs, then programming must be the process of putting them in.

                ~E. W. Dijkstra

                KenAppleby 0K 1 Reply Last reply
                0
                • Pl45m4P Pl45m4

                  @ChrisW67

                  I got intrigued and made some tests.

                  This is my output on Windows where I print the function calls from qlist.h.
                  (only have an older Qt / system at hand right now)

                  Windows 10, 5.15.2, MSVC2019

                  QList<class QVariant>::QList called
                  QList<class QVariant>::QList(InputIterator) called
                  QList<class QVariant>::QList(initializer_list) called
                  QList<class QVariant>::QList called
                  QList<class QVariant>::QList(InputIterator) called
                  QList<class QVariant>::QList(initializer_list) called
                  A results are length 1
                  \ -  QVariant(QVariantList, (QVariant(int, 1), QVariant(int, 2), QVariant(QString, "3")))
                  \ -  QVariant(int, 1)
                  \ -  QVariant(int, 2)
                  \ -  QVariant(QString, "3")
                  (QVariant(int, 1), QVariant(int, 2), QVariant(QString, "3"))
                  B results are length 3
                  \ -  QVariant(int, 1)
                  \ -  QVariant(int, 2)
                  \ -  QVariant(QString, "3")
                  Stack C results are length 3
                  \ -  QVariant(int, 1)
                  \ -  QVariant(int, 2)
                  \ -  QVariant(QString, "3")
                  

                  I assume the problem, if any, has to do with either the QDebug output (don't trust how it interprets data) or some QVariant voodoo, as it does not happen for STL containers.

                  Made an online test. No issues

                  Edit:

                  Maybe this sequence of output

                  QList<class QVariant>::QList called
                  QList<class QVariant>::QList(InputIterator) called
                  QList<class QVariant>::QList(initializer_list) called
                  

                  indicates that it encapsulates the QVariantList somehow and creates an "inner" list inside another QVariantList using the initializer_list c'tor...

                  @KenAppleby-0 I think this is where you are not sure whether this is the expected behavior, right?

                  KenAppleby 0K Offline
                  KenAppleby 0K Offline
                  KenAppleby 0
                  wrote on last edited by KenAppleby 0
                  #8

                  @Pl45m4 said in Unexpected QList<QVariant> Initialiser Behaviour:

                  I think this is where you are not sure whether this is the expected behavior, right?

                  I don't know what behaviour to expect, frankly. As the behaviour is different for the stack variable case with different compilers it looks like the compiler writers weren't sure either, unless there are compiler-dependent conditionals in the QVariant code.

                  I have tested this on 6.7.2 with VC2019 and MinGW, with the same results: {} initialising a stack allocated QList<QVariant> behaves differently to {} initialising a QList<QVariant> member variable of a stack allocated class instance.

                  My only claim here is that this is tricky :-)

                  1 Reply Last reply
                  0
                  • VRoninV Offline
                    VRoninV Offline
                    VRonin
                    wrote on last edited by VRonin
                    #9

                    See https://stackoverflow.com/questions/68328782/constructor-taking-stdinitializer-list-is-preferred-over-other-constructors

                    Basically the std::initializer_list constructor takes precedence over the copy constructor in class A (as it should by the standard)

                    "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
                    ~Napoleon Bonaparte

                    On a crusade to banish setIndexWidget() from the holy land of Qt

                    JonBJ 1 Reply Last reply
                    3
                    • VRoninV VRonin

                      See https://stackoverflow.com/questions/68328782/constructor-taking-stdinitializer-list-is-preferred-over-other-constructors

                      Basically the std::initializer_list constructor takes precedence over the copy constructor in class A (as it should by the standard)

                      JonBJ Offline
                      JonBJ Offline
                      JonB
                      wrote on last edited by
                      #10

                      @VRonin This is the kind of answer we pay for... :)

                      1 Reply Last reply
                      0
                      • KenAppleby 0K KenAppleby 0 has marked this topic as solved on

                      • Login

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