Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can't bind lvalue to rvalue in member function but ok in global function

While looking at the reference pages for std::forward I came across something odd. The example is passing an lvalue as an rvalue reference.. but to a global function... and it compiles and runs. I tried the same thing as a member function and it fails to compile. What gives? I would expect both calls to fail without the use of std::move or std::forward<T>.

#include <iostream>

template <typename T>
void globalDoSomething(T &&data) {
    std::cout << "We're doing it!!" << std::endl;
}

template <typename T>
class A {
public:
    void doSomething(T &&data);
};

template <typename T>
void A<T>::doSomething(T &&data)
{
    std::cerr << "Ah, man. I won't compile." << std::endl;
}

template class A<int>;

int main()
{

    int b = 0;
    globalDoSomething(b);

    A<int> a;
    a.doSomething(b);

    return 0;
}
like image 464
atomSmasher Avatar asked Dec 17 '22 12:12

atomSmasher


2 Answers

It's because the automatic template deduction for globalDoSomething infers T as int&.

If you explicitly instantiate the template function with globalDoSomething<int>(b); like you did for the member function of the template class, it will also fail to compile.

Conversely, if you instantiate the template class with A<int&> a;, it will successfully compile.

like image 187
Patrick Roberts Avatar answered May 16 '23 06:05

Patrick Roberts


To build/add on @Patrick Roberts answer, from template argument deduction

If P is an rvalue reference to a cv-unqualified template parameter (so-called forwarding reference), and the corresponding function call argument is an lvalue, the type lvalue reference to A is used in place of A for deduction

In short, while b is an lvalue, it infers T as int& indeed. And as shown in the example

template<class T>
int f(T&& x) {                    // x is a forwarding reference
    return g(std::forward<T>(x)); // and so can be forwarded
}
 
int main() {
    int i;
    f(i); // argument is lvalue, calls f<int&>(int&), std::forward<int&>(x) is lvalue
}

To test this, you can set a few overloaded functions with different types and check

#include <iostream>
#include <utility>


void f(int&& x) {
    std::cout << "rvalue reference overload f("  ")\n";
}

void f(const int& x) {
    std::cout << "lvalue reference to const overload f("  ")\n";
}

void f(int& x) {
    std::cout << "lvalue reference overload f("  ")\n";
}

template <typename T>
void globalDoSomething(T &&data) {
    f(data);
}

int main()
{
    int b = 0;
    globalDoSomething(b); 
    return 0;
}

And the output is

lvalue reference overload f()

like image 35
Tony Tannous Avatar answered May 16 '23 06:05

Tony Tannous