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. Containers with C++17 structured bindings
Forum Updated to NodeBB v4.3 + New Features

Containers with C++17 structured bindings

Scheduled Pinned Locked Moved Solved C++ Gurus
13 Posts 7 Posters 3.1k Views 5 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.
  • D Offline
    D Offline
    deleted541
    wrote on last edited by
    #3

    Hi, thanks for letting me know.
    I guess the question then is, if plain iterators are the best way to do this if I need both the key and value in a loop.

    1 Reply Last reply
    0
    • Chris KawaC Offline
      Chris KawaC Offline
      Chris Kawa
      Lifetime Qt Champion
      wrote on last edited by
      #4

      Unlike std::map Qt does not store the key/value in a pair, so you can't bind your structure to the result of operator*. It returns only the value.

      If you need both key and a value you can either use the map's iterator:

      for( auto it = qt_m.cbegin(); it != qt_m.cend(); ++it )
      {
          qDebug() << it.key() << " : " << it.value() << '\n';
      }
      

      or the iterator class:

      QMapIterator it(qt_m);
      while(it.hasNext())
      {
          it.next();
          qDebug() << it.key() << " : " << it.value() << '\n';
      }
      
      1 Reply Last reply
      7
      • fcarneyF Offline
        fcarneyF Offline
        fcarney
        wrote on last edited by
        #5

        Or:

        for(auto key: qt_m.keys()){
          qInfo() << key << qt_m[key];
        }
        

        C++ is a perfectly valid school of magic.

        Chris KawaC 1 Reply Last reply
        0
        • fcarneyF fcarney

          Or:

          for(auto key: qt_m.keys()){
            qInfo() << key << qt_m[key];
          }
          
          Chris KawaC Offline
          Chris KawaC Offline
          Chris Kawa
          Lifetime Qt Champion
          wrote on last edited by
          #6

          @fcarney Yeah, but that's a very bad solution. First keys() makes a copy of the data into a new container and then you make a find for each element (that's what operator[] is essentially). It's terrible both performance wise and for memory consumption. Please don't do this in production code.

          1 Reply Last reply
          3
          • fcarneyF Offline
            fcarneyF Offline
            fcarney
            wrote on last edited by
            #7

            @Chris-Kawa said in Containers with C++17 structured bindings:

            It's terrible both performance wise and for memory consumption.

            Now I can see why keys() is not in the std::map. It would encourage poor coding practices. Thanks for your insight. I generally use this to see whats in my map for debugging, but yeah I can see the performance problems it would create on large datasets.

            C++ is a perfectly valid school of magic.

            kshegunovK 1 Reply Last reply
            0
            • SGaistS Offline
              SGaistS Offline
              SGaist
              Lifetime Qt Champion
              wrote on last edited by
              #8

              Hi,

              Just in case, there are key/value iterators for both QMap and QHash:
              QMap::keyValueBegin
              QMap::keyValueEnd
              QMap::constKeyValueBegin
              QMap::constKeyValueEnd
              QHash::keyValueBegin
              QHash::keyValueEnd
              QHash::constKeyValueBegin
              QHash::constKeyValueEnd

              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
              4
              • Chris KawaC Offline
                Chris KawaC Offline
                Chris Kawa
                Lifetime Qt Champion
                wrote on last edited by
                #9

                @SGaist Huh, that's cool. I didn't know those existed. Thanks!

                @david_dlqa So you could write a little adapter thingy like this:

                template<class Container> class adapt
                {
                public:
                    adapt(const Container& container) : c(container) {}
                    typename Container::const_key_value_iterator begin() const { return c.keyValueBegin(); }
                    typename Container::const_key_value_iterator end() const { return c.keyValueEnd(); }
                private:
                    const Container& c;
                };
                

                and then you could use structured bindings like so:

                for( const auto& [key, value] : adapt(qt_m) )
                {
                    qDebug() << key << " : " << value << '\n';
                }
                

                From what i see it's still an inferior solution performance wise, as those keyValue iterators make pairs on the fly, but it might be good enough.

                D 1 Reply Last reply
                5
                • fcarneyF fcarney

                  @Chris-Kawa said in Containers with C++17 structured bindings:

                  It's terrible both performance wise and for memory consumption.

                  Now I can see why keys() is not in the std::map. It would encourage poor coding practices. Thanks for your insight. I generally use this to see whats in my map for debugging, but yeah I can see the performance problems it would create on large datasets.

                  kshegunovK Offline
                  kshegunovK Offline
                  kshegunov
                  Moderators
                  wrote on last edited by
                  #10

                  @fcarney said in Containers with C++17 structured bindings:

                  I generally use this to see whats in my map for debugging, but yeah I can see the performance problems it would create on large datasets.

                  You don't want to keep large datasets in a map to begin with. [1]

                  [1]: https://woboq.com/blog/qmap_qhash_benchmark.html

                  Read and abide by the Qt Code of Conduct

                  1 Reply Last reply
                  1
                  • Chris KawaC Chris Kawa

                    @SGaist Huh, that's cool. I didn't know those existed. Thanks!

                    @david_dlqa So you could write a little adapter thingy like this:

                    template<class Container> class adapt
                    {
                    public:
                        adapt(const Container& container) : c(container) {}
                        typename Container::const_key_value_iterator begin() const { return c.keyValueBegin(); }
                        typename Container::const_key_value_iterator end() const { return c.keyValueEnd(); }
                    private:
                        const Container& c;
                    };
                    

                    and then you could use structured bindings like so:

                    for( const auto& [key, value] : adapt(qt_m) )
                    {
                        qDebug() << key << " : " << value << '\n';
                    }
                    

                    From what i see it's still an inferior solution performance wise, as those keyValue iterators make pairs on the fly, but it might be good enough.

                    D Offline
                    D Offline
                    deleted541
                    wrote on last edited by
                    #11

                    @Chris-Kawa
                    Nice, thats very helpful. I think the performance penalty here is negligible. I ran a test on my machine which produced these results in release mode:

                    const iter 2799
                    non-const iter 2835
                    adapt const-reference 2819
                    adapt copy 2808
                    

                    Test code:

                    #include <QMap>
                    #include <map>
                    #include <QDebug>
                    #include <QElapsedTimer>
                    
                    template<class Container> class adapt
                    {
                    public:
                    	adapt(const Container& container) : c(container) {}
                    	typename Container::const_key_value_iterator begin() const { return c.keyValueBegin(); }
                    	typename Container::const_key_value_iterator end() const { return c.keyValueEnd(); }
                    private:
                    	const Container& c;
                    };
                    
                    int main()
                    {
                    	QMap<int, QString> qt_m;
                    	for(int i = 0; i < (1<<24); ++i) qt_m.insert(rand(), QString::number(rand()));
                    	QString a;
                    	int b;
                    
                    	QElapsedTimer t;
                    	t.start();
                    	for( auto it = qt_m.cbegin(); it != qt_m.cend(); ++it )
                    	{
                    		a = *it;
                    		b = it.key();
                    	}
                    	qDebug() << "const iter" << t.elapsed();
                    
                    	t.restart();
                    	for( auto it = qt_m.begin(); it != qt_m.end(); ++it )
                    	{
                    		a = *it;
                    		b = it.key();
                    	}
                    	qDebug() << "non-const iter" << t.elapsed();
                    
                    	t.restart();
                    	for( const auto& [key, value] : adapt(qt_m) )
                    	{
                    		a = value;
                    		b = key;
                    	}
                    	qDebug() << "adapt const-reference" << t.elapsed();
                    
                    	t.restart();
                    	for( auto [key, value] : adapt(qt_m) )
                    	{
                    		a = value;
                    		b = key;
                    	}
                    	qDebug() << "adapt copy" << t.elapsed();
                     }
                    
                    J.HilkJ 1 Reply Last reply
                    0
                    • D deleted541

                      @Chris-Kawa
                      Nice, thats very helpful. I think the performance penalty here is negligible. I ran a test on my machine which produced these results in release mode:

                      const iter 2799
                      non-const iter 2835
                      adapt const-reference 2819
                      adapt copy 2808
                      

                      Test code:

                      #include <QMap>
                      #include <map>
                      #include <QDebug>
                      #include <QElapsedTimer>
                      
                      template<class Container> class adapt
                      {
                      public:
                      	adapt(const Container& container) : c(container) {}
                      	typename Container::const_key_value_iterator begin() const { return c.keyValueBegin(); }
                      	typename Container::const_key_value_iterator end() const { return c.keyValueEnd(); }
                      private:
                      	const Container& c;
                      };
                      
                      int main()
                      {
                      	QMap<int, QString> qt_m;
                      	for(int i = 0; i < (1<<24); ++i) qt_m.insert(rand(), QString::number(rand()));
                      	QString a;
                      	int b;
                      
                      	QElapsedTimer t;
                      	t.start();
                      	for( auto it = qt_m.cbegin(); it != qt_m.cend(); ++it )
                      	{
                      		a = *it;
                      		b = it.key();
                      	}
                      	qDebug() << "const iter" << t.elapsed();
                      
                      	t.restart();
                      	for( auto it = qt_m.begin(); it != qt_m.end(); ++it )
                      	{
                      		a = *it;
                      		b = it.key();
                      	}
                      	qDebug() << "non-const iter" << t.elapsed();
                      
                      	t.restart();
                      	for( const auto& [key, value] : adapt(qt_m) )
                      	{
                      		a = value;
                      		b = key;
                      	}
                      	qDebug() << "adapt const-reference" << t.elapsed();
                      
                      	t.restart();
                      	for( auto [key, value] : adapt(qt_m) )
                      	{
                      		a = value;
                      		b = key;
                      	}
                      	qDebug() << "adapt copy" << t.elapsed();
                       }
                      
                      J.HilkJ Online
                      J.HilkJ Online
                      J.Hilk
                      Moderators
                      wrote on last edited by
                      #12

                      @david_dlqa said
                      Just a word of caution, as I ran into this as well.

                      If you do speed tests with QElapsedTimer, do not simply print the elapsed() value to qDebug().

                      qDebug() << "const iter" << t.elapsed();

                      the qDebug() call will take a significant amount of time.

                      I usually store the elapsed value in a local variable and print that one at time insensitive points (the end)


                      Be aware of the Qt Code of Conduct, when posting : https://forum.qt.io/topic/113070/qt-code-of-conduct


                      Q: What's that?
                      A: It's blue light.
                      Q: What does it do?
                      A: It turns blue.

                      D 1 Reply Last reply
                      0
                      • J.HilkJ J.Hilk

                        @david_dlqa said
                        Just a word of caution, as I ran into this as well.

                        If you do speed tests with QElapsedTimer, do not simply print the elapsed() value to qDebug().

                        qDebug() << "const iter" << t.elapsed();

                        the qDebug() call will take a significant amount of time.

                        I usually store the elapsed value in a local variable and print that one at time insensitive points (the end)

                        D Offline
                        D Offline
                        deleted541
                        wrote on last edited by
                        #13

                        @J.Hilk
                        Thanks, that makes sense. I just copied that from the documentation hence I thought that was not a concern.
                        I reran the tests and got almost the same results, so I think with times in the seconds, the qDebug overhead is not significant

                        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 Extensions
                        • Unsolved