unable to read from const std::map<>&

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • jabbah
    New Member
    • Nov 2007
    • 63

    unable to read from const std::map<>&

    Actually I'm quite sure I've missed something trivial here, but I just can't find it.
    Seemingly I cannot read from a const map&

    I try

    Code:
    #include <iostream>
    #include <map>
    using namespace std;
    
    void show ( 
      const // troubling-const
      map<char, double>& myconstmap )
    {
      cout << "A->" << myconstmap['A'] << "\n";
    }
    
    int main ()
    {
      map<char, double> mymap;
      mymap['A']=0.1;
    }
    If I remove the troubling const I'm able to access it but I don'r really feel like giving up on const

    I'm thankful for any hint
    J
  • weaknessforcats
    Recognized Expert Expert
    • Mar 2007
    • 9214

    #2
    The const has nothing to do with it.

    It's your use of operator[] in the the cout in line 9. That operator returns a reference to the map element. It's possible to change the map byusing this reference. Your const gets in the way of that and you get a compile error.

    Either: a) don't use const or b) use const but don't use the map [] operator.

    Comment

    • jabbah
      New Member
      • Nov 2007
      • 63

      #3
      Originally posted by weaknessforcats
      The const has nothing to do with it.

      It's your use of operator[] in the the cout in line 9. That operator returns a reference to the map element. It's possible to change the map byusing this reference. Your const gets in the way of that and you get a compile error.
      ok

      Either: a) don't use const
      mhh, I dont really feel like this is a solution, but rather a workaround which I dont feel comfortable with. The map is created in one spot and I want to hand it around and read it from multiple places without the possibility to change it.


      or b) use const but don't use the map [] operator.
      But as far as I can see the only other way to access the elements in the map then is the const_iterator which is not suitable for me - I rather would need some kind of random access.

      Well, ok thanks for your answer.
      Maybe map is just not the right thing here - I'll look for something else.

      Thanks.

      Comment

      • weaknessforcats
        Recognized Expert Expert
        • Mar 2007
        • 9214

        #4
        Why insist on map::operator[] ???

        You can use map::find() instead. That will return the pair that has your key and value.

        Otherwise, you can write a new map iof your own be deriving from map (bs sure map has a virtual destructor) and then write an operator[] in your map that returns a const value for the key rather than a const_iterator.

        Please note, access in a map is not random, like an array. Access is based on a key and not and element number. The map::operator[] tries to make the map look like an array but it does return the value in a form that allows you to change it.

        Comment

        • jabbah
          New Member
          • Nov 2007
          • 63

          #5
          Originally posted by weaknessforcats
          Why insist on map::operator[] ???

          You can use map::find() instead. That will return the pair that has your key and value.
          Because I dont want to do something in logarithmic time, when it should be done in constant time.

          Otherwise, you can write a new map iof your own be deriving from map (bs sure map has a virtual destructor) and then write an operator[] in your map that returns a const value for the key rather than a const_iterator.
          That sounds like a very interessting idea - I think I'll try that. Very interessting. I have never thought about deriving from some stl thing. mhh...


          Please note, access in a map is not random, like an array. Access is based on a key and not and element number. The map::operator[] tries to make the map look like an array but it does return the value in a form that allows you to change it.
          I don't get that. What I meant when I said random was that I can access an arbitrary element in constant time - independent of whether I'm allowed to change the element or not. And as I expect a map to be something like a hashtable and I know my keys will all be unique (Im not actually using char as indicated in my example but pointers to struct), I would expect [] to execute in constant time.
          Although now that you made me think about it again I dont find any statement about execution time of [] in
          http://www.cppreferenc e.com/cppmap/map_operators.h tml

          Comment

          • weaknessforcats
            Recognized Expert Expert
            • Mar 2007
            • 9214

            #6
            Originally posted by jabbah
            What I meant when I said random was that I can access an arbitrary element in constant time - independent of whether I'm allowed to change the element or not. And as I expect a map to be something like a hashtable and I know my keys will all be unique (Im not actually using char as indicated in my example but pointers to struct), I would expect [] to execute in constant time.
            A map is a red-black binary tree. Access is not in constant time like an array.

            All STL associative containers use red-black trees.

            There is a hashmap but it is still non-standard.

            Comment

            • jabbah
              New Member
              • Nov 2007
              • 63

              #7
              Originally posted by weaknessforcats
              A map is a red-black binary tree. Access is not in constant time like an array.
              Ok, I see. So the execution times of both, operator[] and find() are logarithmic, right?

              I think, I'm going to put the map into a wrapper that provides something like
              Code:
              TYPE& operator[]( const key_type& key );
              const TYPE& operator[]( const key_type& key ) const;
              Besides the satisfaction of my const-request this would allow me to change from map to whatever, maybe a hash table in the future.

              Comment

              • weaknessforcats
                Recognized Expert Expert
                • Mar 2007
                • 9214

                #8
                Yes, you could change the implementation of the tree.

                Generally, I advise to not expose your implementation for that very reason. All of these STL containers should be fronted by an access class of your design and with your interface methods.

                Ditto for memory using new and delete. I always use a Create/Delete function and bury the allocation.

                Comment

                • jabbah
                  New Member
                  • Nov 2007
                  • 63

                  #9
                  Thanks weaknessforcats

                  Comment

                  • jabbah
                    New Member
                    • Nov 2007
                    • 63

                    #10
                    Errr, Im afraid Im back .... I tried to:

                    Originally posted by weaknessforcats
                    you can write a new map iof your own be deriving from map (bs sure map has a virtual destructor) and then write an operator[] in your map that returns a const value for the key rather than a const_iterator.
                    like this:

                    Code:
                    #include <iostream>
                    #include <map>
                    using namespace std;
                    
                    
                    template < typename keytype, typename TYPE >
                    class cmap: public std::map<keytype, TYPE> 
                    {
                    public:
                      virtual const TYPE& operator[](const keytype& k) const // this seems to keep me from accessing the non-const operator[]
                      //virtual const TYPE& get(const keytype& k) const 
                      {
                        // get rid of const so we are able to call operator[]()
                        const cmap<keytype,TYPE>& const_cmap = *this;
                        const std::map<keytype,TYPE>& const_map = const_cmap;
                        std::map<keytype,TYPE>& nonconst_map = const_cast < std::map<keytype,TYPE>& > ( const_map );
                        // retrieve the element and return it as const&
                        return nonconst_map[k];
                      }
                    
                      virtual ~cmap(){}
                    };
                    
                    
                    
                    int main (){
                    
                      cmap <char, double> mycmap;
                      mycmap['A'] = 0.2; // compile-error: "assignment of read-only location"
                      
                      const cmap<char, double>& const_mycmap = mycmap;
                      cout << const_mycmap['A'] << "\n";
                      //cout << const_mycmap.get('A') << "\n";
                    }
                    This doesnt compile due to line 29, so seemingly I do have a const operator[] now, but the non-const operator[] is gone.

                    If I exchange line 15 with 16 and 37 with 38 it works, but well then there is still no const operator[]() const.

                    Comment

                    • weaknessforcats
                      Recognized Expert Expert
                      • Mar 2007
                      • 9214

                      #11
                      You have to write your own cmap::operator[]. One for non-const cmaps and one for const cmaps.

                      No amount casting is going to help you.

                      Here is my try. It compiles and links but I didn't test it.
                      [code=cpp]
                      #include <iostream>
                      #include <map>
                      #include <exception>
                      using namespace std;


                      template < typename keytype, typename TYPE >
                      class cmap: public std::map<keytyp e, TYPE>
                      {
                      public:
                      TYPE& operator[](const keytype& k)

                      {
                      map::iterator theData = this->lower_bound(k) ;
                      if (theData == this->end())
                      {
                      exception obj("key not found");
                      throw obj;


                      }
                      return theData->second;
                      }
                      TYPE operator[](const keytype& k) const

                      {
                      map::const_iter ator theData = this->lower_bound(k) ;
                      if (theData == this->end())
                      {
                      exception obj("key not found");
                      throw obj;


                      }
                      return theData->second;
                      }
                      virtual ~cmap(){}
                      };



                      int main (){

                      cmap <char, double> mycmap;
                      mycmap['A'] = 0.2; // compile-error: "assignment of read-only location"

                      const cmap<char, double>& const_mycmap = mycmap;
                      cout << const_mycmap['A'] << "\n";
                      //cout << const_mycmap.ge t('A') << "\n";
                      }
                      [/code]

                      Lastly, the camp destructor does not need to be virtual unless you create a cmap and use it as a map* or map&. And if you do that, be sure the map destructor is virtual. If it's not, that the signal that map cannot be used polymorphically .

                      Comment

                      • jabbah
                        New Member
                        • Nov 2007
                        • 63

                        #12
                        Originally posted by weaknessforcats
                        You have to write your own cmap::operator[]. One for non-const cmaps and one for const cmaps.
                        ok, I had never guessed that!

                        And - ok, learned something again - obviously it is not possible to return a const TYPE& if there is no such object. which finally is the answer to my unspelled question "why is the const TYPE& operator[]() const not already implemented in std::map?". But the exception is a nice resolution to this. I think it suits my needs quite well, since if my code ever tries to call const_mycmap[key] for some undefined key, it is a bug anyways and I'll be thankful for the exception.

                        But contrary to your proposal, I don't want an exception in the case of accessing an undefined key in the non-const context. E.g. line 45 of your code raises an exception. Also it changes the semantics of the base class std::map in a surprising way.

                        Additionally, I needed to change the declaration of the iterators and replace the exception object. But this might be subject to different compilers.

                        Below is the version that works for me and with which I feel quite satisfied.

                        [code=cpp]
                        #include <iostream>
                        #include <map>
                        #include <exception>
                        using namespace std;


                        class message: public exception
                        {
                        protected:
                        string What;
                        public:
                        message( string m ): What(m) {}
                        ~message() throw() {}
                        virtual const char* what() const throw() {
                        return What.c_str();
                        }
                        };



                        template < typename keytype, typename TYPE >
                        class cmap: public std::map<keytyp e, TYPE>
                        {
                        public:

                        TYPE& operator[](const keytype& k)
                        {
                        // use the operator[] of the base class map
                        return map<keytype,TYP E>::operator[](k);
                        }


                        const TYPE& operator[](const keytype& k) const
                        {
                        //map::const_iter ator theData ; // g++ says:
                        // `template<class _Key, class _Tp, class _Compare, class _Alloc>
                        // class std::map' used without template parameters
                        // missing template arguments before "theData"

                        typename map< keytype, TYPE >::const_iterat or theData ;
                        // took me a while to figure out that (for whatever reason) typename
                        // is required here

                        theData = this->lower_bound(k) ;
                        if ( theData == this->end() )
                        {
                        //exception obj("key not found"); // g++ says:
                        // no matching function for call to
                        // `std::exception ::exception(con st char[14])'

                        message obj("key not found");
                        throw obj;
                        }
                        return theData->second;
                        }

                        virtual ~cmap(){}
                        };



                        int main ()
                        {

                        try
                        {
                        cmap <char, double> mycmap;
                        mycmap['A'] = 0.2;
                        cout << mycmap['A'] << " {" << &(mycmap['A']) << "}\n";

                        const cmap<char, double>& const_mycmap = mycmap;
                        cout << const_mycmap['A'] << " {" << &(const_mycm ap['A']) << "}\n";
                        cout << const_mycmap['B'] << "\n"; // raises an exception, which is good!
                        }
                        catch ( message e )
                        {
                        cout << "Exception: \"" << e.what() << "\"\n";
                        exit( EXIT_FAILURE );
                        }
                        }
                        [/code]

                        Comment

                        • jabbah
                          New Member
                          • Nov 2007
                          • 63

                          #13
                          bugfix: it should be

                          theData = this->find(k);

                          instead of

                          theData = this->lower_bound(k) ;

                          Comment

                          Working...