Understanding template argument deduction with rvalue/lvalue - c++

This is a followup from function template does not recognize lvalue
Lets play with the following code:
#include <iostream>
template <class T>
void func(T&&) {
std::cout<<"in rvalue\n";
}
template <class T>
void func(const T&) {
std::cout<<"in lvalue\n";
}
int main()
{
double n=3;
func<double>(n);
func(n);
}
It prints:
in lvalue
in rvalue
I don't understand what's happening in the second call. How the compiler
resolve the template parameter ? Why isn't there any ambiguity ?

When you say func<double>(n), there's no argument deduction, since you specify the argument, and so the choice is between func(double &&) and func(const double &). The former isn't viable, because an rvalue reference cannot bind to an lvalue (namely n).
Only func(n) performs argument deduction. This is a complex topic, but in a nutshell, you have these two possible candidates:
T = double &: func(T &&) --> func(double &) (first overload)
T = double: func(const T &) --> func(const double &) (second overload)
The first overload is strictly better, because it requires one less conversion of the argument value (namely from double to const double).
The magic ingredient is the "reference collapsing", which means that T && can be an lvalue reference when T is itself a reference type (specificaly, double & && becomes double &, and that allows the first deduction to exist).

Related

Overload resolution with templates and rvalue references

This overload resolution behavior baffles me:
#include "stdio.h"
template<class T>
class C
{
public:
C(T v): m(v) {};
T m;
template<class U>
T f(U &&p)
{
printf("rRef called.\n");
return p;
}
template<class U>
T f(const U &p)
{
printf("const Ref called.\n");
return p;
}
};
int main()
{
C<int> a(5);
a.f<int&>(a.m);
a.f(a.m);
return 0;
}
Outputs:
const Ref called.
rRef called.
When debugging in gdb or Visual Studio, both debuggers show
int C<int>::f<int &>() called in both cases, but the explicit template resolution resolves to the expected const ref, while the second resolves to a rvalue reference. Why? Why doesn't the compiler even try
int C<int>::f<int>() which I thought would be the obvious match?
How can a rvalue reference bind to a member value? Isn't a.m a lvalue?
When you make the call:
a.f<int&>(a.m);
the compiler has to choose between the following candidates:
template<class U>
T f(U && p); // #1
template<class U>
T f(U const & p); // #2
For this overload resolution process, first both templates are transformed for the parameter int&.
Substituting U = int & for #1 gives int & &&, which due to reference collapsing, becomes int &.
Similarly, substituting U = int & for #2 gives int & const &, which again due to reference collapsing, becomes int &.
Now, since both overloads match, partial ordering is used to determine which template to call. Now U const & is more specialized than U &&. This is because U && can bind to all the types that U const & can, but the converse is not true.
Hence, since #2 is more specialized, it wins in overload resolution, and gets called.
In this call:
a.f(a.m);
the template parameter is not specified. This means the parameter of #1 is considered to be a forwarding reference, and this matches all types that are passed in, and so #1 gets called.

Non-const pointer prefers const T& overload to const T*

Suppose I have two overloads of a function
template <typename T>
void f(const T&) {
cout << "f(T&)" << endl;
}
template <typename T>
void f(const T*) {
cout << "f(T*)" << endl;
}
Why does f(new int) resolves to the f(const T&) instead of f(const T*)? Anywhere in the standard talks about this counter-intuitive behavior?
http://ideone.com/kl8NxL
For overload resolution with template deduction, the first step is to resolve the templates. Then non-template ordering is applied to the results. In your code the template resolutions are:
void f(int * const &) // 1
void f(int const *) // 2
According to C++14 [over.ics.ref], a reference binding directly to an argument as in (1) is an identity conversion (even if there are added cv-qualifiers). The binding of T to T const & is a direct binding, i.e. no temporaries are created and bound.
However, (2) involves a qualification conversion. The argument type int * must be converted to const int * before it matches the function parameter.
The identity conversion is considered a sub-sequence of any non-identity conversion sequence, so (1) wins according to the sub-sequence rule [over.ics.rank]/3.1.1

Function template overload resolution using const references

I am trying to understand the overload resolution rules in the following case:
template<typename T>
void f(const T& x) {
std::cout << __PRETTY_FUNCTION__ << std::endl; //-
}
template<typename T>
void f(T& x) { // <> Überladung Variante 2
std::cout << __PRETTY_FUNCTION__ << std::endl; //-
}
int main()
{
int e1 = 0;
f(e1);
const int e2 = 0;
f(e2);
}
The output is:
void f(T &) [T = int]
void f(const T &) [T = int]
As I understand in the first call to f(e1) leads to the viable functions
void f(const int&)
void f(int&)
from which the first one is chosen because the const-qualification hasn't to be removed.
The second call to f(e2) leads to the type deductions / viable functions
void f(const int&); // T -> int from first template overload
void f(const int&); // T -> const int from second overload
and the output shows that the first overload is choosen.
But why?
When performing type deduction with references, the const-ness (more specifically CV-ness) is not removed. So in your case the compiler has 2 overloads to choose from:
void f(const T &)
void f(T &)
The compiler then performs "pattern matching" when choosing the overload for your const int e2 = 0; argument. The first const overload is a better match (more specialized), as the second one would require deducing T as const int, which adds something (i.e. const-ness).
The rules for template type deductions are not super straightforward, so if you want to learn all nitty-gritty details about templates, I highly recommend the book
C++ Templates: The Complete Guide by David Vandevoorde and Nicolai M. Josuttis.
It's pre C++11, but nevertheless it tells you everything you can think of.
PS: you must make a differentiation between instantiation and template type deduction. The type deduction happens first, then an instantiation follows. So in your case you don't have 2 ambiguous instantiations as you may have thought initially.

How to filter const types and non const types using meta programing?

I have this code
#include <iostream>
size_t F()
{
return 0;
}
template <class Type, class... NextTypes>
size_t F(const Type& type, const NextTypes&... nextTypes)
{
if (!std::is_const<Type>::value)
return sizeof(type) + F(nextTypes...);
else
return F(nextTypes...);
}
int main()
{
int a = 0;
const int b = 0;
const size_t size = F(a,b);
std::cout << "size = " << size << std::endl;
return 0;
}
I'm trying to know in compilation time the total size of constant parameters and non const parameters. The current out put is 8, for some reason the compiler thinks b is not constant, I used typeid and decltype to print the types of a and b and indeed the output shows b is an int and not const int as I expected. What am I missing? Is it possible to separate a variadic set of arguments to const arguments and non const?
Consider this function template:
template<typename T>
void deduce(const T&);
If you let the compiler deduce a type for T from an argument expression, the deduced type will never be const: It will try to make the const T of the function parameter identical to the type of the argument expression used to call the function. For example:
struct cls {};
const cls c;
deduce(c) // deduces T == cls
By deducing T == cls, the compiler succeeds in making const T identical to the argument type const cls. There is no reason for the compiler to produce two different functions for const- and non-const argument types; the parameter type of the function template instantiation will be const-qualified in any case: you requested it by saying const T& instead of, say, T&.
You can deduce the const-ness of an argument by not cv-qualifying the function parameter:
template<typename T>
void deduce(T&);
However, this will fail to bind to non-const temporaries (rvalues). To support them as well, you can use universal references:
template<typename T>
void deduce(T&&);
This will deduce an lvalue-reference type for T if the argument is an lvalue, and no reference if the argument is an rvalue. The const-ness will be deduced correctly.
For example, if the argument has the type const A and is an lvalue, T will be deduced to const A&. The function parameter then is const A& &&, which is collapsed to const A& (an lvalue-reference). If the argument is an rvalue, T will be deduced to const A, and the function parameter becomes const A&& (an rvalue-reference).
Note that since T can be a reference in this case, you need to remove that before checking for const-ness: std::is_const< typename std::remove_reference<T>::type >::value.

Moving const and overloaded Universal Reference in C++

I am watching Scott Meyer's video "The Universal Reference/Overloading Collision Conundrum", where he gives an example of what not to do:
class MessedUp {
public:
template<typename T>
void doWork(const T& param) { std::cout << "doWork(const T& param)" << std::endl; }
template<typename T>
void doWork(T&& param) { std::cout << "doWork(T&& param)" << std::endl; }
};
.... //somewhere in the main
MessedUp m;
int w = 10;
const int cw = 20;
m.doWork(cw); // calls doWork(const T& param) as expected
m.doWork(std::move(cw)); // Calls doWork(T&& param)
I am curious as to why compiler chose doWork(T&& param) rather than doWork(const T& param) during Template Overload resolution. As far as I know, const objects can't be moved.
After template type deduction and substitution, the two overloads become:
//template<typename T>
void doWork(const int& param) { std::cout << "doWork(const T& param)" << std::endl; }
//template<typename T>
void doWork(const int&& param) { std::cout << "doWork(T&& param)" << std::endl; }
Note how T in the second overload has been deduced to const int.
Now, what happens is normal overload resolution: We compare the implicit conversion sequences required to convert the argument expression std::move(cw) (which is an xvalue of type const int) to the parameter types. Both rank as Exact Matches, so we have to look at the tie-breakers in [over.ics.rank]/3 and compare the two implicit conversion sequences S1 and S2 (a reference binding here is a conversion sequence):
Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if
[...]
S1 and S2 are reference bindings [...], and S1 binds an rvalue reference to an rvalue and S2 binds an lvalue reference.
As an xvalue is an rvalue (and a glvalue), this point applies, and the second overload is chosen.
&& does not mean move, it means rvalue reference. rvalue references will only bind to temporary(anonymous) objects, or objects cast to appear to be temporary objects by functions like std::move or std::forward, or objects automatically marked by the compiler to be temporary like locals returned from function on simple return X; lines.
You can have an rvalue reference to a const object. When this happens, you cannot move (unless mutable state can be moved), but it is still an rvalue reference.
Now, T&& can bind to an lvalue reference if T is an lvalue reference, so in type deduction context T&& can be called a universal reference. So one of the real problems with the above design is that m.doWork(w) will also bind to T&& with T=int&.
In overload resolution, a function that takes a template<typename T> void foo(T&&) with T=foo& be considered to be a worse match than template<typename T> void foo(T&) with T=foo if everything else is equal: but in your case, there is no T such that foo(T const&) is a foo(T const&&).
&& are useful to determine temporary rvalues from non-rvalue. So, you can steal the resource safely.
When you use std::move it casts the type to a rvalue and compiler will uses && overload.
What is happening is that doWork(T&& param) is being called with T = const int, because that is a perfect match (instead of a conversion to lvalue).
If you had trued to move the object, it would indeed have failed because const objects can't be moved.