Imagine that you have this generic pseudo-code:
template<typename Iterable>
void f(Iterable&& iterable)
{
...
}
We want to handle rvalue and lvalue references to iterable objects1, and the idea is that the function handles the container performing operations element by element.
It is plausible that we want to forward the reference specification of the container to the elements. In other words, if iterable
is an rvalue reference, the function will have to move the elements from the container.
Using C++17, I would do
auto [begin, end] = [&] {
if constexpr(std::is_lvalue_reference_v<Iterable>)
return std::array{std::begin(iterable),
std::end(iterable)};
else
return std::array{
std::make_move_iterator(std::begin(iterable)),
std::make_move_iterator(std::end(iterable))};
}();
std::for_each(begin, end, [&](auto&& element)
{
...
});
Obviously, this is not the best code to maintain2, error prone and probably not so easy to optimize for the compiler.
My question is: it could be possible, for future C++ standards, to introduce the concept of forwarding range-based loops? It would be nice if this
for(auto&& el : std::move(iterable))
{
...
}
could handle el as rvalue reference. In this way, this would be possible:
template<typename Iterable>
void f(Iterable&& iterable)
{
for(auto&& el : std::forward<Iterable>(iterable))
{
/*
* el is forwarded as lvalue reference if Iterable is lvalue reference,
* as rvalue reference if Iterable is rvalue reference
*/
external_fun(std::forward<decltype(el)>(el));
}
}
I am concerned about code-breaking changes, but at the same time I am not able to think about situations in which passing a rvalue reference as argument of a range based loop is expected to work without moving objects.
As suggested, I tried to write down how I would change the 6.5.4 section of the standard. The draft can be read at this address.
Do you think that it would be possible to introduce this feature without introducing serious issues?
1Checked with C++20 concepts or static_asserts
2And it's quite worse without C++17
This won't work. Fundamentally there are two kinds of things you can iterate over: those that own the elements, and those that don't. For non-owning ranges, the value category of the range is immaterial. They don't own their elements and so you can't safely move from them. The range-based
for
loop must work with both kind of ranges.There are also corner cases to consider (e.g., proxy iterators). The range-based
for
loop is basically syntax sugar that imposes only a very minimal set of requirements on the thing being iterated over. The benefit is that it can iterate over lots of things. The cost is that it doesn't have much room to be clever.If you know that the iterable in fact owns its elements (so that moving is safe), then all you need is a function that forwards something according to the value category of some other thing: