Home > Blockchain >  C : Why can we bind elements in a vector returned by a function to non-const lvalue references?
C : Why can we bind elements in a vector returned by a function to non-const lvalue references?

Time:12-18

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.

  • Related