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. How to avoid mixing const_iterator and iterator when using QList::erase?
Forum Updated to NodeBB v4.3 + New Features

How to avoid mixing const_iterator and iterator when using QList::erase?

Scheduled Pinned Locked Moved Solved General and Desktop
13 Posts 5 Posters 2.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.
  • J Jack Hill

    Hi,
    suppose we have a list model that has a backend m_numbers = QList<int> and we implement a function to remove rows that satisfy the predicate lowerBound <= value <= upperBound

    void MyListModel::removeValues(int lowerBound, int upperBound)
    {
        int row = 0;
        auto itr = m_numbers.begin();
        while (itr != m_numbers.end()) {
            if (lowerBound <= *itr && *itr <= upperBound) {
                beginRemoveRows(parent, row, row);
                itr = m_numbers.erase(itr);
                endRemoveRows();
            } else {
                itr++;
                row++;
            }
        }
    }
    

    This works fine but I have clazy complaining about mixing iterators with const_iterators due to QList::iterator QList::erase(QList::const_iterator pos). So,

    1. is this clazy warning something to worry about?
    2. is there a way of rewriting this function to quell the clazy warning? It seems odd to me that there is no QList::const_iterator QList::erase(QList::const_iterator pos) or QList::iterator QList::erase(QList::iterator pos)
    JoeCFDJ Offline
    JoeCFDJ Offline
    JoeCFD
    wrote on last edited by
    #3

    @Jack-Hill your code is not efficient and may be dangerous. Better to loop in reverse order when items are deleted.

    1 Reply Last reply
    0
    • J Jack Hill

      Hi,
      suppose we have a list model that has a backend m_numbers = QList<int> and we implement a function to remove rows that satisfy the predicate lowerBound <= value <= upperBound

      void MyListModel::removeValues(int lowerBound, int upperBound)
      {
          int row = 0;
          auto itr = m_numbers.begin();
          while (itr != m_numbers.end()) {
              if (lowerBound <= *itr && *itr <= upperBound) {
                  beginRemoveRows(parent, row, row);
                  itr = m_numbers.erase(itr);
                  endRemoveRows();
              } else {
                  itr++;
                  row++;
              }
          }
      }
      

      This works fine but I have clazy complaining about mixing iterators with const_iterators due to QList::iterator QList::erase(QList::const_iterator pos). So,

      1. is this clazy warning something to worry about?
      2. is there a way of rewriting this function to quell the clazy warning? It seems odd to me that there is no QList::const_iterator QList::erase(QList::const_iterator pos) or QList::iterator QList::erase(QList::iterator pos)
      JonBJ Online
      JonBJ Online
      JonB
      wrote on last edited by JonB
      #4

      @Jack-Hill
      Just an observation about algorithm, not your issue. You call begin/endRemoveRows() once to remove one row at a time. I would prefer to call to once only with the range to be removed. That may help efficiency if things act on it.

      J 1 Reply Last reply
      1
      • M mpergand

        @Jack-Hill said in How to avoid mixing const_iterator and iterator when using QList::erase?:

        I have clazy complaining about mixing iterators

        Try using cbegin() and cend()

        I faced similar issues some time ago:

         /*
                  1) Use cbegin and cend to be explicit about using const iterator.
                  2) begin() and end() return const_iterator when method is declared as const
              */
        
                        typedef typename std::vector<E*>::const_iterator const_node_it;
                         const_node_it  begin() const
                                    {
                                    return _list.cbegin();
                                    }
        
                        const_node_it  end() const
                                    {
                                    return _list.cend();
        
                                        }
        
        
        J Offline
        J Offline
        Jack Hill
        wrote on last edited by
        #5

        @mpergand I don't think that would work because erase returns a non-const iterator

        @JoeCFD You mean something like this?

        int row = m_numbers.size() - 1;
        auto itr = m_numbers.end();
        while (itr != m_numbers.begin()) {
            if (lowerBound <= *itr && *itr <= upperBound) {
                beginRemoveRows(parent, row, row);
                itr = m_numbers.erase(itr);
                endRemoveRows();
            } else {
                itr--;
                row--;
            }
        }
        
        JoeCFDJ 1 Reply Last reply
        0
        • JonBJ JonB

          @Jack-Hill
          Just an observation about algorithm, not your issue. You call begin/endRemoveRows() once to remove one row at a time. I would prefer to call to once only with the range to be removed. That may help efficiency if things act on it.

          J Offline
          J Offline
          Jack Hill
          wrote on last edited by
          #6

          @JonB True. Would probably be better to remove consecutive items in one go instead of one-by-one.

          Also I just realised I think it would be better not bother with iterators and just use indices directly like so (not tested)

          for (int row = 0; row < m_numbers.size(); ) {
              const int num = m_numbers[row];
              if (lowerBound <= num && num <= upperBound) {
                  beginRemoveRows(parent, row, row);
                  m_numbers.remove(row);
                  endRemoveRows();
              } else {
                  row++;
              }
          }
          
          JonBJ 1 Reply Last reply
          0
          • J Jack Hill

            @JonB True. Would probably be better to remove consecutive items in one go instead of one-by-one.

            Also I just realised I think it would be better not bother with iterators and just use indices directly like so (not tested)

            for (int row = 0; row < m_numbers.size(); ) {
                const int num = m_numbers[row];
                if (lowerBound <= num && num <= upperBound) {
                    beginRemoveRows(parent, row, row);
                    m_numbers.remove(row);
                    endRemoveRows();
                } else {
                    row++;
                }
            }
            
            JonBJ Online
            JonBJ Online
            JonB
            wrote on last edited by
            #7

            @Jack-Hill said in How to avoid mixing const_iterator and iterator when using QList::erase?:

            Also I just realised I think it would be better not bother with iterators and just use indices directly like so (not tested)

            :) Don't know what to say. Then this topic wouldn't exist. All these iterators and consts. I was a C guy, it was just so much easier! I have to look up iterators each time, and don't use them if I feel I don't have to/can't be bothered. But I'm sure the C++ experts around these days will recommend you do.

            1 Reply Last reply
            0
            • J Jack Hill

              @mpergand I don't think that would work because erase returns a non-const iterator

              @JoeCFD You mean something like this?

              int row = m_numbers.size() - 1;
              auto itr = m_numbers.end();
              while (itr != m_numbers.begin()) {
                  if (lowerBound <= *itr && *itr <= upperBound) {
                      beginRemoveRows(parent, row, row);
                      itr = m_numbers.erase(itr);
                      endRemoveRows();
                  } else {
                      itr--;
                      row--;
                  }
              }
              
              JoeCFDJ Offline
              JoeCFDJ Offline
              JoeCFD
              wrote on last edited by
              #8

              @Jack-Hill index is ok. But your iterator does not match it.
              https://doc.qt.io/qt-5/qlist.html#crbegin

              J 1 Reply Last reply
              0
              • JoeCFDJ JoeCFD

                @Jack-Hill index is ok. But your iterator does not match it.
                https://doc.qt.io/qt-5/qlist.html#crbegin

                J Offline
                J Offline
                Jack Hill
                wrote on last edited by Jack Hill
                #9

                @JoeCFD I don't think it's possible to use reverse iterators because there's no matching erase function.

                @JonB Indeed. This is from an existing code-base, so I didn't immediately think of switching to indices.

                JoeCFDJ 1 Reply Last reply
                0
                • J Jack Hill

                  @JoeCFD I don't think it's possible to use reverse iterators because there's no matching erase function.

                  @JonB Indeed. This is from an existing code-base, so I didn't immediately think of switching to indices.

                  JoeCFDJ Offline
                  JoeCFDJ Offline
                  JoeCFD
                  wrote on last edited by
                  #10

                  @Jack-Hill

                  #include <QList>
                  
                  #include <iostream>
                  
                  int main()
                  {
                      QList< int > list{ 1,2,3,4,5,6,7,8,9,10 };
                  
                      auto iter = list.rbegin();
                      while( iter != list.rend() ) {
                          if ( 0 == *iter % 3 ) {
                              list.erase( std::next( iter ).base() );
                          }
                          
                          iter++;
                      }
                      
                      for ( auto const value : list ) {
                          std::cout << value << std::endl;
                      }
                      
                      return 0;
                  }
                  
                  
                  J 1 Reply Last reply
                  0
                  • JoeCFDJ JoeCFD

                    @Jack-Hill

                    #include <QList>
                    
                    #include <iostream>
                    
                    int main()
                    {
                        QList< int > list{ 1,2,3,4,5,6,7,8,9,10 };
                    
                        auto iter = list.rbegin();
                        while( iter != list.rend() ) {
                            if ( 0 == *iter % 3 ) {
                                list.erase( std::next( iter ).base() );
                            }
                            
                            iter++;
                        }
                        
                        for ( auto const value : list ) {
                            std::cout << value << std::endl;
                        }
                        
                        return 0;
                    }
                    
                    
                    J Offline
                    J Offline
                    Jack Hill
                    wrote on last edited by
                    #11

                    @JoeCFD That does work but it still triggers the clazy warning. It seems to me like the safest way of imperatively erasing items from QLists is to iterate with indices.

                    JoeCFDJ 1 Reply Last reply
                    0
                    • J Jack Hill

                      @JoeCFD That does work but it still triggers the clazy warning. It seems to me like the safest way of imperatively erasing items from QLists is to iterate with indices.

                      JoeCFDJ Offline
                      JoeCFDJ Offline
                      JoeCFD
                      wrote on last edited by JoeCFD
                      #12

                      @Jack-Hill I did not see any warning with g++ 11.3.0 on Ubuntu 22.04.

                      1 Reply Last reply
                      0
                      • J Jack Hill

                        Hi,
                        suppose we have a list model that has a backend m_numbers = QList<int> and we implement a function to remove rows that satisfy the predicate lowerBound <= value <= upperBound

                        void MyListModel::removeValues(int lowerBound, int upperBound)
                        {
                            int row = 0;
                            auto itr = m_numbers.begin();
                            while (itr != m_numbers.end()) {
                                if (lowerBound <= *itr && *itr <= upperBound) {
                                    beginRemoveRows(parent, row, row);
                                    itr = m_numbers.erase(itr);
                                    endRemoveRows();
                                } else {
                                    itr++;
                                    row++;
                                }
                            }
                        }
                        

                        This works fine but I have clazy complaining about mixing iterators with const_iterators due to QList::iterator QList::erase(QList::const_iterator pos). So,

                        1. is this clazy warning something to worry about?
                        2. is there a way of rewriting this function to quell the clazy warning? It seems odd to me that there is no QList::const_iterator QList::erase(QList::const_iterator pos) or QList::iterator QList::erase(QList::iterator pos)
                        kshegunovK Offline
                        kshegunovK Offline
                        kshegunov
                        Moderators
                        wrote on last edited by kshegunov
                        #13

                        @Jack-Hill said in How to avoid mixing const_iterator and iterator when using QList::erase?:

                        This works fine but I have clazy complaining about mixing iterators with const_iterators due to QList::iterator QList::erase(QList::const_iterator pos)

                        Then don't. This can be as simple as:

                        for (int i = 0, size = m_numbers.size(); i < size; i++) {
                            auto value = m_numbers[i];
                            if (value < lowerBound || value > upperBound)
                                continue;
                            beginRemoveRows(parent, i, i); // for whatever `parent` is here, it isn't clear where it comes from
                            m_numbers.removeAt(i--);
                            size--;
                            endRemoveRows();
                        }
                        

                        If you want to minimize the number of draws the view does on the other hand you'd do two passes - take the bounds of elements to be removed and only then remove them in batches.

                        [Edit: Fixed wrong boolean operation ~kshegunov]

                        Read and abide by the Qt Code of Conduct

                        1 Reply Last reply
                        1
                        • J Jack Hill 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