6

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).

beginpluses
  • 497
  • 2
  • 9
  • Interesting question, it is possible that there is no use for this kind of signature. Either you do `T&&` or `T const&`. https://youtu.be/ZbVCGCy3mGQ?t=1194 – alfC Jun 20 '19 at 23:42
  • @alfC There are uses, but they are somewhat obscure, see my answer. – eerorika Jun 20 '19 at 23:47
  • Maybe, only maybe, it is a way to take anything and then *only* internally in the function promise not to modify it. Sort of `... void func(T&& t){ ... use t only as std::as_const(t) ... ; }` or as the video says, have the overload to force a certain resolution. – alfC Jun 20 '19 at 23:47
  • The `&&` means "You can modify the object, to suck out its guts", but the `const` means "You cannot modify the object". Impasse. – Eljay Jun 20 '19 at 23:57
  • @Eljay I'm not questioning that, but just to clarify, deleting isn't modifying, right? – Ted Lyngmo Jun 21 '19 at 00:27
  • Deleting makes the deleted function available for "best match" checking. – Eljay Jun 21 '19 at 00:28
  • @TedLyngmo "deleting" is an operation on a pointer. It doesn't matter whether the pointer is itself const, or whether it is a pointer to const, it can be deleted in either case (assuming it is a pointer value that can be deleted at all; i.e. it is a valid pointer value returned by `new`). – eerorika Jun 21 '19 at 00:32
  • Thanks - Glad that got in the comments. I found that a bit odd earlier so I thought it be best to get on record. – Ted Lyngmo Jun 21 '19 at 00:35

1 Answers1

10

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.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Could you add a section explaining how moving from an object might change the object? (e.g, setting the data pointer to null with std::vector or std::string?) – Alecto Irene Perez Jun 20 '19 at 23:34
  • 1
    Terminology note: [What are rvalues, lvalues, xvalues, glvalues, and prvalues?](https://stackoverflow.com/questions/3601602/what-are-rvalues-lvalues-xvalues-glvalues-and-prvalues) – user4581301 Jun 20 '19 at 23:55
  • 1
    I have a vague sense of what you are trying to say (I get lost with `xvalue` nomenclature), but is it fair to say that `ref(const T&&)` is there to forbid construction from temporary objects? – alfC Jun 20 '19 at 23:59
  • 2
    @alfC Yes, `ref(const T&&) = delete` is there to forbid construction from temporary (i.e. rvalue). And if it was `ref(T&&) = delete`, then it would prevent all usage, since the forwarding reference binds to anything. – eerorika Jun 20 '19 at 23:59
  • Do you know an example in which the `T const&&` case is not left unimplemented or deleted? – alfC Jun 21 '19 at 00:07
  • @alfC see the optional, variant and tuple examples in the answer. – eerorika Jun 21 '19 at 00:08