Type deduction understanding is crucial to feel comfortable with modern c++ We know that using function templates we have a mechanism that enable the discovery of the parameter type passed to function by caller.
Consider the function template case 1 :
Case 1 – Param´s Type is a Reference or Pointer
-Param´s Type is a Reference or Pointer
Consider the function template:
template<typename T>
void f(T& param);
When we call f(expression) the compiler will deduce two types: one for T and another for expression.
We have these variable declarations:
int x = 1; // x is an int
const int y = x; // cx is a const int
const int& ry = x; // ry is a reference to x as a const int
the deduced types for param and T in the various calls are as follows:
f(x) ; //T is int& and param´s type is int&,
f(y); //T is int& and param´s type is const int&,
f(z); // T is int& and param´s type is const int&,
Case 2 – Param´s Type is a universal reference
-Case 2: Param's Type is a Universal Reference
Universal references are not rvalues references or lvaluereferences, can be both. See last post
template<typename T>
void f(T&& param);
The function f param is a rvalue reference and we have, as
before, the
variable declarations:
int x = 1; // x is an int
const int y = x; // y is a const int
const int& z = x; // z is a const int reference to x int
The deduced types for param and T in various calls are as follows:
f(x) ; //T is int& and param´s typ is T is int&,
f(y); //T is int& and param´s type is const int&,
f(z); // T is int& and param´s type const int&,
f(1) // T is and param´s type is const int&&,
For rvalues reference param´s type the type deduction is :
- If we call function f(int&& param) with a lvalue arguments the deduced type is int&
- If If we call function f(int&& param) with a rvalue arguments the deduced type is int&&
This make possible the conditional cast provided by std::forward as we saw in my last post. How all this work?
In a function all parameters are lvalues. If we have a function f(T param) is possible to use a rvalue or a lvalue argument to call the function. If we can take the address of the argument used, that is a lvalue. If is not possible to take the address we are using a rvalue.
If we have the function T f_arg() is possible to call f(f_arg()). The return of f_arg() is a rvalue. Why? Because is not possible to take the address of the returned value of f_arg().
The C++ compiler does not allow to define Int& &, reference for reference type . Having the function f(T& param) and T& arg = x
and calling f(arg) the compiler will have : f(T& &). The compiler collapse the T& & to T& to solve the situation .
There is a lot lot of combinations where is necessary some collapsing. Let´s see the collapsing rules used by compiler:
T& & becomes T& (lvalue)
T& && becomes T& (lvalue)
T&& & becomes T& (lvalue)
T&& && becomes T&& (rvalue)
This mechanism enable the perfect_forwarding . We used, in the last post the function std::forward<T> .
How perfect_forwarding works?.
The T&& parameter can receive, both, lvalues and rvalues and we can
use the basic implementation to replace std::forward<T>:
template <typename T>
T&& forward(typename std::remove_reference<T>::type& param)
{
return static_cast<T&&>(param);
}
This function executes a conditional cast to T&&. Only cast to
rvalue reference, rvalue arguments.
How it works?
Case 1-The argument is int&, T will also become T&
The compiler see the code:
int& && forward(typename std::remove_reference<int&>::type& param)
{
return static_cast<int& &&>(param);
}
The collapsing rules are applied : T& & becomes T& (lvalue) and T& && becomes T& (lvalue) and the function return a cast to lvalue, int&&.
int& forward(int& param)
{
return static_cast<int&>(param);
}
Case 2-The argument is int&&, T will also become T&&
The compiler compiler see the code::
int&& && forward(typename std::remove_reference<int&&>::type& param)
{
return static_cast<int&& &&>(param);
}
The collapsing rules are applied : T&& && becomes T&& (rvalue) and T&& && becomes T&& (rvalue) Forward receives a int&& param and return a cast to rvalue, int&&.
int&& forward(int& param)
{
return static_cast<int&&>(param);
}
As we can see the function forward can return a rvalue, case1, or a lvalue, case 2. Visit the last post https://lnkd.in/duYWZJy
Next post : Smart pointers