Range-For-Loops and Std::Vector<Bool>

Range-for-loops and std::vector bool

Because std::vector<bool> is not a container !

std::vector<T>'s iterators usually dereference to a T&, which you can bind to your own auto&.

std::vector<bool>, however, packs its bools together inside integers, so you need a proxy to do the bit-masking when accessing them. Thus, its iterators return a Proxy.

And since the returned Proxy is an prvalue (a temporary), it cannot bind to an lvalue reference such as auto&.

The solution : use auto&&, which will correctly collapse into an lvalue reference if given one, or bind and maintain the temporary alive if it's given a proxy.

Ranged based for loop for vector of booleans

TL;DR: The proxy object knows how to read and write the single bits, regardless of how you keep it. Converting the proxy object to bool loses that information.


for (auto&& x : v)
x = !x;

and

for (auto x : v)
x = !x;

have the same behavior because in each case the proxy object (std::vector<bool>::reference) obtained from dereferencing a std::vector<bool>::iterator is stored in x. Whether the proxy object is stored by value or reference doesn't matter - its behavior of modifying the proxied bit is the same.

In

for (bool &&x : v)
x = !x;

and

for (bool x : v)
x = !x;

the proxy object is implicitly converted to a bool. This necessarily loses the information needed (and thus the capability) to affect the compressed bit.

Note that these are all implementation-defined. Your implementation is allowed to forego the space optimization too, in which case the behavior you see could be different. Only auto&& works in every case.

Range-for-loops and std::vector bool

Because std::vector<bool> is not a container !

std::vector<T>'s iterators usually dereference to a T&, which you can bind to your own auto&.

std::vector<bool>, however, packs its bools together inside integers, so you need a proxy to do the bit-masking when accessing them. Thus, its iterators return a Proxy.

And since the returned Proxy is an prvalue (a temporary), it cannot bind to an lvalue reference such as auto&.

The solution : use auto&&, which will correctly collapse into an lvalue reference if given one, or bind and maintain the temporary alive if it's given a proxy.

how to complement the values of std::vector bool using range for loop taking element by reference

you can bind them (the returned proxy object) to rvalue reference

for (auto&& bit : rep)
bit = !bit;

godbolt

What's the difference between & and && in a range-based for loop?

7 years after I asked this question, I feel qualified to provide a more complete answer.

I'll start by saying that the code I chose back then is not ideal for the purpose of the question. That's because there is no difference between & and && for the example.

Here's the thing: both

std::vector<int> v = {0, 1, 2, 3, 4, 5};

for (auto& i : v)
{
std::cout << ++i << ' ';
}

std::cout << '\n';

and

std::vector<int> v = {0, 1, 2, 3, 4, 5};

for (auto&& i : v)
{
std::cout << ++i << ' ';
}

std::cout << '\n';

are equivalent.

Here's proof:

#include <vector>

std::vector<int> v;

void f()
{
for (auto& i : v)
{
static_assert(std::is_same<decltype(i), int&>::value);
}

for (auto&& i : v)
{
static_assert(std::is_same<decltype(i), int&>::value);
}
}

But why?

Like David G said in the comments, a rvalue reference to a lvalue reference becomes a lvalue reference due to reference collapsing, eg

#include <type_traits>
using T1 = int&;
using T2 = T1&&;
static_assert(std::is_same<T1, T2>::value);

Note that this, however, is different:

for (int&& i : v)
{
// ...
}

and will fail, since a rvalue reference can't bind to a lvalue. Reference collapsing doesn't apply to this case, since there is no type deduction.

TLDR: for the standard containers, the difference between & and && in a range-based for loop is:

  • value_type& is valid
  • value_type&& is not valid
  • Both auto& and auto&& are equivalent to value_type&

Now let's try the opposite: an iterable object that returns rvalues.

#include <iostream>

struct Generated
{
int operator*() const
{
return i;
}

Generated& operator++()
{
++i;
return *this;
}

bool operator!=(const Generated& x) const
{
return i != x.i;
}

int i;
};

struct Generator
{
Generated begin() const { return { 0 }; }
Generated end() const { return { 6 }; }
};

int main()
{
Generator g;

for (const auto& i : g)
{
std::cout << /*++*/i << ' ';
}
std::cout << '\n';

for (auto&& i : g)
{
std::cout << ++i << ' ';
}
std::cout << '\n';
}

Here, auto& doesn't work, since you can't bind a non-const lvalue to a rvalue.

Now we actually have const int& and int&&:

Generator g;

for (const auto& i : g)
{
static_assert(std::is_same<decltype(i), const int&>::value);
}

for (auto&& i : g)
{
static_assert(std::is_same<decltype(i), int&&>::value);
}

Range-based for loop with special case for the first item

Maybe a for_first_then_each is what you're looking for? It takes your range in terms of iterators and applies the first function to the first element and the second function to the rest.

#include <iostream>
#include <vector>

template<typename BeginIt, typename EndIt, typename FirstFun, typename OthersFun>
void for_first_then_each(BeginIt begin, EndIt end, FirstFun firstFun, OthersFun othersFun) {
if(begin == end) return;
firstFun(*begin);
for(auto it = std::next(begin); it != end; ++it) {
othersFun(*it);
};
}

int main() {

std::vector<int> v = {0, 1, 2, 3};

for_first_then_each(v.begin(), v.end(),
[](auto first) { std::cout << first + 42 << '\n'; },
[](auto other) { std::cout << other - 42 << '\n'; }
);

// Outputs 42, -41, -40, -39

return 0;
}

Why does c++ for each loops accept r-values but std::ranges do not?

The for-loop works fine because the vector<int> (rvalue) is valid while the loop is evaluated.

The second code snippet has 2 issues:

  • The predicate must return a bool
  • the vector<int> you are using in the views object is dangling by the time it is evaluated, so the compiler does not accept it.

If instead of a vector<int> rvalue, the object cannot dangle (lvalue) or holds references into something else that cannot not dangle, this would work.

For example:

auto vec = std::vector<int>({0, 1});
auto vw = vec | std::ranges::views::filter([](int i) { std::cout << i; return true; });

Or you can pass an rvalue that has references to a non-dangling vector<int> object, like std::span:

auto vec = std::vector<int>({0, 1});
auto vw = span{vec} | std::ranges::views::filter([](int i) { std::cout << i; return true; })

Here, std::span is still a rvalue, but the compiler accepts it because the author of std::span has created an exception for it.

For a custom type with references into something else (typically views), you can create your own exception by creating a specialization of the template variable enable_borrowed_range.

In the case of vector it would look like this (but don't do this!):

template<> // don't do this
inline constexpr bool ranges::enable_borrowed_range<std::vector<int>> = true;

Your code now compiles but it will trigger undefined behavior because the vector is dangling once the view is evaluated.

Range-based for loop with auto specifier combined with static_cast

Not that this is a good idea as written, but this might be a useful example of a more general transform concept (and an evil lambda trick):

for(auto sv : v |
views::transform([](std::string_view x) {return x;})) …


Related Topics



Leave a reply



Submit