Actually, exception handling has special rules with respect to lvalues and rvalues. The temporary exception object is an lvalue, see 15.1/3 of the current draft:
A throw-expression initializes a temporary object, called the exception object, the type of which is determined by removing any top-level cv-qualifiers from the static type of the operand of throw and adjusting the type from “array of T” or “function returning T” to “pointer to T” or “pointer to function returning T”, respectively. The temporary is an lvalue and is used to initialize the variable named in the matching handler (15.3). If the type of the exception object would be an incomplete type or a pointer to an incomplete type other than (possibly cv-qualified) void the program is ill-formed. Except for these restrictions and the restrictions on type matching mentioned in 15.3, the operand of throw is treated exactly as a function argument in a call (5.2.2) or the operand of a return statement.
And catching by rvalue reference is illegal, too, see 15.3/1:
The exception-declaration in a handler describes the type(s) of exceptions that can cause that handler to be entered. The exception-declaration shall not denote an incomplete type or an rvalue reference type. The exception-declaration shall not denote a pointer or reference to an incomplete type, other than void*, const void*, volatile void*, or const volatile void*.
Also, you don't seem to understand perfect forwarding. Your forward invocation is no better than a move. The idea of perfect forwarding is to encode the value category of the argument as part of the type and let template argument deduction figure it out. But your exception handler is not and cannot be a function template.
Basically, perfect forwarding relies on template argument deduction and rvalue references:
void inner(const int&); // #1 takes only lvalues or const rvalues
void inner(int&&); // #2 takes non-const rvalues only
template<class T>
void outer(T && x) {
inner(forward<T>(x));
}
int main() {
int k = 23;
outer(k); // outer<T=int&> --> forward<int&> --> #1
outer(k+2); // outer<T=int> --> forward<int> --> #2
}
Depending on the value category of the argument, template argumend deduction deduces T to be either an lvalue reference or a normal value type. Due to reference collapsing, T&& is also an lvalue reference in the first case, or an rvalue reference in the second case. If you see T&& and T is a template parameter which can be deduced, it's basically a "catch everything". std::forward restores the original value category (encoded in T) so we can perfectly forward the argument to the overloaded inner functions and select the correct one. But this only works because outer is a template and because there are special rules for determining T with respect to its value category. If you use an rvalue references without templates/template argument deduction (like in #2), the function will only accept rvalues.