3

I think about forwarding references as part of perfect forwarding. I was trying to explain it to someone and realized I didn't know: Can it ever deduce T&& as some non-reference type?

#include <type_traits>
#include <utility>

template <typename Expected>
void checkDeduction(auto&& x) {
    static_assert(std::is_same_v<Expected, decltype(x)>);
}

int main() {
    checkDeduction<int&&>(1);
    const volatile int x = 0;
    checkDeduction<const volatile int&>(x);
    checkDeduction<const volatile int&&>(std::move(x)); // Weird, but whatever.   
    // Is there anything for which checkDeduction<int>(foo) will compile?
}

https://godbolt.org/z/cr8K1dsKa

I think the answer is "no", that it's always either an rvalue or lvalue reference.

I think this is because if f forwards to g:

decltype(auto) f(auto&& x) { return g(std::forward<decltype(x)>(x)); }

it's not that f has the same signature as g, but rather that f is compiled with the value categories that the caller of f provides, so if g takes int, and you call f(1) then f gets an int&& and forwards that to g. At that call to g, the int&& decays to an int.

Ben
  • 9,184
  • 1
  • 43
  • 56

1 Answers1

0

Plainly T&& is some kind of reference; the interesting question is whether T will always be deduced as a reference type. The answer is no: if the argument is an lvalue of type U, T will be U&, but if it’s an rvalue, T will just be U.

There is a sort of missed opportunity here: the language could say that a prvalue is deduced as U and an xvalue is deduced as U&& (which would still collapse with the “outer” reference operator). However, the judgment was that that distinction didn’t matter, at least not enough to justify creating additional template specializations everywhere.

It’s still possible to write f<X&&>(…) to force a forwarding reference’s template parameter to be an rvalue reference, although this wouldn’t usually accomplish anything.

Davis Herring
  • 36,443
  • 4
  • 48
  • 76