Hello I have this example from C++ primer:
template <typename T> void f(T&& x) // binds to nonconstant rvalues { std::cout << "f(T&&)\n"; } template <typename T> void f(T const& x) // lvalues and constant revalues { std::cout << "f(T const&)\n"; }
And here is my attempt to test the output:
int main(){
int i = 5;
int const ci = 10;
f(i); // f(T& &&) -> f(int&)
f(ci); // f(T const&) -> f(int const&)
f(5); // f(T &&) -> f(int&&)
f(std::move(ci)); // f(T&&) -> f(int const&&)
cout << '\n';
}
The output:
f(T&&)
f(T const&)
f(T&&)
f(T&&)
In the book the version of f
taking a forwarding reference is said to bind only to non-constant rvalues but in main
I've passed a constant rvalue f(5)
and f(std::move(ci))
but still the first version called.
Is there a way to call the second version f(T const&)
passing an rvalue? I know I can do it explicitly: f<int const&>(5);
or f<int const&>( std::move(ci) );
but I want to know where can be called passing in an rvalue? Thank you!
I think the comment after the forwarding reference version of f
is not correct: f(T&&) // binds to nonconstant rvalues
because it can be bound to constant rvalues so for example:
f(std::move(ci)); // f(int const&&) and not
f(int const&)
Template argument deduction is used when selecting user-defined conversion function template arguments. A is the type that is required as the result of the conversion. P is the return type of the conversion function template.
Universal reference was a term Scott Meyers coined to describe the concept of taking an rvalue reference to a cv-unqualified template parameter, which can then be deduced as either a value or an lvalue reference.
Template argument deduction is used in declarations of functions, when deducing the meaning of the auto specifier in the function's return type, from the return statement.
Type template parameter cannot be deduced from the type of a function default argument: Deduction of template template parameter can use the type used in the template specialization used in the function call: Besides function calls and operator expressions, template argument deduction is used in the following situations:
Template argument deduction is used when selecting user-defined conversion function template arguments. A is the type that is required as the result of the conversion. P is the return type of the conversion function template, except that a) if the return type is a reference type then P is the referred type;
In practice, almost all universal references are parameters to function templates. Because the type deduction rules for auto -declared variables are essentially the same as for templates, it’s also possible to have auto -declared universal references.
The comments in the book are, evidently, not entirely correct. When you have the two overloads available of
template <typename T> void f(T&& x);
template <typename T> void f(T const& x);
both of them can always be called with any argument (with some exceptions that I'll omit here), but the second one will be preferred if the argument is a const
lvalue. The first one will be preferred under all other circumstances thanks to the reference collapsing rules that apply when deducing template arguments.
However, let's say that T
is fixed by an enclosing class:
template <class T>
struct S {
void f(T&& x);
void f(T const& x);
};
and let's assume that T
is a non-const
object type. Now, the situation is different because T
is not deduced when calling f
. The comments in the book seem to be referring to this latter situation, where the first S::f
now may be called with non-const
rvalues of type T
but not with const
rvalues of type T
nor lvalues of (possibly const
) T
. The code, unfortunately, doesn't match up with the comments.
Let's go back to the actual code that's in the book. As I said, the function taking T const&
will be preferred when the argument is a const
lvalue. Let's say that the argument is 5
, but you want to explicitly call the second function even though it would not normally be selected. There are a few ways to do this. The one that is easiest to read is to actually turn the argument into a const
lvalue:
f((const int&)5);
Your suggestion of f<const int&>(5)
also works, but is a bit more confusing. The reason why it is confusing is that it requires the reader of the code to actually do the reference collapsing mentally and then remember that the first overload is less specialized than the second one. A third method is:
static_cast<void(*)(int const&)>(&f)(5);
Although this one is the hardest to read, the part before the (5)
can be used whenever one particular overload needs to be extracted and bound to a function pointer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With