Jaebum
Jaebum

Reputation: 1570

C++ Template type deduction for temporary value

#include <iostream>
#include <vector>
using namespace std;

template <typename T>
void wrapper(T& u)
{
    g(u);
}

class A {};

void g(const A& a) {}
int main()
{
    const A ca;
    wrapper(ca);
    wrapper(A()); // Error
}

Hi I have a question on why the last statement gives a compiler error.

:18:10: error: cannot bind non-const lvalue reference of type 'A&' to an rvalue of type 'A' wrapper(A());

I thought that the template type T would be deduced as const A& as the ca is also deduced as const A&. Why the type deduction fails in this case?

Upvotes: 4

Views: 771

Answers (3)

Anirban Sarkar
Anirban Sarkar

Reputation: 826

The error message is trying to convey:

A non-const reference cannot bind to a temporary value because the temporary object's lifetime would have expired before the control reaches the function.

  • The variable ca is not a temporary, because it has a name that can be referred to in main.
  • It's lifetime does not expire till the end of main, and so can be safely referenced from within wrapper.
  • Template type deduction does not drop const since it would be a violation of const-correctness for ca.

For wrapper(A());, the type parameter T would be deduced as A. Since the temporary has no const-ness, so the parameter u would be deduced to a A&, but a since non-const reference cannot bind to a temporary value for the reason mentioned above, there is no wrapper function that is viable to be called using the argument A().

Now, C++ features the extension of lifetime for a temporary object if you are only reading from it. This is the reason why const T& binds to temporary objects. In your case the nameless temporary object's lifetime is extended till the end of the wrapper function.

If you explicitly set the type parameter to be const T& the following:

template <typename T>
void wrapper(const T& u)
{
    g(u);
}

class A {};

void g(const A& a) {}

int main()
{
    const A ca;
    wrapper(ca);
    wrapper(A()); // Error no more.
}

would compile just fine.


[C++11]

For wrapper(A());, the type parameter T would still be deduced as A, and the parameter u would be of type A&&, called an rvalue reference to A. Suppose we add another overload to wrapper so that the following:

template <typename T>
void wrapper(const T& u)
{
    g(u);
}
template <typename T>
void wrapper(T&& u)
{
    g(u);//u is an lvalue since it has a name.
}

exists.

You can expect wrapper(A()); to compile, preferring the rvalue overload, because the rvalue reference is an lvalue in wrapper as it has a name.

Upvotes: 1

jfMR
jfMR

Reputation: 24778

Why the type deduction fails in this case?

It does not fail. T is deduced to A. Therefore, the parameter u is of type A&.

The call to wrapper() is what actually fails, since an rvalue (i.e.: A() in this case) can't be bound to a non-const lvalue reference (i.e.: the parameter u).

Upvotes: 6

I thought that the template type T would be deduced as const A& as the ca is also deduced as const A&. Why the type deduction fails in this case?

Because that's not how the deduction rules work. They strive to deduce as much of a match to the function argument type as possible. A temporary is not necessarily const, it can just bind to a const reference.

But your function template does not accept by a const reference, instead it accepts by a non-const lvalue reference. So no const is gonna spring up unless the function argument is const itself (which it isn't).

The proper solution is to use a forwarding reference (an rvalue reference to a deduced template parameter):

template <typename T>
void wrapper(T&& u)
{
    g(std::forward<T>(u));
}

Now u can bind to any object. And the types deduced will tell you the value category of the function argument, allowing you to forward that category to the function call g(...), and making sure the proper overload is picked.


By the way, in case you are curious, if you add the const directly to the temporary type, your original code will build just fine.

using CA = A const;
wrapper(CA()); // Not an error anymore

Now u ends up being a A const& and binds to the temporary just fine. But that is just a curiosity that's unlikely to be useful in practice. Use forwarding references.

Upvotes: 6

Related Questions