"Perfect forwarding" is used in templates, i.e.:
template<typename T>
void func(T && arg)
{
func2(std::forward<T>(arg));
}
Outside of template context the end result drastically changes. All that std::forward does is return static_cast<T &&>, so your call function becomes nothing more than:
void call(int& other) {
check(static_cast<int &&>(other));
}
Hence you get an rvalue. The reason this works differently in templates is because a && template parameter is a forwarding reference (a fancy term for either an lvalue or an rvalue-deduced reference, depending on what gets dropped in that parameter), and because of reference collapsing rules. Briefly, when used in a template context, the end result is:
T gets deduced as either an lvalue or an rvalue reference, depending on what the parameter is.
The result of the static_cast<T &&> is an lvalue reference, if T is an lvalue reference, or an rvalue reference if T is an rvalue reference, due to reference collapsing rules.
The end result is that the same kind of a reference gets forwarded. But this only works in template context, since it requires both forwarding reference semantics and reference collapsing rules to work just right.