Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Special Interest Groups
  3. C++ Gurus
  4. c'tor breaks my QList.append()
Forum Updated to NodeBB v4.3 + New Features

c'tor breaks my QList.append()

Scheduled Pinned Locked Moved Solved C++ Gurus
12 Posts 2 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.
  • mzimmersM mzimmers

    Hi all -

    I'm trying to add a c'tor to a class from which I construct a QList. Here's the gist of it:

    class EquipmentItem
    {
    public:
        QString m_name;
        QString m_description;
        QString m_serialNbr;
        ...
        // the line below breaks my list.append
        EquipmentItem(EquipmentItem &item) {} 
    }
    
    QList<EquipmentItem> list;
    
    // line below uses: void append(rvalue_ref t)
    list.append({"Heater", 
                "RS 485",
                "12345"
                });
    

    Builds and runs correctly if I don't have a c'tor defined. When I do, I get an error:

    error: no matching function for call to 'QList<EquipmentItem>::append(<brace-enclosed initializer list>)'
    

    along with several failed candidate messages.

    I've tried variations on the c'tor, but I end up with the same error. I would assume that QList.append() is invoking a copy c'tor (?) so I don't understand why creating another c'tor would affect it. Obviously, at least one of my assumptions here is flawed.

    Can someone explain what's happening behind the scenes of that append() call, and how I can correctly form a c'tor in order to obviate the issue?

    Thanks...

    Chris KawaC Online
    Chris KawaC Online
    Chris Kawa
    Lifetime Qt Champion
    wrote on last edited by Chris Kawa
    #2

    @mzimmers First of all copy constructor should take a const reference, so this:

    EquipmentItem(const EquipmentItem &item) {} 
    

    but that's just a side note. It's not the issue here.

    This

    {"Heater",
     "RS 485",
     "12345"}
    

    creates an std::initializer_list<const char*>. On the other hand append expects const QList<EquipmentItem>&.

    QList<EquipmentItem> has a constructor that takes std::initializer_list<EquipmentItem>, so the only way this can work is if there exists an implicit conversion from const char* to EquipmentItem. But, since the only constructor you have for your type is a copy constructor from another item, there's no valid conversion, so compilation fails.

    In short - if you want this append syntax you have to compile you need an item constructor like this:

    EquipmentItem(const char* stuff) {  /* do something */ }
    
    mzimmersM 1 Reply Last reply
    5
    • Chris KawaC Online
      Chris KawaC Online
      Chris Kawa
      Lifetime Qt Champion
      wrote on last edited by Chris Kawa
      #3

      Just to add - what I said above would append 3 items. If you want just one item that gets created from that initializer list you have to provide a constructor that takes an initializer list, three strings or no constructor at all. If you don't have a constructor a compiler will initialize your class piece-wise from the initializer list. If you provide any constructor, be it default, custom, move or copy, then you prevent compiler from generating any automatic construction.

      So another way to fix this is to remove all constructors or provide one of these:

      EquipmentItem(const char* a, const char* b, const char* c) { ... }
      
      EquipmentItem(const std::initializer_list<const char*>&) { ... }
      
      1 Reply Last reply
      2
      • Chris KawaC Chris Kawa

        @mzimmers First of all copy constructor should take a const reference, so this:

        EquipmentItem(const EquipmentItem &item) {} 
        

        but that's just a side note. It's not the issue here.

        This

        {"Heater",
         "RS 485",
         "12345"}
        

        creates an std::initializer_list<const char*>. On the other hand append expects const QList<EquipmentItem>&.

        QList<EquipmentItem> has a constructor that takes std::initializer_list<EquipmentItem>, so the only way this can work is if there exists an implicit conversion from const char* to EquipmentItem. But, since the only constructor you have for your type is a copy constructor from another item, there's no valid conversion, so compilation fails.

        In short - if you want this append syntax you have to compile you need an item constructor like this:

        EquipmentItem(const char* stuff) {  /* do something */ }
        
        mzimmersM Offline
        mzimmersM Offline
        mzimmers
        wrote on last edited by
        #4

        @Chris-Kawa wow...lots to digest here.

        QList<EquipmentItem> has a constructor that takes std::initializer_list<EquipmentItem>

        Oh, so that's what that is. I thought somehow it was converting the content between the curly braces to an object and invoking the copy c'tor. So, I guess I got away with this because all my members could be considered <const char *>, and if I had an int member, it wouldn't have worked?

        In any event, I'm not at all married to my current implementation - that's going to get thrown away anyway once my back-end is defined (so to speak). Do you have a recommendation for a better way to do this? My only requirement is that upon construction, I need to populate a QUuid member. This is why I was trying to add my own c'tor in the first place.

        Thanks...

        Chris KawaC 1 Reply Last reply
        0
        • mzimmersM mzimmers

          @Chris-Kawa wow...lots to digest here.

          QList<EquipmentItem> has a constructor that takes std::initializer_list<EquipmentItem>

          Oh, so that's what that is. I thought somehow it was converting the content between the curly braces to an object and invoking the copy c'tor. So, I guess I got away with this because all my members could be considered <const char *>, and if I had an int member, it wouldn't have worked?

          In any event, I'm not at all married to my current implementation - that's going to get thrown away anyway once my back-end is defined (so to speak). Do you have a recommendation for a better way to do this? My only requirement is that upon construction, I need to populate a QUuid member. This is why I was trying to add my own c'tor in the first place.

          Thanks...

          Chris KawaC Online
          Chris KawaC Online
          Chris Kawa
          Lifetime Qt Champion
          wrote on last edited by
          #5

          @mzimmers said:

          if I had an int member, it wouldn't have worked?

          You can have an int, or any other types there. It's so called list initialization. The point is that it's an automatically generated initialization. If you add any constructor, even an unrelated one, it disables any and all automatic construction mechanisms.

          mzimmersM 1 Reply Last reply
          0
          • Chris KawaC Chris Kawa

            @mzimmers said:

            if I had an int member, it wouldn't have worked?

            You can have an int, or any other types there. It's so called list initialization. The point is that it's an automatically generated initialization. If you add any constructor, even an unrelated one, it disables any and all automatic construction mechanisms.

            mzimmersM Offline
            mzimmersM Offline
            mzimmers
            wrote on last edited by mzimmers
            #6

            @Chris-Kawa OK, I think I get it. So, the QList.append() invokes an automatic construction mechanism (to use your term), which is only available if I don't try to help out by creating my own c'tors?

            Very interesting.

            So, how might you recommend doing this? Should I just go ahead and create a c'tor that accepts arguments for each member that I want to initialize externally? (EDIT: I realize I'll have to change my append() calls, probably (re)using a EquipmentItem variable that I initialize for each instance I want to create.)

            Thanks...

            Chris KawaC 1 Reply Last reply
            0
            • mzimmersM mzimmers

              @Chris-Kawa OK, I think I get it. So, the QList.append() invokes an automatic construction mechanism (to use your term), which is only available if I don't try to help out by creating my own c'tors?

              Very interesting.

              So, how might you recommend doing this? Should I just go ahead and create a c'tor that accepts arguments for each member that I want to initialize externally? (EDIT: I realize I'll have to change my append() calls, probably (re)using a EquipmentItem variable that I initialize for each instance I want to create.)

              Thanks...

              Chris KawaC Online
              Chris KawaC Online
              Chris Kawa
              Lifetime Qt Champion
              wrote on last edited by Chris Kawa
              #7

              @mzimmers said:

              So, how might you recommend doing this?

              If you don't need any custom behavior just don't add a constructor at all. It will "just work"™️. See the rule of zero and the rules of three/five for recommendations about providing initialization methods to your classes. Here's an example. In short - if you don't need custom behavior don't provide custom methods.

              So, the QList.append() invokes an automatic construction mechanism

              No, append doesn't do anything special. It just takes either a list or a single value. The value for QList<T> needs to be T, so if you provide anything else (like the {} initializer) it needs to be convertible to T to compile.

              The list or its methods are not important here. It's exactly the same without any list, i.e. if you wrote

              EquipmentItem item {"Heater", "RS 485", "12345"};
              

              It will compile fine if you don't provide any constructors or if you provide one of the ones I mentioned. If you provide just a copy constructor this will not compile, because there's no copy here and you disabled automatic initialization by providing a constructor.

              mzimmersM 1 Reply Last reply
              3
              • Chris KawaC Chris Kawa

                @mzimmers said:

                So, how might you recommend doing this?

                If you don't need any custom behavior just don't add a constructor at all. It will "just work"™️. See the rule of zero and the rules of three/five for recommendations about providing initialization methods to your classes. Here's an example. In short - if you don't need custom behavior don't provide custom methods.

                So, the QList.append() invokes an automatic construction mechanism

                No, append doesn't do anything special. It just takes either a list or a single value. The value for QList<T> needs to be T, so if you provide anything else (like the {} initializer) it needs to be convertible to T to compile.

                The list or its methods are not important here. It's exactly the same without any list, i.e. if you wrote

                EquipmentItem item {"Heater", "RS 485", "12345"};
                

                It will compile fine if you don't provide any constructors or if you provide one of the ones I mentioned. If you provide just a copy constructor this will not compile, because there's no copy here and you disabled automatic initialization by providing a constructor.

                mzimmersM Offline
                mzimmersM Offline
                mzimmers
                wrote on last edited by mzimmers
                #8

                @Chris-Kawa OK, here's what I have so far (most extraneous stuff removed):

                class EquipmentItem
                {
                    Q_GADGET
                    Q_PROPERTY(QString name MEMBER m_name)
                
                public:
                    QUuid m_uuid;
                    QString m_name;
                    bool operator==(const EquipmentItem &rhs) const;
                    EquipmentItem(QString name = "");
                                 
                EquipmentItem::EquipmentItem(QString name) :
                    m_uuid(QUuid::createUuid()),
                    m_name(name)
                {}
                
                EquipmentItem *item = new EquipmentItem("name");
                m_equipmentList.append(*item);
                delete item;
                

                I've confirmed that it works, but I'd welcome feedback on whether this is a "best practices of C++" implementation, particularly where I create and delete the instance and then append it to the list.

                Thanks...

                Chris KawaC 1 Reply Last reply
                0
                • mzimmersM mzimmers

                  @Chris-Kawa OK, here's what I have so far (most extraneous stuff removed):

                  class EquipmentItem
                  {
                      Q_GADGET
                      Q_PROPERTY(QString name MEMBER m_name)
                  
                  public:
                      QUuid m_uuid;
                      QString m_name;
                      bool operator==(const EquipmentItem &rhs) const;
                      EquipmentItem(QString name = "");
                                   
                  EquipmentItem::EquipmentItem(QString name) :
                      m_uuid(QUuid::createUuid()),
                      m_name(name)
                  {}
                  
                  EquipmentItem *item = new EquipmentItem("name");
                  m_equipmentList.append(*item);
                  delete item;
                  

                  I've confirmed that it works, but I'd welcome feedback on whether this is a "best practices of C++" implementation, particularly where I create and delete the instance and then append it to the list.

                  Thanks...

                  Chris KawaC Online
                  Chris KawaC Online
                  Chris Kawa
                  Lifetime Qt Champion
                  wrote on last edited by
                  #9

                  @mzimmers said in c'tor breaks my QList.append():

                  I'd welcome feedback on whether this is a "best practices of C++" implementation, particularly where I create and delete the instance and then append it to the list

                  No, that's definitely bad. You're doing dynamic allocation just to copy the object and then immediately delete it. That's unnecessarily slow. Create the item on the stack if you need it also somewhere else, but if not then the best way to do it is to use QList::emplaceBack. This will create the item already on the list, so there would be no copies at all:

                  m_equipmentList.emplaceBack("name");
                  

                  Some other notes about this constructor:

                  EquipmentItem(QString name = "")
                  

                  Whenever you don't actually need a copy prefer a const reference. QString is implicitly shared, so copying it is not super expensive, but a const reference will be even faster still, since there's no copy made at all.
                  Another thing is the "". That's the most expensive way to make an empty QString. It calls a constructor that takes a string literal and copies it into its internal data storage. If you want an empty string just use a default constructor, that involves no copies, so the code should rather be:

                  EquipmentItem(const QString& name = QString())
                  
                  mzimmersM 1 Reply Last reply
                  2
                  • Chris KawaC Chris Kawa

                    @mzimmers said in c'tor breaks my QList.append():

                    I'd welcome feedback on whether this is a "best practices of C++" implementation, particularly where I create and delete the instance and then append it to the list

                    No, that's definitely bad. You're doing dynamic allocation just to copy the object and then immediately delete it. That's unnecessarily slow. Create the item on the stack if you need it also somewhere else, but if not then the best way to do it is to use QList::emplaceBack. This will create the item already on the list, so there would be no copies at all:

                    m_equipmentList.emplaceBack("name");
                    

                    Some other notes about this constructor:

                    EquipmentItem(QString name = "")
                    

                    Whenever you don't actually need a copy prefer a const reference. QString is implicitly shared, so copying it is not super expensive, but a const reference will be even faster still, since there's no copy made at all.
                    Another thing is the "". That's the most expensive way to make an empty QString. It calls a constructor that takes a string literal and copies it into its internal data storage. If you want an empty string just use a default constructor, that involves no copies, so the code should rather be:

                    EquipmentItem(const QString& name = QString())
                    
                    mzimmersM Offline
                    mzimmersM Offline
                    mzimmers
                    wrote on last edited by mzimmers
                    #10

                    @Chris-Kawa noted on the first point (I've included the other members):

                        EquipmentItem(const QString &name = QString(),
                                      const QString &description = QString(),
                                      const QString &serialNbr = QString(),
                                      const QUrl &iconFile = QUrl::fromEncoded(QByteArray()),
                                      const Equipment::Category &category = Equipment::OTHER,
                                      const QUrl &drawerFile = QUrl::fromEncoded(QByteArray()));
                    

                    Regarding your second point, I assume this is what you meant:

                    EquipmentItem item (
                                    "name",
                                    "decrip",
                                    "12345",
                                    QUrl::fromEncoded("qrc:/icons/Pump.png"),
                                    Equipment::PUMP,
                                    QUrl("qrc:/Pump.qml")
                                    );
                    
                        m_equipmentList.emplaceBack(item);
                    

                    Thanks for the feedback -- I sensed something was sub-optimal with my original implementation, but I couldn't put my finger on it.

                    Regarding the use of QList.append()...I followed an example from a video tutorial on this page. It's quite lengthy, so I don't expect you to watch it, but in the chance that you've already seen it, I'd be curious of what you think of the author's approach. And, I don't understand why he used append instead of emplace_back, either.

                    Chris KawaC 1 Reply Last reply
                    0
                    • mzimmersM mzimmers

                      @Chris-Kawa noted on the first point (I've included the other members):

                          EquipmentItem(const QString &name = QString(),
                                        const QString &description = QString(),
                                        const QString &serialNbr = QString(),
                                        const QUrl &iconFile = QUrl::fromEncoded(QByteArray()),
                                        const Equipment::Category &category = Equipment::OTHER,
                                        const QUrl &drawerFile = QUrl::fromEncoded(QByteArray()));
                      

                      Regarding your second point, I assume this is what you meant:

                      EquipmentItem item (
                                      "name",
                                      "decrip",
                                      "12345",
                                      QUrl::fromEncoded("qrc:/icons/Pump.png"),
                                      Equipment::PUMP,
                                      QUrl("qrc:/Pump.qml")
                                      );
                      
                          m_equipmentList.emplaceBack(item);
                      

                      Thanks for the feedback -- I sensed something was sub-optimal with my original implementation, but I couldn't put my finger on it.

                      Regarding the use of QList.append()...I followed an example from a video tutorial on this page. It's quite lengthy, so I don't expect you to watch it, but in the chance that you've already seen it, I'd be curious of what you think of the author's approach. And, I don't understand why he used append instead of emplace_back, either.

                      Chris KawaC Online
                      Chris KawaC Online
                      Chris Kawa
                      Lifetime Qt Champion
                      wrote on last edited by Chris Kawa
                      #11

                      Regarding your second point, I assume this is what you meant:

                      No, that does a copy. If you use emplaceBack you pass all the constructor arguments directly to the function, so:

                      m_equipmentList.emplaceBack(
                                      "name",
                                      "decrip",
                                      "12345",
                                      QUrl::fromEncoded("qrc:/icons/Pump.png"),
                                      Equipment::PUMP,
                                      QUrl("qrc:/Pump.qml")
                                      );
                      

                      the way it works is that append creates a new instance on the list and initializes it using a copy constructor. emplaceBack doesn't do a copy. It creates the item directly on the list using a normal constructor and passes all these arguments to it using so-called perfect forwarding.

                      As for the constructor, like with a QString, if you want an empty url just use default constructor, so const QUrl &iconFile = QUrl(). You don't need a roundtrip through creating an empty array and trying to decode it with fromEncoded.

                      For basic types, like enums, you don't need a reference. Copy costs the same in that case, so you can just do Equipment::Category category = Equipment::OTHER

                      mzimmersM 1 Reply Last reply
                      3
                      • Chris KawaC Chris Kawa

                        Regarding your second point, I assume this is what you meant:

                        No, that does a copy. If you use emplaceBack you pass all the constructor arguments directly to the function, so:

                        m_equipmentList.emplaceBack(
                                        "name",
                                        "decrip",
                                        "12345",
                                        QUrl::fromEncoded("qrc:/icons/Pump.png"),
                                        Equipment::PUMP,
                                        QUrl("qrc:/Pump.qml")
                                        );
                        

                        the way it works is that append creates a new instance on the list and initializes it using a copy constructor. emplaceBack doesn't do a copy. It creates the item directly on the list using a normal constructor and passes all these arguments to it using so-called perfect forwarding.

                        As for the constructor, like with a QString, if you want an empty url just use default constructor, so const QUrl &iconFile = QUrl(). You don't need a roundtrip through creating an empty array and trying to decode it with fromEncoded.

                        For basic types, like enums, you don't need a reference. Copy costs the same in that case, so you can just do Equipment::Category category = Equipment::OTHER

                        mzimmersM Offline
                        mzimmersM Offline
                        mzimmers
                        wrote on last edited by
                        #12

                        @Chris-Kawa thanks for all the help on this. Feel free to add anything, but I'm going to consider the topic solved.

                        1 Reply Last reply
                        0
                        • mzimmersM mzimmers 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