Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why isn't forwarding reference const?

The forwarding reference is supposed to forward the argument to another function, right? So why isn't it const?

template <typename T>
void func(const T&&);

Non-const reference allows the function to modify its arguments (instead of just forwarding them).

like image 973
beginpluses Avatar asked Jun 20 '19 23:06

beginpluses


People also ask

Why are Rvalue references called forwarding references?

These happen due to reference collapsing rules. In short, an rvalue reference to an lvalue reference collapses into an lvalue reference. The great Scott Meyers explained it way better than I can. He called these references universal references. The committee, however, decided calling them forwarding references instead.

Does C++11 solve the perfect forwarding problem?

To make things worse, C++11 adds rvalue references to the mix (which we'd also want to forward correctly), and this clearly isn't a scalable solution. To explain how C++11 solves the perfect forwarding problem, we have to first understand two new rules that were added to the language.

Why do we need perfect forwarding in C++?

Perfect forwarding is extremely useful, because it enables a kind of higher order programming. Higher order functions are functions that may take other functions as arguments or return them. Without perfect forwarding, higher order functions are cumbersome because there is no convenient way to forward arguments to wrapped functions.

Can you take a reference to a reference in C++?

Taking a reference to a reference is illegal in C++. However, it can sometimes arise in the context of templates and type deduction: What happens if we call this function as follows: In the template instantiation, T is explicitly set to int&. So what is the type of k inside?


1 Answers

Why isn't forwarding reference const?

Because it is desireable to be able to move a perfectly forwarded xvalue. An object cannot usually be moved from a const reference, because the argument of the move constructor needs to be non-const.

Furthermore, it is desirable to be able to bind lvalues into forwarding references. It is not possible to bind lvalues into const T&& which is a const rvalue reference - it is not a forwarding reference at all 1.

If you don't wish to move from the argument, but only constantly refer to it, then you don't need a forwarding reference. In that case a const lvalue reference is sufficient.

Example:

struct S {
     S() = default;
     S(S&&) = default;      // move
     S(const S&) = default; // copy
};

void foo(S fooarg);

template <typename T>
void bar(T&& bararg) { // forwarding reference
    foo(std::forward<T>(bararg));
}

// call site
S s;
bar(s);            // 1 copy
bar(S{});          // 2 move
bar(std::move(s)); // 3 move

Here we wish bararg to be moved into fooarg in cases 2 and 3 , and we wish it to be copied in case 1. Forwarding reference achieves this. A const rvalue reference does not, because it is not possible to pass the const reference to the move constructor.


Const rvalue references are only rarely useful. Standard library uses them in a few places:

template <class T> void as_const(const T&&) = delete;
template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;

The intention of these deleted overloads is to prevent calling the function with a temporary argument (rvalue). const prevents the argument from becoming a forwarding reference, which would bind to anything and therefore make any call deleted.

constexpr const T&& optional::operator*() const&&;
constexpr const T&& optional::value() const &&;

template <class T, class... Types>
constexpr const T&& get(const std::variant<Types...>&& v);

template< class T, class... Types >
constexpr const T&& get(const tuple<Types...>&& t) noexcept;

Above, const rvalue reference is used as a return type of a wrapper when accessing the wrapped value so the value category and constness of the wrapped value is maintained.


1 Standard (draft) says:

[temp.deduct.call] ... A forwarding reference is an rvalue reference to a cv-unqualified template parameter that does not represent a template parameter of a class template (during class template argument deduction ([over.match.class.deduct])). If P is a forwarding reference and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction.

like image 163
eerorika Avatar answered Sep 21 '22 05:09

eerorika