Suppose we receive an rvalue int, by the rules of deduction of the templates, T = int. Then in this fragment:
decltype(forward(arg).get())
Being arg a rvalue will call the first function get()
of arg
, which returns i_type=int
.
forward
, will return int&&
(reference rvalue).
decltype(expr)
will return int&&
.
It is not a correct approach.
decltype
does not execute anything ... only evaluates (if executed it could modify values and that will never happen). Thus, decltype
will evaluate the expression that is happening to it, forward(arg).get()
and will copy the final type of the expression.
Since decltype
is not going to execute code, it does not make sense to use forward
, since in this case it will not matter to us to work with an R-value or with an L-value (no copy will be created or any movement made). So forward(arg)
is equivalent to arg
. Now the call is simpler: decltype(arg.get())
. And what is the return type of the call arg.get()
? exact, int
, then decltype(forward(arg).get())
becomes int
.
Do not forget that decltype
is processed in compile time then the binary results in the processing. If you examine the assembly you will see that there is no reference to decltype
... decltype
is used to let the compiler determine what the type should be ... and this choice is made at compile time.
Bonus
std::forward
is a utility that allows the compiler to determine whether a parameter should be used as R-value or as L-value . You can find more information about this in this question .
As you see, std::forward
only makes sense when you deal with arguments in template functions because these functions can be passed an R-value or an L-value and, a priori, you do not have mechanisms to determine what to do in each case.
This is because the std::forward
surrounding get
does not make any sense ... what you try to decide is if you have to use arg
as R-value or as L-value ... but once this is decided, the call to get
will not be so traumatic because the function itself already has a return type that is going to be unmovable.
In principle you could argue that the return of get
is variable because you have two versions of that function ... and it is true ... but what determines what get
calls does not depend on the std::forward
that surrounds to get
but of the std::forward
that surrounds arg
:
Arg a;
a.get() = 10; // Ok, int& get()
std::move(a).get() = 10; // Error, int get() &&
So the template could be greatly simplified:
template<class T>
void wrapper(T&& arg)
{
foo(std::forward<T>(arg).get());
}