I am new to move semantics and want to understand what's happening in this piece of code:
vector<int> create_vector() {
return {0, 1, 2};
}
for (int &x : create_vector()) {
/* ... */
}
If I understand correctly, the function call create_vector()
returns an rvalue. Therefore, the elements inside it should be rvalues as well (i.e. 0, 1, 2 are rvalues). However, in the range for loop, we are binding these rvalues to a non-const lvalue reference int &x
, which shouldn't be allowed? I thought non-const lvalue references can only bind to lvalues, not rvalues.
What's happening here? Why is this allowed?
CodePudding user response:
You're returning the Container with rvalue, but the individual elements are being returned after calling ::begin()
on the vector and then calling the operator*()
on the returned iterator which returns a reference as you can see here:
// Forward iterator requirements
_GLIBCXX20_CONSTEXPR
reference
operator*() const _GLIBCXX_NOEXCEPT
{ return *_M_current; }
If you write the for loop in the cppinsights.io then you'll see that the for loop is calling .begin
and then it's calling the operator*()
:
#include <vector>
using namespace std;
int foo() {
int res;
for (auto& val : vector<int>{}) {
res = val;
}
return res;
}
Here's the link that produces this from the above example:
#include <vector>
using namespace std;
int foo()
{
int res;
{
std::vector<int, std::allocator<int> > && __range1 = std::vector<int, std::allocator<int> >{};
__gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > > __begin1 = __range1.begin();
__gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > > __end1 = __range1.end();
for(; !__gnu_cxx::operator==(__begin1, __end1); __begin1.operator ()) {
int & val = __begin1.operator*();
res = res val;
}
}
return res;
}
Here's the vector::begin on cppreference; and here's a link to itrator_traits which can be used to get the reference/pointer type of vector's iterator like this:
using your_vector = std::vector<int>;
using vector_iterator = typename your_vector::iterator;
using traits = std::iterator_traits<vector_iterator>;
using reference_type = typename traits::reference;
using pointer_type = typename traits::pointer;
There are easier ways to get these types but it's the universal way for standard containers.