1
votes

According to https://en.cppreference.com/w/cpp/language/reference, forwarding references are either,

  1. function parameter of a function template declared as rvalue reference.
  2. auto&&.

Here at https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers Scott Meyers explained that, although vector::push_back(T&& x) takes a T&&, it is not a universal reference. It's just a plain rvalue reference.

However, my below code compiles and runs well:

#include <iostream>

template<typename T>
class MyClass
{
public:
    void f(T&& x)
    {
        std::cout << __FUNCSIG__ << std::endl;
    }
};

int main()
{
    int i = 10;

    MyClass<int&> myClass1;
    myClass1.f(i); //void __thiscall MyClass<int &>::f(int &)

    MyClass<int&&> myClass2;
    myClass2.f(10); //void __thiscall MyClass<int &&>::f(int &&)
}

It sounds like T&& is treated as a forwarding reference here, because f(T&& x) accepts both lvalue and rvalue references. But f(T&& x) is a class template member; it is not a standalone function template. Is this against the forwarding reference definition that I mentioned above?

<<\ADD-->

In the case of the forwarding reference in a function template, the function template itself also needs to instantiate based on a specific type T. We can express this explicitly:

template<class T>
void f(T&& x)
{}

int main() {
    int i = 10;
    f<int&>(i); // argument is lvalue
    f<int&&>(10); // argument is rvalue
}

f<\int&> and f<\int&&> are two different instances of the above function template. They are not pointing to the same function address behind the scenes either, similar to class template instantiation. Of course, we need class object to use its functions.

<--End ADD>

2

2 Answers

3
votes

because f(T&& x) accepts both lvalue and rvalue references

It doesn't. You aren't comparing the same member function here. MyClass<int&>::f is a member function of one class that accepts lvalues only. Meanwhile MyClass<int&&>::f is a member function of another class, and it accepts only rvalues.

A class template is not a class. It's a cookie cutter from which classes are made. And despite the specializations having similarly named member functions, those are still different functions inside different classes.

Your code will not build successfuly if you try to pass MyClass<int&>::f an rvalue,

myClass1.f(10); // ERROR

Reference collapsing makes the generated f's accept references of different value categories, but once the class is instantiated that member will accept only a specific value category. It is not forwarding the value category that is deduced at the call site, like a forwarding reference would.

0
votes

The thing about forwarding references is that they automatically become rvalue references or lvalue references during template argument deduction. The caller doesn't need to specify which they want.

In your example, you've explicitly specified int& and int&&, which forces f to have one type or the other. And then it takes lvalues or rvalues correspondingly, but not both like a forwarding reference can: myClass1.f(10); and myClass2.f(i); are both ill-formed.