Template class method f(const T) don't accept rvalue when T is lvalue reference

71 views Asked by At

In the following codes, I have two versions of class A instantiated, one is bound to int and the other to int&.

The method forward has const in its parameter, so the int& version should have the parameter const int& t.

A rvalue can be used as const T&, however, the compiler complains about binding a non-const lvalue to a rvalue.

I have const in the code, so why isn't it working?

#include <iostream>
using namespace std;

template <typename T>
class A
{
public:
    T forward(const T t)
    {
        return t;
    }
};

int main()
{
    A<int> a;
    A<int&> al;
    int i = 1;
    cout << a.forward(1) << endl;  //ok
    cout << al.forward(i) << endl; //ok
    cout << al.forward(1) << endl;  //error: non-const lvalue reference to type 'int' cannot bind to a temporary of type 'int'
    return 0;
}

The error goes away when I add these codes or replace A<int&> with A<const int&>.

template <typename T>
class A<T&>
{
public:
    T& forward(const T& t)
    {
        return t;
    }
};

But I don't understand why.

2

There are 2 answers

0
user12002570 On BEST ANSWER

References cannot be const-qualified in C++. This means that your attempt to add a top-level const to int& does not work(i.e., ignored).

This essentially means that the parameter of A<int&>::forward is of type int& which can only bind to an lvalue.

0
alagner On

For A<int> a; forward will take argument of type const int. However, for A<int&> al; acts as if forward were to take argument of type int& const which is not possible.

You can provide two functions and SFINAE-away one of them, e.g. this way:

#include <type_traits>
#include <iostream>

template <typename T>
class A
{
public:
    T forward(const T t) requires(!std::is_reference_v<T>)
    {
        return t;
    }

    auto forward(
        std::add_lvalue_reference_t<std::add_const_t<std::remove_reference_t<T>>> t) -> decltype(t) 
        requires(std::is_reference_v<T>)
    {
        return t;
    }

};


int main()
{
    using std::cout;
    A<int> a;
    A<int&> al;
    int i = 1;
    cout << a.forward(1) << '\n';  //ok
    cout << al.forward(i) << '\n'; //ok
    cout << al.forward(1) << '\n'; //risky
    static_assert(std::is_same_v<const int&, decltype(al.forward(i))>);
    return 0;
}

Note that this gets risky for rvalues and requires more thorough implementation.

https://godbolt.org/z/Gv494G6bv