Date: Thu, 16 Nov 2023 16:31:42 +0400
Hi everyone, According to standard
8.6.4 The range-based for statement
The range-based for statement
for ( init-statementopt for-range-declaration : for-range-initializer )
statement
is equivalent to
{
init-statementopt auto &&range = for-range-initializer ;
auto begin = begin-expr ;
auto end = end-expr ;
for ( ; begin != end ; ++begin ) {
for-range-declaration = * begin ;
statement
}
}
begin-expr and end-expr are determined as follows:
-
if the for-range-initializer is an expression of class type C, the
unqualified-ids begin and end are looked up in the scope of C as if by
class member access lookup (6.5.5), and if both find at least one
declaration, begin-expr and end-expr are range.begin() and range.end(),
respectively;
This means that if I have code like this, it will not compile, regardless
of the fact that I provided begin() and end() in my class.
#include <iostream>
#include <memory>
#include <vector>
template<typename Iter>
struct range {
range(Iter b, Iter e)
: _begin(b)
, _end(e)
{}
Iter begin() && {
return _begin;
}
Iter end() && {
return _end;
}
private:
Iter _begin;
Iter _end;
};
template<std::size_t N, typename Container>
auto head(const Container& cnt) {
auto b = cnt.begin();
auto e = b;
std::advance(e, N);
return range(b, e);
}
int main() {
std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
for(auto v : head<7>(v)) {
std::cout << v << std::endl;
}
return 0;
}
The code introduces a template class range designed to represent a range of
elements using iterators. Notably, it employs rvalue references (`&&`) for
the begin() and end() member functions. Additionally, the code includes a
template function head that extracts the first N elements from a container.
Proposal
If I rewrite the range-based for like this
int main() {
std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// for(auto v : head<7>(v)) {
// std::cout << v << std::endl;
// }
{
auto&& range = head<7>(v);
auto begin = std::forward<decltype(range)>(range).begin();
auto end = std::forward<decltype(range)>(range).end();
for ( ; begin != end ; ++begin ) {
auto v = * begin ;
std::cout << v << std::endl;
}
}
return 0;
}
it will work properly, and the `begin()` and `end()` functions that are
provided will be called.
I think this might be beneficial in cases where we want to avoid iterator
invalidation, and we can encourage our class users to use it as an rvalue
in such a context.
Why is the range variable not forwarded? Is there a specific reason for
this?
8.6.4 The range-based for statement
The range-based for statement
for ( init-statementopt for-range-declaration : for-range-initializer )
statement
is equivalent to
{
init-statementopt auto &&range = for-range-initializer ;
auto begin = begin-expr ;
auto end = end-expr ;
for ( ; begin != end ; ++begin ) {
for-range-declaration = * begin ;
statement
}
}
begin-expr and end-expr are determined as follows:
-
if the for-range-initializer is an expression of class type C, the
unqualified-ids begin and end are looked up in the scope of C as if by
class member access lookup (6.5.5), and if both find at least one
declaration, begin-expr and end-expr are range.begin() and range.end(),
respectively;
This means that if I have code like this, it will not compile, regardless
of the fact that I provided begin() and end() in my class.
#include <iostream>
#include <memory>
#include <vector>
template<typename Iter>
struct range {
range(Iter b, Iter e)
: _begin(b)
, _end(e)
{}
Iter begin() && {
return _begin;
}
Iter end() && {
return _end;
}
private:
Iter _begin;
Iter _end;
};
template<std::size_t N, typename Container>
auto head(const Container& cnt) {
auto b = cnt.begin();
auto e = b;
std::advance(e, N);
return range(b, e);
}
int main() {
std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
for(auto v : head<7>(v)) {
std::cout << v << std::endl;
}
return 0;
}
The code introduces a template class range designed to represent a range of
elements using iterators. Notably, it employs rvalue references (`&&`) for
the begin() and end() member functions. Additionally, the code includes a
template function head that extracts the first N elements from a container.
Proposal
If I rewrite the range-based for like this
int main() {
std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// for(auto v : head<7>(v)) {
// std::cout << v << std::endl;
// }
{
auto&& range = head<7>(v);
auto begin = std::forward<decltype(range)>(range).begin();
auto end = std::forward<decltype(range)>(range).end();
for ( ; begin != end ; ++begin ) {
auto v = * begin ;
std::cout << v << std::endl;
}
}
return 0;
}
it will work properly, and the `begin()` and `end()` functions that are
provided will be called.
I think this might be beneficial in cases where we want to avoid iterator
invalidation, and we can encourage our class users to use it as an rvalue
in such a context.
Why is the range variable not forwarded? Is there a specific reason for
this?
Received on 2023-11-16 12:31:55