According to the C++23 standard, what is the output of this program?
#include <iostream>
#include <utility>
int y(int &) { return 1; }
int y(int &&) { return 2; }
template <class T> int f(T &&x) { return y(x); }
template <class T> int g(T &&x) { return y(std::move(x)); }
template <class T> int h(T &&x) { return y(std::forward<T>(x)); }
int main() {
int i = 10;
std::cout << f(i) << f(20);
std::cout << g(i) << g(20);
std::cout << h(i) << h(20);
return 0;
}The T&& in the templated function doesn't necessarily result in an rvalue reference, it depends on the type that is used to instantiate the template. If instantiated with an lvalue, it collapses to an lvalue reference, if instantiated with an rvalue, it collapses to an rvalue reference.
112212
The T&& in the templated function doesn't necessarily result in an rvalue reference, it depends on the type that is used to instantiate the template. If instantiated with an lvalue, it collapses to an lvalue reference, if instantiated with an rvalue, it collapses to an rvalue reference. See note [1].
Scott Meyers has written a very good article about this, where he introduces the concept of "universal references" (the official term is "forwarding reference") https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers.
In this example, all three functions are called once with an lvalue and once with an rvalue. In all cases, calling with an lvalue (i) collapses T&& x to int& x (an lvalue reference), and calling with an rvalue (20) collapses T&& x to int&& x (an rvalue reference). Inside the functions, x itself is always an lvalue, no matter if its type is an rvalue reference or an lvalue reference.
y(int&) is called for both cases. Output: 11.std::move(x) obtains an rvalue reference, and y(int&&)is called for both cases. Output: 22.std::forward(x) obtains an lvalue reference when x is an lvalue reference, and an rvalue reference when x is an rvalue reference, resulting in first calling y(int&) and then calling y(int&&). Output: 12.Note [1]: §[dcl.ref]¶6 in the standard:
If a typedef-name (§[dcl.typedef], §[temp.param]) or a decltype-specifier (§[dcl.type.decltype]) denotes a type
TRthat is a reference to a typeT, an attempt to create the type “lvalue reference to cvTR” creates the type “lvalue reference toT”, while an attempt to create the type “rvalue reference to cvTR” creates the typeTR.
The example at the end of that paragraph is worth a look.
Note from the contributor: This demonstrates Scott Meyers's advice to use std::forward for forwarding references, and std::move for rvalue references.