deducing references to const from rvalue arguments
Asked Answered
L

3

10

Okay, this may seem like a silly question, but here it goes:

template <typename T>
void foo(T& x)
{
}

int main()
{
    foo(42);
    // error in passing argument 1 of 'void foo(T&) [with T = int]'
}

What is preventing C++ to instantiate the foo function template with T = const int instead?

Ludwig answered 20/5, 2011 at 16:43 Comment(0)
B
8

The problem is that template type deduction has to work out an exact match, and in that particular case, because of the reference in the signature, an exact match requires an lvalue. The value 42, is not an lvalue, but rather an rvalue, and resolving T with const int would not yield a perfect match. Since template type deduction is limited to exact matches, that deduction is not allowed.

If instead of using a literal you use a non mutable lvalue, then the compiler will deduce the type appropriatedly, as const int will become a perfect match for the argument:

const int k = 10;
foo( k );            // foo<const int>( const int & ) is a perfect match

Now there is a special rule that enables calling a function that takes a const reference (nonmutable lvalue) with an rvalue, that implies creation of a temporary lvalue which is later bound to the reference, but for that rule to kick in the function has to have that signature before hand, which is why explicitly stating that the type of the template is const int works: foo<const int>(42).

Blotchy answered 20/5, 2011 at 17:14 Comment(3)
+1 for pointing out that it's only template type deduction that fails, nothing to do with rules about reference binding, since foo<const int>(42) does compile. But the later part about not allowing two user-defined conversions is not so relevant.Pollen
@aschepler: The second part is absolutely irrelevant, but it is quite graphic and shares the similarity that there is an extra step needed that the compiler is not allowed to take. In the case of template type deduction because it requires a perfect match, in the case of type conversions because only one user conversion is allowed in the chain. Maybe it does not help making the point clearer... so I have removed it.Akers
I'm not sure if this answer is correct. Observation: There's a difference between class- and non-class type arguments. typedef const int X; foo( X() ); fails, while struct Y{}; typedef const Y X; foo( Y() ); is fine. Attempt of an explanation: The const of non-class non-array prvalues is removed as the type of the expression [expr]/6; additionally, in the OP's example, 42 is not a const int.Timmi
W
1

Them's the rules ;-). If you leave the compiler to deduce the type from the argument, it picks the simplest thing it can.

It doesn't seem unreasonable to me. Your template is saying it expects a non-const reference, so it doesn't compile with an rvalue.

You could either tell it what you mean at the call site: foo<int const>(42); or change your template to make it clear it doesn't need a mutable reference: template <typename T> void foo(T const & x) { }.

In C++11 you have more options for expressing what your template will and will not accept.

Worms answered 20/5, 2011 at 16:51 Comment(0)
P
0

Be it template or normal functions, rvalue cannot be passed by reference. (so const T& works but not T&).

What is preventing C++ to instantiate the foo function template with T = const int instead?

Suppose, C++ allows and makes T = const int instead. Now after sometime you change foo as,

template<typename T>
void foo (T& x)
{
  x = 0;
}

Now compiler has to generate error. For end user the experience will be strange, as for a valid statement like x = 0; it started giving error. That could be the reason that why compiler prevents at the first stage itself!

Pillsbury answered 20/5, 2011 at 16:51 Comment(2)
But the question is why template instantiated with T=int when T=const int would compile.Scuffle
There is some truth in the answer, in particular the exact answer is the bolded text: rvalue cannot be passed by reference (not even const&), but then you digress quite a bit... T can perfectly be deduced to be const int if the actual argument in the call is a non mutable lvalue (i.e. const int object, rather than rvalue 42), and the template function is always verified after instantiation, which means the if T is deduced to const int the foo that you provided will fail as expected --remember, the template is not compiled until it is instantiated.Akers

© 2022 - 2024 — McMap. All rights reserved.