Is it safe to use `for(auto& e : cont)`? What is wrong with vector<bool>?

旧城冷巷雨未停 提交于 2019-12-12 14:33:34

问题


I have found that for (auto& e : cont) is sometimes used instead of ordinary for (auto e : cont) (where cont is some container, e.g. std::vector). I have found two reasons for it so far:

  1. Taking a reference should avoid copying the object (faster execution)
  2. Making a copy may be forbidden for some classes (e.g. std::thread)

After few tests I can see:

  1. for (auto& e : cont) works with any std::vector<T> except std::vector<bool>
  2. for (cont::reference e : cont) works with any std::vector<T> including std::vector<bool> (the obvious question here is:
    Should I rather use this instead of for (auto& e : cont)?
  3. std::vector<bool> is not considered to be real container and many think that it should be renamed (the implementation is fine and usefull, but should have different name like bitset or bitfield or dynamic_bitset)
  4. std::vector<bool> can be implemented in a way that for (auto& e : cont) would work as well (see my attempts below)

Here is the code I have used for testing:
(the trick is to use reference& iterator::operator * () { return *this; })

#include <vector>
#include <iostream>
#include <typeinfo>
using namespace std;

#define USE_STD_VECT_BOOL 0

#if USE_STD_VECT_BOOL
typedef vector<bool> BITS;
#else
typedef class bvect {
    unsigned data; // we could use vector<unsigned> but this is just an examle
    unsigned size;
public:
    bvect(): data(0), size(0) {}
    void push_back(bool value) {
        if(value) data |= (1u<<size);
        size++; }
    class reference {
        friend class bvect;
    protected:
        unsigned& data;
        unsigned  flag;
        reference(unsigned& data, unsigned flag)
        : data(data), flag(flag) {}
    public:
        operator bool() const {
            return data & flag; }
        reference& operator = (bool value) {
            if(value) data |= flag;
            else data &= ~flag;
            return *this; }
    };
    class iterator: protected reference  {
        friend class bvect;
        iterator(unsigned& data, unsigned flag)
        : reference(data, flag) {}
    public:
        typedef bool value_type;
        typedef bvect::reference reference;
        typedef input_iterator_tag iterator_category;
    //  HERE IS THE TRICK:
        reference& operator * () {
            return *this; }
        iterator& operator ++ () {
            flag <<= 1;
            return *this; }
        iterator operator ++ (int) {
            iterator tmp(*this);
            operator ++ ();
            return tmp; }
        bool operator == (const iterator& rhs) {
            return flag == rhs.flag; }
        bool operator != (const iterator& rhs) {
            return flag != rhs.flag; }
    };
    iterator begin() {
        return iterator(data, 1); }
    iterator end() {
        return iterator(data, 1<<size); }
} BITS;
#endif

int main() {
    BITS bits;
    bits.push_back(0);
    bits.push_back(1);
#if !USE_STD_VECT_BOOL
//  won't compile for vector<bool>
    for(auto& a : bits)
        cout << typeid(a).name()
          << " = " << (int)(bool)a
          << endl;
#endif
//  std::_Bit_Reference
    for(BITS::reference a : bits)
        cout << typeid(a).name()
          << " = " << (int)(bool)a
          << endl;
//  few more tests
    for(auto a : bits)
        cout << (int)(bool)a;
    for(bool a : bits)
        cout << (int)(bool)a;
    cout << endl;
}

Questions:

  1. Should I rather use for (cont::reference e : cont) instead of for (auto& e : cont)?
  2. What is wrong with the trick? Can it be enhanced to be fine for any use-case?
    EDIT: I am refering to bvect::reference& bvect::iterator::operator * () { return *this; } here.
  3. Can/Should STL be changed? (refering to vector<bool>)

FEEDBACK: Answers and Comments:

  1. Using for (auto&& e : cont) (for writing) or for (const auto& e : cont) (for reading/enumerating) seems to work in all cases. (Thanks go to dyp and Praetorian)
  2. Using typename iterator_traits<decltype(begin(cont))>::reference seems to work even for arrays (cont=boo[2]). (Yes, it is ugly but could be shortened using some template alias I think. I cannot think of counter-example where this would be needed, so, for now, this is not the solution. auto&& is)
  3. Standard says that iterator::operator * () have to return iterator::reference (not iterator::reference&), but still no clue why.

Final Verdict:

auto it = bits.begin();
auto&& e = *it; cout << (bool)e;
it++; cout << (bool)e;
cout << endl;

Output:

10

This is definitely bad. We should stick with the standard (iterator::operator * () have to return iterator::reference). Thank you :)


回答1:


vector<bool> is a specialization of the vector class template that stores the booleans in a bitfield for space optimization. Since you cannot return a reference to a bitfield, vector<bool>::reference is a class type, a proxy that represents a single bool. vector<bool>::operator[] returns this proxy class instance by value; the same applies to dereferencing a vector<bool>::iterator.

vector<bool> cont;
for (auto& e : cont) { ... }

Here you're attempting to bind an lvalue reference to an rvalue, which is not allowed.

Should I rather use for (cont::reference e : cont) instead of for (auto& e : cont)?
What is wrong with the trick? Can it be enhanced to be fine for any use-case?

The nice thing about a range-based for is that it works for a plain C array too. Using cont::reference will fail for those, as well as any iterable type that doesn't have a member type named reference. You should use for(auto const& e : cont) if you want read only access to the container elements within the loop, and for(auto&& e : cont) if you want to modify the elements.

In the latter case, auto&& e is a universal reference that can bind to lvalues and rvalues, so it works in the vector<bool> case too.




回答2:


For space optimization vector<bool> uses to store bool value only one bit (instead of one byte like bool). You can't get address of a bit in byte, due to things such operator[] doesn't work. In your case, you can't iterate bit by bit in this vector. You should better use set<bool>. There is a lot of discussion about vector<bool> if it should be in STL.



来源:https://stackoverflow.com/questions/25142557/is-it-safe-to-use-forauto-e-cont-what-is-wrong-with-vectorbool

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!