Overloaded [] operator on template class in C++ with const / nonconst versions
Asked Answered
M

6

10

Whew, that was a long title.

Here's my problem. I've got a template class in C++ and I'm overloading the [] operator. I have both a const and a non-const version, with the non-const version returning by reference so that items in the class can be changed as so:

myobject[1] = myvalue;

This all works until I use a boolean as the template parameter. Here's a full example that shows the error:

#include <string>
#include <vector>
using namespace std;

template <class T>
class MyClass
{
    private:
        vector<T> _items;

    public:

        void add(T item)
        {
            _items.push_back(item); 
        }

        const T operator[](int idx) const
        {
            return _items[idx];
        }

        T& operator[](int idx)
        {
            return _items[idx];
        }

};


int main(int argc, char** argv)
{
    MyClass<string> Test1;      //  Works
    Test1.add("hi");
    Test1.add("how are");
    Test1[1] = "you?";


    MyClass<int> Test2;         //  Also works
    Test2.add(1);
    Test2.add(2);
    Test2[1] = 3;


    MyClass<bool> Test3;        // Works up until...
    Test3.add(true);
    Test3.add(true);
    Test3[1] = false;           // ...this point. :(

    return 0;
}

The error is a compiler error and the message is:

error: invalid initialization of non-const reference of type ‘bool&’ from a temporary of type ‘std::_Bit_reference’

I've read up and found that STL uses some temporary data types, but I don't understand why it works with everything except a bool.

Any help on this would be appreciated.

Meekins answered 11/8, 2010 at 13:37 Comment(2)
Read this article: gotw.ca/publications/mill09.htmMinster
It can be fixed. See below. You just need to return the same type that the operator[] returns (which is usually T& (but not always as in the case of vector<bool>))Corina
A
8

Because vector<bool> is specialized in STL, and does not actually meet the requirements of a standard container.

Herb Sutter talks about it more in a GOTW article: http://www.gotw.ca/gotw/050.htm

Alston answered 11/8, 2010 at 13:40 Comment(3)
Amazing. I've been using STL for years and never run into this. Thank god I abstracted the vector class away, or I'd be in a world of hurt right now. Thanks for the answer.Meekins
All true and explains why it does not work as described above. But that does not mean the above code is unfix able. The problems only really arise when you start trying to obtain the address of any particular element that things start to fall apart badly (which is not the case here).Corina
I think the bigger problem is, as that article mentions, that the STL branches its behavior depending on the input. This means that behavior can be drastically different depending on the input. Very poor coding in my opinion.Meekins
R
6

A vector<bool> is not a real container. Your code is effectively trying to return a reference to a single bit, which is not allowed. If you change your container to a deque, I believe you'll get the behavior you expect.

Reneareneau answered 11/8, 2010 at 13:43 Comment(0)
R
5

A vector<bool> is not implemented like all other vectors, and does not work like them either. You are better off simply not using it, and not worrying if your code can't handle its many peculiarities - it is mostly considered to be A Bad Thing, foisted on us by some unthinking C++ Standard committee members.

Robtrobust answered 11/8, 2010 at 13:40 Comment(1)
And even the Standard Committee members soon realized their mistake, and tried to removed, but such is the nature of Standards, it's stuck in there....Opuscule
C
4

Some monor changes to your class should fix it.

template <class T>
class MyClass
{ 
    private:
        vector<T> _items;

    public:

        // This works better if you pass by const reference.
        // This allows the compiler to form temorary objects and pass them to the method.
        void add(T const& item)
        {
            _items.push_back(item);
        }

        // For the const version of operator[] you were returning by value.
        // Normally I would have returned by const ref.

        // In normal situations the result of operator[] is T& or T const&
        // But in the case of vector<bool> it is special 
        // (because apparently we want to pack a bool vector)

        // But technically the return type from vector is `reference` (not T&) 
        // so it you use that it should compensate for the odd behavior of vector<bool>
        // Of course const version is `const_reference`

        typename vector<T>::const_reference operator[](int idx) const
        {
            return _items[idx];
        }

        typename vector<T>::reference operator[](int idx)
        {
            return _items[idx];
        }
};  
Corina answered 11/8, 2010 at 14:8 Comment(1)
+1 for finding an actual solution :). I probably won't go this route, simply because I do not like bending over backwards to satisfy the nuances of a third party library. The class I'm writing abstracts STL, thus the user should not be able to see any remaining artifacts from STL in the API :P. Thanks for the input!Meekins
P
1

As the other answers point out, a specialization is provided to optimize for space allocation in the case of vector< bool>.

However you can still make your code valid if you make use of vector::reference instead of T&. In fact it is a good practice to use container::reference when referencing data held by a STL container.

T& operator[](int idx)

becomes

typename vector<T>::reference operator[](int idx)

Of course ther is also a typedef for const reference:

const T operator[](int idx) const

and this one becomes (removing the useless extra copy)

typename vector<T>::const_reference operator[](int idx) const
Profit answered 11/8, 2010 at 14:14 Comment(0)
M
1

The reason for the error is that vector<bool> is specialized to pack the boolean values stored within and vector<bool>::operator[] returns some sort of proxy that lets you access the value.

I don't think a solution would be to return the same type as vector<bool>::operator[] because then you'd be just copying over the regrettable special behavior to your container.

If you want to keep using vector as the underlying type, I believe the bool problem could be patched up by using a vector<MyBool> instead when MyClass is instantiated with bool.

It might look like this:

#include <string>
#include <vector>
using namespace std;

namespace detail
{
    struct FixForBool
    {
        bool value;
        FixForBool(bool b): value(b) {}
        operator bool&() { return value; }
        operator const bool& () const { return value; }
    };

    template <class T>
    struct FixForValueTypeSelection
    {
        typedef T type;
    };

    template <>
    struct FixForValueTypeSelection<bool>
    {
        typedef FixForBool type;
    };

}

template <class T>
class MyClass
{
    private:
        vector<typename detail::FixForValueTypeSelection<T>::type> _items;

    public:

        void add(T item)
        {
            _items.push_back(item);
        }

        const T operator[](int idx) const
        {
            return _items[idx];
        }

        T& operator[](int idx)
        {
            return _items[idx];
        }

};


int main(int argc, char** argv)
{
    MyClass<string> Test1;      //  Works
    Test1.add("hi");
    Test1.add("how are");
    Test1[1] = "you?";


    MyClass<int> Test2;         //  Also works
    Test2.add(1);
    Test2.add(2);
    Test2[1] = 3;


    MyClass<bool> Test3;        // Works up until...
    Test3.add(true);
    Test3.add(true);
    Test3[1] = false;           // ...this point. :(

    return 0;
}
Manouch answered 11/8, 2010 at 15:57 Comment(3)
Elegant ... I probably won't use it (simply because I have no attachment to vector--this class is abstracting vector anyway), but that is an elegant solution if vector has to be used.Meekins
I myself am having second doubts, though. I still wouldn't recommend taking the address with &my_vec[0].Manouch
Working with the addresses of elements in STL just as risky as working with iterators in STL. Any time that an iterator is invalidated (by adding a new element), the address can potentially be invalidated. Either way, this example class just wraps around the vector<> class. By doing that, it simply passes along the same level of risk as the vector<> class.Meekins

© 2022 - 2024 — McMap. All rights reserved.