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 typeT
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 constanta
)- 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
How to Have Functions Inside Functions in C++
Is a String Literal in С++ Created in Static Memory
Why Copying Stringstream Is Not Allowed
Cmake Error At Cmakelists.Txt:30 (Project): No Cmake_C_Compiler Could Be Found
Difference in Make_Shared and Normal Shared_Ptr in C++
Why Does Rand() Yield the Same Sequence of Numbers on Every Run
Examples of Good Gotos in C or C++
Why Does Printf Not Print Out Just One Byte When Printing Hex
Regular Expression to Detect Semi-Colon Terminated C++ For & While Loops
Sorting Zipped (Locked) Containers in C++ Using Boost or the Stl
Is It Safe to Delete a Null Pointer
How to Pass a Unique_Ptr Argument to a Constructor or a Function
Why Isn't It Legal to Convert "Pointer to Pointer to Non-Const" to a "Pointer to Pointer to Const"
A Confusing Detail About the Most Vexing Parse
What Are Transparent Comparators