问题
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:
- Taking a reference should avoid copying the object (faster execution)
- Making a copy may be forbidden for some classes (e.g.
std::thread
)
After few tests I can see:
for (auto& e : cont)
works with anystd::vector<T>
exceptstd::vector<bool>
for (cont::reference e : cont)
works with anystd::vector<T>
includingstd::vector<bool>
(the obvious question here is:
Should I rather use this instead offor (auto& e : cont)
?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 likebitset
orbitfield
ordynamic_bitset
)std::vector<bool>
can be implemented in a way thatfor (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:
- Should I rather use
for (cont::reference e : cont)
instead offor (auto& e : cont)
? - What is wrong with the trick? Can it be enhanced to be fine for any use-case?
EDIT: I am refering tobvect::reference& bvect::iterator::operator * () { return *this; }
here. - Can/Should STL be changed? (refering to
vector<bool>
)
FEEDBACK: Answers and Comments:
- Using
for (auto&& e : cont)
(for writing) orfor (const auto& e : cont)
(for reading/enumerating) seems to work in all cases. (Thanks go to dyp and Praetorian) - 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) - Standard says that
iterator::operator * ()
have to returniterator::reference
(notiterator::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 offor (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