Reputation: 2552
I want understand more how std::forward
works and how Explicit Template Arguments & Template Argument Deduction work along with Forwarding references, I've tried this example:
template <typename T>
T&& fwd_(typename std::remove_reference<T>::type& arg){
std::cout << "fwd_(type&)\n";
return static_cast<T&&>(arg);
}
template <typename T>
T&& fwd_(typename std::remove_reference<T>::type&& arg){
std::cout << "fwd_(type&&)\n";
++arg;
return static_cast<T&&>(arg);
}
int main(){
int&& l = fwd_<int>(5);
int&& m = fwd_<int&&>(7);
int& j = fwd_<int&>(m);
int& k = fwd_<int&>(7); // UB?
std::cout << k << std::endl;
std::cout << '\n';
}
k
? and how come I can pass an r-value as an l-value? (fwd_<int&>(7)
)?I am sure that that l-value reference is bound to the function template parameter; not to the argument passed.
I am also sure that that expression yields undefined behavior because that parameter is destroyed when fwd_
returns.
I've tried that and changed the optimization level so I got different values because of UB.
Are my thoughts correct? And if so how could I write a clean and correct version that mimicks the library one std::forward
? Thank you!
Upvotes: 1
Views: 116
Reputation: 85276
I don't know what happened to k? and how come I can pass an r-value as an l-value?
(fwd_<int&>(7))
?
Yes this is dangerous, since T
is int&
and return value T&&
, as a result of reference collapsing we get int& &&
→ int&
, i.e. you end up returning a reference to a temporary, which is not life-extended, thus causing UB after the temporary is destroyed at the end of the full-expression (the ;
).
For this reason the C++ standard explicitly excludes this usage from std::forward
:
template <class T> constexpr T&& forward(remove_reference_t<T>& t) noexcept;
template <class T> constexpr T&& forward(remove_reference_t<T>&& t) noexcept;
. . .3 Remarks: If the second form is instantiated with an lvalue reference type, the program is ill-formed.
And libstdc++ for example has an assertion to fail early in this case:
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
{
static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
" substituting _Tp is an lvalue reference type");
return static_cast<_Tp&&>(__t);
}
Upvotes: 2