Range Based Loop: Get Item by Value or Reference to Const

Range based loop: get item by value or reference to const?

If you don't want to change the items as well as want to avoid making copies, then auto const & is the correct choice:

for (auto const &x : vec)

Whoever suggests you to use auto & is wrong. Ignore them.

Here is recap:

  • Choose auto x when you want to work with copies.
  • Choose auto &x when you want to work with original items and may modify them.
  • Choose auto const &x when you want to work with original items and will not modify them.

Can't modify the value of a reference in Range based loop

You can't modify a reference to x because it is const. It is const because iterating a std::set through loop gives only const values.

See solution with const_cast example code at the end of my answer.

It is known that std::set stores all entries in a sorted tree.

Now imagine if you can modify a variable when iterating a loop, it means that after modification sorted order of std::set might be changed. But std::set should always keep invariant of its tree, hence it can't allow to make any modifications thus gives only const values when iterating.

If you need to really modify set entry then you have to take it from set, delete from set and that add again to set. Even if sorted order is not changed after your modification, still you need to reinsert into set.

But there is a hacky workaround - you can make your method delMinentry as having const modifier. Then all fields that it modifies should be marked as mutable. Mutable fields allow modifications from const methods. And std::set allows to call const methods when iterating.

There is one more workaround - you make delMinterm() as const method, but inside this method do const_cast<Term &>(*this).delMintermNonConst(), in other words from const method you can call non-const method if you do const_cast. Also you can do const cast directly on loop variable if you're sure what you do, in other words if you modify Term in such a way that std::set sorted order is not changed:

for (auto &x : PI) {
const_cast<Term &>(x).delMinterm(i);
}

If delMinterm() results in such modification of a Term after which order of std::set may change then you can't do const_cast in code above. In other words if after delMinterm your operator < may give a different result between terms, then you can't do this const cast and you have to reinsert into set (delete Term and add again).

Also don't forget that after reinserting into set you have to redo set iteration loop again from start, because after change to inner structure you can't keep iterating loop running further, iterators are invalidated.

If set's order changes (hence you can't do const_cast) then you have to re-insert values of set, do this by copying values to vector, modifying them through delMinterm(), copying back to set, like this:

std::vector<Term> scopy(PI.cbegin(), PI.cend());
for (auto & x: scopy)
x.delMinterm(i);
PI = std::set<Term>(scopy.cbegin(), scopy.cend());

In a C++ range-based for loop, can begin() return a reference to a non-copyable iterator?

Yes, iterators must be copyable. This is because range iteration is logically equivalent to the following code:

    auto && __range = range_expression ;
for (auto __begin = begin_expr, __end = end_expr; __begin != __end; ++__begin) {

range_declaration = *__begin;
loop_statement

}

This is the C++11 version. C++17, and later, are slightly different, but the fundamental reason is the same: __begin and __end are auto, and not auto &, or something like that. They are a non-reference type. The "begin_expr" and "end_expr", in so many words, are the begin and end expressions that end up calling your custom begin() and end().

Even if your begin() and end() returns references, they get assigned to a non-reference type and, as such, must be copyable.

Note that even if this was not the case, the shown implementation is not very useful, since both references will always be so the same object, and so the begin and the end expression will end up being the same object, and always compare equal (hopefully).

C++ range based for loop over vector of pointers. How to capture elements by const reference?

The vector contains pointers, so that's what you get. If you want something else you need to loop over a different object.

C++20 ranges offers a way to do that. It wouldn't be very hard to roll your own variant.

#include <iostream>
#include <memory>
#include <vector>
#include <ranges>

class Foo {
public:
Foo(int a) : a{a} {}
int value() const { return a; }
private:
int a;
};

int main() {
std::unique_ptr<Foo> f1 = std::make_unique<Foo>(1);
std::unique_ptr<Foo> f2 = std::make_unique<Foo>(2);
std::vector<Foo *> v;
v.push_back(f1.get());
v.push_back(f2.get());

for (const auto& f : std::ranges::views::transform(v, [](auto ptr) -> const Foo& { return *ptr; })) {
std::cout << f.value() << '\n';
}

return 0;
}

Using references as control variable in range-based for loops in c++

I found this informative article that will help you:

I think the below quote from the article answers your question:

In the range based for loop, auto& will create references (plural) to the original elements in the range.

Also the different ways of using auto in a range-based for loop are covered in it.


for (auto x : range)

This will create a copy of each element in the range. Therefore, use this variant when you want to work with a copy.


for (const auto x : range)

The use of const auto may suggest that you want to work with an immutable copy of each element.


for (auto& x : range)

Use auto& when you want to modify elements in the range in non-generic code.


for (const auto& x : range)

Use const auto& when you want read-only access to elements in the range, even in generic code


for (auto&& x : range)

Use auto&& when you want to modify elements in the range in generic code


for (const auto&& x : range)

This variant will bind only to rvalues, which you will not be able to modify or move because of the const. This makes it less than useless. Hence, there is no reason for choosing this variant over const auto&.


for (decltype(auto) x : range) // C++14

It means: apply automatic type deduction, but use decltype rules. Whereas auto strips down top-level cv qualifiers and references, decltype preserves them.

range based for loop with an auto reference to a pointer

Your code compiles fine.

Nevertheless, there is not really a point using a reference here, if you don't like to change it.

Best practise here is

  • Use const auto &T if the content shall not be changed. The reference is important, if the type T of auto is large. Otherwise you will copy the object.

  • Use auto & T if you like to change the content of the container you are iterating.

Range based for loop through non-const literal c++

int x,y;
for(int& i:{std::ref(x),std::ref(y)}){
i=7;
}

auto& won't work, and you do have to repeat std::ref for each element.

Another trick is:

auto action=[&](auto&foo){
// code
};
action(v1);
action(v2);

You can write:

void foreach_arg(auto&&f, auto&&...args){
((void)f(decltype(args)(args)),...);
}

or pre-c++20 versions in more characters, then:

auto action=[&](auto&foo){
// code
};
foreach_arg(action, v1, v2);

If you want the arguments mentioned first, you can do:

auto foreacher(auto&&...args){
return [&](auto&&f){
((void)f(decltype(args)(args)),...);
};
}

and get:

foreacher(v1,v2)([&](auto&v){
// loop body
});

apologies for any typos. Just writing insomnia-code, untested as yet.

decltype thing is a short std::forward equivalent in this case (with auto&& args; with some other declarations it doesn't work).

auto arguments to functions are a c++20 thing.

Then,... is comma-fold execute, a c++17 thing.

auto arguments to lambdas are a c++14 thing.

All can be replaced with c++11 constructs (in this case) at the cost of a lot more verbosity.

Does lifetime of a const auto reference inside a ranged-based for loop extends to the end of the scope?

Does the lifetime for the reference last through the scope, or is this an undefined behaviour?

To start with, the following range-based for loop:

for (auto const& ref : q1) {
// ...
}

expands, as per [stmt.ranged]/1, to

{
auto &&range = (q1);
for (auto begin_ = range.begin(), end_ = range.end(); begin_ != end_;
++begin_) {
auto const& ref = *begin_;
// ...
}
}

Now, as per the docs for std::deque<T>::clear:

Erases all elements from the container. After this call, size() returns zero.

Invalidates any references, pointers, or iterators referring to contained elements. Any past-the-end iterators are also invalidated.

if the if (ref.x == 1) branch is ever entered, then past the point of q1.clear(); the iterator whose pointed-to object ref is referring to (here: the deque element) is invalidated, and any further use of the iterator is undefined behavior: e.g. incrementing it an dereferencing it in the next loop iteration.

Now, in this particular example, as the for-range-declaration is auto const& as compared to only auto &, if the *begin_ dereferencing (prior to invalidating iterators) resolves to a value and not a reference, this (temporary) value would bind to the ref reference and have its lifetime extended. However, std::deque is a sequence container and shall adhere to the requirements of [sequence.reqmts], where particularly Table 78 specifies:

/3 In Tables 77 and 78, X denotes a sequence container class, a denotes a value of type X [...]

Table 78:

  • Expression: a.front()
  • Return type: reference; (const_­reference for constant a)
  • Operational semantics: *a.begin()

Whilst not entirely water-proof, it seems highly likely that dereferencing a std::deque iterator (e.g. begin() or end()) yields a reference type, something both Clang and GCC agrees on:

#include <deque>
#include <type_traits>

struct S{};

int main() {
std::deque<S> d;
static_assert(std::is_same_v<decltype(*d.begin()), S&>, "");
// Accepted by both clang and gcc.
}

in which case the use of ref in

 q2.push_front(ref);

is UB after the q1.clear().

We may also note that the end() iterator, stored as end_, is also invalidated by the invocation of clear(), which is another source of UB once the invariant of the expanded loop is tested in the next iteration.



If it is an undefined behaviour, why is this working???

Trying to reason with undefined behavior is a futile exercise.



Related Topics



Leave a reply



Submit