Topic: no const operator[] in STL map?


Author: "Jonathan H Lundquist" <fluxsmith@fluxsmith.com>
Date: 1999/01/27
Raw View
This thread has convinced me to add:
const T& operator[](const key_type&) const
To my implementation of map and hash_map at www.fluxsmith.com

My question is what exception should be thrown.  I find the standard useless
in this regard.  For instance, it essentially says domain_error is for
domain errors.  Until someone here corrects me... I have settled on
domain_error, however, it seems to me there is some degree of overlap
between domain_error, out_of_range, and invalid_argument.  This is an
example of where I think a reasonable argument could be made for any of the
three.

Anthony Shipman <als@aaii.com.au> wrote in message
news:916993358.766341@aloomba.aaii.oz.au...
>
>The version of STL supplied for IRIX 6.5 does not have the
> const T& operator[](const key_type&) const
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: sbnaran@localhost.localdomain (Siemel Naran)
Date: 1999/01/27
Raw View
On 27 Jan 99 07:40:06 GMT, Jonathan H Lundquist <fluxsmith@fluxsmith.com> wrote:

>This thread has convinced me to add:
>const T& operator[](const key_type&) const
>To my implementation of map and hash_map at www.fluxsmith.com

I like it, but it is non-conforming.  Clients using your www.fluxsmith.com
map classes may come to depend on the existence of a const operator[]
function that throws if the object does not exist.  When they port their
code to another compiler that does not use this extension, they're in
trouble.

You could use a macro NON_CONFORMING like this.

   class map {
      ...

      #if defined(NON_CONFORMING)
      const mapped_type& operator[](const key_type&) const; // may throw
      #endif

      ...
   };


>My question is what exception should be thrown.  I find the standard useless
>in this regard.  For instance, it essentially says domain_error is for
>domain errors.  Until someone here corrects me... I have settled on
>domain_error, however, it seems to me there is some degree of overlap
>between domain_error, out_of_range, and invalid_argument.  This is an
>example of where I think a reasonable argument could be made for any of the
>three.

I like "out_of_range" because it is as if you are accessing the map/array
out of bounds.

--
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: "Jonathan H Lundquist" <fluxsmith@fluxsmith.com>
Date: 1999/01/28
Raw View
My own mental process concluded that out_of_range was the *most* appropriate
when the index is assumed to be integral, i.e. vector.  In the case of map I
concluded it's a matter of the index being outside the domain of existing
keys.  However, I think we all really have to find a resolution of what the
exceptions mean that is certainly not clear (at least to me) from the
standard.  And Stroustrup's book is no help either, he doesn't say much
about it, and I would love for him to elucidate... but he implies he doesn't
like the standard design.

Siemel Naran <sbnaran@localhost.localdomain> wrote in message
news:slrn7auj7t.6u8.sbnaran@localhost.localdomain...
>On 27 Jan 99 07:40:06 GMT, Jonathan H Lundquist <fluxsmith@fluxsmith.com>
wrote:
>>domain errors.  Until someone here corrects me... I have settled on
>>domain_error, however, it seems to me there is some degree of overlap
>>between domain_error, out_of_range, and invalid_argument.  This is an
>>example of where I think a reasonable argument could be made for any of
the
>>three.
>
>I like "out_of_range" because it is as if you are accessing the map/array
>out of bounds.




[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: AllanW@my-dejanews.com
Date: 1999/01/26
Raw View
In article <slrn7anvsm.8jt.sbnaran@localhost.localdomain>,
  sbnaran@uiuc.edu wrote:
> On 25 Jan 99 02:34:00 GMT, Bruce Visscher <bvisscher@mindspring.com> wrote:
> >Siemel Naran wrote:
> >> But there is a logical alternative if const operator[] can't
> >> find the object with key_type 'key' in the map.  Just throw an
> >> exception.
> >
> >I agree that it would have been nice had the standard provided a const
> >operator[].  However, I don't agree that it should throw.  To me there is an
> >obvious alternative: return by value rather than by reference and return
> >mapped_type() if the key is not found.
>
> This would involve lots of copying if mapped_type were a large
> object.  Besides, the user may want to know if the lookup of
> the object failed as they may write code like this:
>
>    try
>    {
>       int i;
>       cin >> i;
>       map[i]=9;
>    }
>    catch (typeof(map)::non_existent) { }

But the catch never hits, because if map[i]=9 is valid then the
map must be non-const, so this is the non-const operator[] which
creates the element.

If what you meant was something like this:
    try {
        int i;
        cin >> i;
        cout << myMap[i];
    }
    catch (typeof(myMap)::non_existant)
    {
        // Do something else
    }

then we could rewrite it as
    int i;
    cin >> i;
    if (myMap.count(i))
    {
        cout << myMap[i];
    }
    else
    {
        // Do something else
    }

> >One useful application for map suggested in [Musser & Sauni] is that of a
> >sparse array.  With a const overload implemented like this you would get
> >consistent behavior (m[x] returns mapped_type() if x is not found whether or
> >not m is const).  In fact, the const overload would be the one to use if
> >wherever possible for a sparse vector.  You wouldn't want to allocate
> >10000000 elements if you did:
>
> Once one has an operator[] that throws an exception if the object does
> not exist, one can write an adaptor that returns a value, and a default
> value if the object does not exist.  But if one has an operator[] that
> returns a value, then one can't engineer this to turn it into a
> function that throws if the object didn't exist.

    myMap::operator[](Key &k) throw(non_existant)
    {
        if (count(k)<1) throw(non_existant());
        return BaseClass::operator[](k);
    }

> >There is precedence for my suggestion too (typing the following in from my
> >pdf copy, I hope I don't make any mistakes):
> >
> >26.3.2 Template class valarray
> >[...]
> >  template<class T>
> >  class valarray {
> >[...]
> >        // 26.3.2.3 element access:
> >        T            operator[](size_t) const;
> >        T&         operator[](size_t);
> >[...]
> >
> >It's not that unusual in my experience to see non-const overload of
> >operator[] return by reference while the const overload returns by value.
>
> The reason why valarray<T> can have its constant operator[] return
> by value is that there is something to return.
>
> I have no idea why valarray<T> returns by value.  It should just
> return by const reference.  As operator[] is inline, the lvalue
> returned (ie, const T&) should be converted at compile time to an
> rvalue (ie, T).

The const version returns by value so that it can still be used
in most places that a read-only reference can be used, and yet it
may also return a default-constructed object for which there is
no element to refer to.

> >This seems to bother some, though, so you could instead return by reference
> >to const and statically allocate a default constructed mapped_type when
> >needed instead (i.e., the first time map::operator[] const was invoked for a
> >non-existant key).

I did something similar a few years ago. As I recall, I had trouble
convincing my compiler to create a class static object of parameter
type, so I ended up creating one extra instance for each class.
More modern compilers shouldn't have that trouble.

> The use of static objects to represent null objects (so that you
> can return a null reference) may makes things harder for
> maintainance...
[continues about problems with "null object"]

Bruce Visscher wasn't writing about a null object, he was writing
about a default object.

Currently for non-const maps, operator[] initializes the element
by calling the null constructor. This allows you to write:

    ++myMap[key];

If your class initializes itself to some equivalent of 0, and
operator++ does the equivalent of adding 1, then this will
correctly count how many times each key was incremented.

The same thing ought to apply when reading a map, which can be
done when the map is read-only.

    std::cout << myMap[key];

ought to report how many times key was incremented, even if that
number is 0 times.

----
AllanW@my-dejanews.com is a "Spam Magnet" -- never read.
Please reply in USENET only, sorry.

-----------== Posted via Deja News, The Discussion Network ==----------
http://www.dejanews.com/       Search, Read, Discuss, or Start Your Own
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: Bruce Visscher <bvisscher@mindspring.com>
Date: 1999/01/25
Raw View
Siemel Naran wrote:

> On 22 Jan 1999 15:51:17 GMT, Anthony Shipman <als@aaii.com.au> wrote:
>
> >The version of STL supplied for IRIX 6.5 does not have the
> >       const T& operator[](const key_type&) const
> >operator that is listed in the STL tutorial by Musser and Saini.
> >
> >Has it been removed from the standard or is it a fault with the SGI STL?
>
> No, the standard does not have such an operator.
> I wish it did.

Me too.

> [snip]
>
> But there is a logical alternative if const operator[] can't
> find the object with key_type 'key' in the map.  Just throw an
> exception.
>

I agree that it would have been nice had the standard provided a const
operator[].  However, I don't agree that it should throw.  To me there is an
obvious alternative: return by value rather than by reference and return
mapped_type() if the key is not found.

One useful application for map suggested in [Musser & Sauni] is that of a
sparse array.  With a const overload implemented like this you would get
consistent behavior (m[x] returns mapped_type() if x is not found whether or
not m is const).  In fact, the const overload would be the one to use if
wherever possible for a sparse vector.  You wouldn't want to allocate
10000000 elements if you did:

    struct T {/*...*/};
    map<int, T> const& g();
    void f(map<int, T> const&);
    const map<int, T>& m=g();
    for(int i=0; i<10000000; ++i)
        f(m);.

> There is already a precedent for such behaviour.

There is precedence for my suggestion too (typing the following in from my
pdf copy, I hope I don't make any mistakes):

26.3.2 Template class valarray
[...]
  template<class T>
  class valarray {
[...]
        // 26.3.2.3 element access:
        T            operator[](size_t) const;
        T&         operator[](size_t);
[...]

It's not that unusual in my experience to see non-const overload of
operator[] return by reference while the const overload returns by value.

This seems to bother some, though, so you could instead return by reference
to const and statically allocate a default constructed mapped_type when
needed instead (i.e., the first time map::operator[] const was invoked for a
non-existant key).

Bruce Visscher
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: sbnaran@localhost.localdomain (Siemel Naran)
Date: 1999/01/25
Raw View
On 25 Jan 99 02:34:00 GMT, Bruce Visscher <bvisscher@mindspring.com> wrote:
>Siemel Naran wrote:

>> No, the standard does not have such an operator.
>> I wish it did.
>
>Me too.


>> But there is a logical alternative if const operator[] can't
>> find the object with key_type 'key' in the map.  Just throw an
>> exception.
>
>I agree that it would have been nice had the standard provided a const
>operator[].  However, I don't agree that it should throw.  To me there is an
>obvious alternative: return by value rather than by reference and return
>mapped_type() if the key is not found.

This would involve lots of copying if mapped_type were a large
object.  Besides, the user may want to know if the lookup of
the object failed as they may write code like this:

   try
   {
      int i;
      cin >> i;
      map[i]=9;
   }
   catch (typeof(map)::non_existent) { }


>One useful application for map suggested in [Musser & Sauni] is that of a
>sparse array.  With a const overload implemented like this you would get
>consistent behavior (m[x] returns mapped_type() if x is not found whether or
>not m is const).  In fact, the const overload would be the one to use if
>wherever possible for a sparse vector.  You wouldn't want to allocate
>10000000 elements if you did:

Once one has an operator[] that throws an exception if the object does
not exist, one can write an adaptor that returns a value, and a default
value if the object does not exist.  But if one has an operator[] that
returns a value, then one can't engineer this to turn it into a
function that throws if the object didn't exist.


>There is precedence for my suggestion too (typing the following in from my
>pdf copy, I hope I don't make any mistakes):
>
>26.3.2 Template class valarray
>[...]
>  template<class T>
>  class valarray {
>[...]
>        // 26.3.2.3 element access:
>        T            operator[](size_t) const;
>        T&         operator[](size_t);
>[...]
>
>It's not that unusual in my experience to see non-const overload of
>operator[] return by reference while the const overload returns by value.

The reason why valarray<T> can have its constant operator[] return
by value is that there is something to return.

I have no idea why valarray<T> returns by value.  It should just
return by const reference.  As operator[] is inline, the lvalue
returned (ie, const T&) should be converted at compile time to an
rvalue (ie, T).


>This seems to bother some, though, so you could instead return by reference
>to const and statically allocate a default constructed mapped_type when
>needed instead (i.e., the first time map::operator[] const was invoked for a
>non-existant key).

The use of static objects to represent null objects (so that you
can return a null reference) may makes things harder for
maintainance.  This is because the usual semantics of operator[]
is to return a reference to an existing object.  The user need
not bother to check whether the reference is valid; instead the
user can assume that the reference is valid.  When returning
by pointer, it makes sense for the user to check whether the
returned pointer is valid -- ie, they test the pointer against
'null' or '0'.

But returning a reference to a so-called null object means that
users may have to check their references against null.  This
appears to be a little non-intuitive.  Moreover, how do users
check their objects against null object?  Do they compare the
returned reference to the null object, ie. use operator==(T,T)?
Or do they compare the address of the returned reference against
the address of the null object, ie. use operator==(T*,T*)?  If
the latter, then is the null object const or not?

--
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: Ron Natalie <ron@sensor.com>
Date: 1999/01/25
Raw View
I would be happier if it just threw something in the case
where [] is used on a const map and the element doesn't exist.
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: Ron Natalie <ron@sensor.com>
Date: 1999/01/24
Raw View
Anthony Shipman wrote:
>
> The version of STL supplied for IRIX 6.5 does not have the
>         const T& operator[](const key_type&) const
> operator that is listed in the STL tutorial by Musser and Saini.
>
> Has it been removed from the standard or is it a fault with the SGI STL?

The third option is Musser and Saini are wrong.  There is no
"operator[]" for const maps in the standard.

Stroustrup (C++PL, 3ed) says "Subscripting a map adds a
default element when the key is not found.  Therefore,
there is no version of operator[]() for const maps."
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: Valentin Bonnard <bonnardv@pratique.fr>
Date: 1999/01/24
Raw View
Anthony Shipman wrote:
>
> The version of STL supplied for IRIX 6.5 does not have the
>         const T& operator[](const key_type&) const
> operator that is listed in the STL tutorial by Musser and Saini.
>
> Has it been removed from the standard or is it a fault with the SGI STL?

This operator is equivalent with insert thus mutating by definition
and thus cannot be const.

--

Valentin Bonnard                mailto:bonnardv@pratique.fr
info about C++/a propos du C++: http://pages.pratique.fr/~bonnardv/
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: Biju Thomas <bijuthom@ibm.net>
Date: 1999/01/24
Raw View
Siemel Naran wrote:
>
> And what if it can't find
> the object in the map?  It then creates an empty object (technically,
> it creates a std::pair<const key_type,mapped_type> with the mapped
> object initialized with the default constructor), and returns a
> reference to it.

Does it mean that the mapped_type stored in a map should always have a
default constructor? If I don't have a default constructor for the
mapped_type, is there any way for me to specify the value from which the
mapped_type object can be copy-constructed. (For example, like in the
constructor for vector.)

--
Regards,
Biju Thomas
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: sbnaran@localhost.localdomain (Siemel Naran)
Date: 1999/01/24
Raw View
On 24 Jan 99 14:01:23 GMT, Biju Thomas <bijuthom@ibm.net> wrote:

>Does it mean that the mapped_type stored in a map should always have a
>default constructor? If I don't have a default constructor for the
>mapped_type, is there any way for me to specify the value from which the
>mapped_type object can be copy-constructed. (For example, like in the
>constructor for vector.)

If you use operator[], then yes, a default constructor is required.
If you use insert(const T&), then a default constructor is not required.

--
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: hinnant@_anti-spam_lightlink.com (Howard Hinnant)
Date: 1999/01/24
Raw View
In article <36AA5AA6.2CC30DA4@ibm.net>, bijuthom@ibm.net wrote:

> Does it mean that the mapped_type stored in a map should always have a
> default constructor?

Yes, if you want to use operator[].

> If I don't have a default constructor for the
> mapped_type, is there any way for me to specify the value from which the
> mapped_type object can be copy-constructed. (For example, like in the
> constructor for vector.)

No.  Here is the standard-suggested implementation:

T& operator[](const key_type& x);

-1- Returns: (*((insert(make_pair(x, T()))).first)).second.

-Howard


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: als@aaii.com.au (Anthony Shipman)
Date: 1999/01/22
Raw View
The version of STL supplied for IRIX 6.5 does not have the
 const T& operator[](const key_type&) const
operator that is listed in the STL tutorial by Musser and Saini.

Has it been removed from the standard or is it a fault with the SGI STL?

--
Anthony Shipman,                "You've got to be taught before it's too late,
AAII, Melbourne, Australia       Before you are six or seven or eight,
als@aaii.com.au                  To hate all the people your relatives hate,
+61 3 92477679                   You've got to be carefully taught."  R&H


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: sbnaran@fermi.ceg.uiuc.edu (Siemel Naran)
Date: 1999/01/23
Raw View
On 22 Jan 1999 15:51:17 GMT, Anthony Shipman <als@aaii.com.au> wrote:

>The version of STL supplied for IRIX 6.5 does not have the
> const T& operator[](const key_type&) const
>operator that is listed in the STL tutorial by Musser and Saini.
>
>Has it been removed from the standard or is it a fault with the SGI STL?

No, the standard does not have such an operator.
I wish it did.

What happens if non-const operator[](const key_type& key) finds the
object with key_type 'key' in the map?  Then it just returns a
non-const reference to the mapped_type.  And what if it can't find
the object in the map?  It then creates an empty object (technically,
it creates a std::pair<const key_type,mapped_type> with the mapped
object initialized with the default constructor), and returns a
reference to it.  So the bottom line is that non-const operator[]
always returns a non-const reference to a mapped_type.

What happens if const operator[](const key_type& key) finds the
object with key_type 'key' in the map?  Then it just returns a
const reference to the mapped_type.  And what if it can't find
the object in the map?  It can't create an empty object as this
changes the map, and a const function of class map should not
change the map.  So there's nothing to return.  This is probably
the rational for disallowing the function.

But there is a logical alternative if const operator[] can't
find the object with key_type 'key' in the map.  Just throw an
exception.

There is already a precedent for such behaviour. What happens if
we dynamic_cast from Base& to Derived1&, and the object is really
a Derived2&.  As the value returned by dynamic_cast must be a
Derived1&, we now have nothing to return.  For pointer types, a
failed dynamic_cast just returns NULL.  But there is no such
thing as a null reference.  So dynamic_cast throws an exception.
Similarly map's const operator[] could throw an exception if the
object does not exist.

Note furthermore that the bottom line above
   So the bottom line is that non-const operator[] always returns
   a non-const reference to a mapped_type
is not correct.  The process of creating the empty object might
fail if there is no memory available.  (In fact, the default
constructor mapped_type::mapped_type() may throw any exception,
but this is unusual).  So the real bottom line is that non-const
operator[] returns a non-const reference to a mapped_type or
throws an exception.

--
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]