Today I ran into roughly the following code:
#include <iostream>
void f(float&& f) { std::cout << f << "f "; }
void f(int&& i) { std::cout << i << "i "; }
int main()
{
int iv = 2; float fv = 1.0f;
f(2); f(1.0f);
f(iv); f(fv);
}
The first two f-calls print 2i 1f
, as expected.
Now for the second line, I would have expected that it either doesn’t compile at all, since iv and fv are not temporaries (and thus can't bind to an r value reference), or that it creates a copy of the variable to pass to the function, and thus print 2i 1f
a second time.
However, somehow it prints 2f 1i
, which is just about the last thing I would have expected.
If you copy the code into cppinsights, it transforms the calls into
f(static_cast<float>(iv));
f(static_cast<int>(fv));
So it seemingly very intentionally casts the integer to a float, and the float to an integer, but I don't have any idea why it does that, and don't really know how to google that either. Why does this happen? What are the rules that lead to this result?
The behavior of the program can be understood from reference-initialization.
From dcl.init#ref-5.4:
Case 1
Here we discuss why
void f(int&& i)
isn't viable for the callf(iv)
.The lvalue
iv
cannot be bind to the rvalue reference parameteri
invoid f(int&& i)
for the callf(iv)
and so the overloadf(int&&)
isn't viable. Basically,int&& i = iv;
isn't allowed becauseiv
is an lvalue of related type.Case 2
Here we discuss why
void f(float&& i)
is viable for the callf(iv)
.For the call
f(iv)
the overloadvoid f(float&& f)
is viable because here first the initializer expressioniv
is implicitly converted to a prvalue of the destination type(float
) and then temporary materialization can happen such that the parameterf
can be bound to that materialized temporary2.0f
(which is an xvalue).Similarly for the call
f(fv)
, the overloadvoid f(float&& i)
isn't viable becausefv
is an lvalue of related type. And for the callf(fv)
the overloadvoid f(int&& i)
can be used because first the initializer is implicitly converted to a prvalue and then temporary materialization happens such thati
can be bound to the materialized temporary1
(of typeint
).