1

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>

Zoom
  • 11
  • 3

2 Answers2

4

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.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • I got the first part, thanks. If T&& is not forwarding reference, it's must be a rvalue reference, but why does it accept lvalue reference (myClass1.f(i))? From the [Reference collapsing] rules you provided, The reference collapsing rules (save for A& & -> A&, which is C++98/03) exist for one reason: to allow perfect forwarding to work. So my understanding is, the 'reference collapsing' rules should only apply to those forwarding scenarios, but not for the rlvaue references. Please correct me. – Zoom Apr 26 '20 at 00:31
  • @Zoom - The reference collapsing rules simply express what happens when you have an alias to a reference (such as `T = A&`) and you apply another reference declarator to it (`T&` collapses to just `A&`). They are **designed** to facilitate the operation of `std::forward`, but obviously work in general everywhere, and not just when forwarding. – StoryTeller - Unslander Monica Apr 26 '20 at 06:23
  • I still dont get it why the class template case is not called a forwarding reference. For a regular forwarding reference in a simple function template, we need to instantiate the function template with a specific type T. Once type T is deduced, the function is fixed, and it wont accept other types, ie, 'std::forward(0);' is a bad forward call too. Same for a class template, we need to instantiate the template first. But it indeed has the ability to forward different types once the reference collapsing rules apply, although we need create objects due to the nature of the class usage. – Zoom Apr 26 '20 at 17:50
0

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.

aschepler
  • 70,891
  • 9
  • 107
  • 161