4

if I take for example the ranges::fill algorithm:

https://en.cppreference.com/w/cpp/algorithm/ranges/fill

the signature is:

template< class T, ranges::output_range<const T&> R >
constexpr ranges::borrowed_iterator_t<R> fill( R&& r, const T& value );

And an example use:

#include <algorithm>
#include <vector>
#include <iostream>
 
int main()
{
    std::vector<int> v{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    namespace ranges = std::ranges;
    ranges::fill(v, 10);
}

Why does ranges::fill take a rvalue reference as argument ( R&& r) ? I would have expected it to take a lvalue reference ( R& r) instead.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
Ludovic Aubert
  • 9,534
  • 4
  • 16
  • 28

1 Answers1

2

Since R is a template parameter, R&& is not an rvalue reference, it is a forwarding/universal reference.

Forwarding references

Forwarding references are a special kind of references that preserve the value category of a function argument, making it possible to forward it by means of std::forward. Forwarding references are either:

  1. function parameter of a function template declared as rvalue reference to cv-unqualified type template parameter of that same function template:

    template<class T>
    int f(T&& x) {                    // x is a forwarding reference
        return g(std::forward<T>(x)); // and so can be forwarded
    }
    
    int main() {
        int i;
        f(i); // argument is lvalue, calls f<int&>(int&), std::forward<int&>(x) is lvalue
        f(0); // argument is rvalue, calls f<int>(int&&), std::forward<int>(x) is rvalue
    }
    
    template<class T>
    int g(const T&& x); // x is not a forwarding reference: const T is not cv-unqualified
    
    template<class T> struct A {
        template<class U>
        A(T&& x, U&& y, int* p); // x is not a forwarding reference: T is not a
                                 // type template parameter of the constructor,
                                 // but y is a forwarding reference
    };
    
  2. auto&& except when deduced from a brace-enclosed initializer list:

    auto&& vec = foo();       // foo() may be lvalue or rvalue, vec is a forwarding reference
    auto i = std::begin(vec); // works either way
    (*i)++;                   // works either way
    g(std::forward<decltype(vec)>(vec)); // forwards, preserving value category
    
    for (auto&& x: f()) {
      // x is a forwarding reference; this is the safest way to use range for loops
    }
    
    auto&& z = {1, 2, 3}; // *not* a forwarding reference (special case for initializer lists)
    
dfrib
  • 70,367
  • 12
  • 127
  • 192
  • Sadly, it got way less readable than previously, due to lack of `typename` or `class` in OP's particular case. Now the template argument can be anything really... – alagner Jan 27 '22 at 11:02
  • @alagner I'm personally still yet to decide if things are harder to read due to more implicit/brief mechanisms, or just less familiar - I'm leaning towards the latter myself (concepts, removing need for typename when non-ambiguous and so on). – dfrib Jan 27 '22 at 11:05
  • @drfib maybe I should have written "at this stage". No strong opinions here, either. For sure I'm used to the pre-20 form. – alagner Jan 27 '22 at 11:07